TCP 重传基础:序列号与确认号的核心作用

说到 TCP 重传,很多人第一反应就是“丢包了重发呗”。但实际抓包分析时你会发现,事情远没那么简单。为什么有的重传间隔 200ms,有的却要等 1 秒?为什么有些包明明没丢,却触发了重传?

我个人习惯,在讲重传定位之前,先把地基打牢。这个地基就是——序列号(SEQ)和确认号(ACK)。你想想看,没有这两个数字,TCP 根本不知道丢了什么、该重传什么。

2.1 序列号:数据流的“页码”

TCP 是面向字节流的协议。它不关心你发的是 HTTP 请求还是 SSH 命令,它只认字节。每个字节都有一个编号,从 0 到 2³²-1,循环使用。

序列号(SEQ)就是当前数据段中第一个字节的编号。比如你发 100 个字节,SEQ=1000,那这 100 个字节的编号就是 1000~1099。

关键点:SEQ 不是报文段的编号,而是字节的编号。这个区别很重要,我见过不少新手把 SEQ 当成“第几个包”,结果分析重传时完全对不上号。

举个例子。假设客户端要发 500 字节的数据,MSS(最大段大小)设为 1460 字节。它会分成两个段:

报文段 SEQ 数据长度 字节范围
第 1 段 1000 1460 1000 ~ 2459
第 2 段 2460 500 2460 ~ 2959

注意看,第 2 段的 SEQ 不是 1001,而是 2460。因为前 1460 个字节已经被第 1 段占用了。这个逻辑搞清楚了,后面看重传时序图才不会晕。

2.2 确认号:接收方的“已读回执”

确认号(ACK)表示接收方期望收到的下一个字节的序列号。说白了,就是“我已经收到了你发来的所有字节,直到 ACK-1 为止,现在请发 ACK 这个编号的字节”。

还是刚才的例子。接收方收到 SEQ=1000、长度 1460 的段后,会回复 ACK=2460。意思是:“1000~2459 我都收到了,下一个请发 2460”。

一个小技巧:抓包时看 ACK 字段,就能判断对端是否收到了某个段。如果 ACK 一直停在某个值不动,那这个值之前的字节大概率丢了。

这里有个容易混淆的点:ACK 是累积确认。什么意思?即使中间丢了一个包,只要后续包到达,ACK 也不会往前跳。比如你发了 SEQ=1000、2000、3000 三个段,中间 2000 丢了,接收方收到 3000 后,仍然回复 ACK=2000。它不会说“我收到了 3000,请重传 2000”,而是坚持“请发 2000”。

嗯,这个机制就是 TCP 重传的根源——重复 ACK。

2.3 序列号与确认号的协同工作

光讲概念太干,我们画个图看看它们怎么配合。

TCP 序列号与确认号交互流程 发送方 接收方 ① SEQ=1000, 数据 1460 字节 ② ACK=2460, 窗口=65535 ③ SEQ=2460, 数据 500 字节 ④ ACK=2960, 窗口=65535 丢包场景:段 3 丢失 ⑤ SEQ=2960, 数据 1000 字节 ✗ 丢失 ⑥ 重复 ACK=2960 (第 1 次) ⑦ 重复 ACK=2960 (第 2 次) ⑧ 重复 ACK=2960 (第 3 次) → 触发快速重传

正常流程很简单:发一段,收一个 ACK,再发下一段。但丢包发生时,接收方会反复回复相同的 ACK。收到 3 次重复 ACK 后,发送方就知道——这个包丢了,得重传。这就是快速重传的触发条件。

2.4 重传计时器:RTO 的来龙去脉

除了重复 ACK 触发重传,TCP 还有一个兜底机制——重传超时(RTO)。发送方每发一个段,就启动一个计时器。如果计时器到期还没收到 ACK,就认为包丢了,立即重传。

RTO 不是固定值。它根据网络往返时间(RTT)动态计算。公式大致是:

SRTT = (1 - α) × SRTT + α × RTT
RTTVAR = (1 - β) × RTTVAR + β × |SRTT - RTT|
RTO = SRTT + 4 × RTTVAR

其中 α 通常取 1/8,β 取 1/4。说白了,RTO 就是“平均 RTT 加上 4 倍抖动”。网络越不稳定,RTO 越大。

我曾经踩过一个坑:某次线上业务延迟飙升,抓包发现大量超时重传。查了半天,发现是中间链路的 RTT 从 10ms 突然跳到 500ms,但 RTO 还按旧值 200ms 计算。结果每个包都超时重传,雪上加霜。后来我养成了一个习惯——监控 RTT 的抖动值,RTTVAR 比 RTT 本身更能反映网络健康度。

2.5 序列号回绕与 PAWS

序列号只有 32 位,最大 4,294,967,295。高速网络下,这个数字很快会用完。比如 10Gbps 链路,不到 3 秒就绕一圈回来了。

序列号回绕会导致一个问题:新包的 SEQ 和旧包的 SEQ 相同,接收方怎么区分?

TCP 用了一个叫 PAWS(Protection Against Wrapped Sequences) 的机制。它在 TCP 选项字段里带一个时间戳,接收方通过时间戳判断哪个包更新。这个细节在抓包分析时很有用——如果你看到 SEQ 相同但时间戳不同的包,别慌,不是重传,是回绕。

场景 SEQ 特征 时间戳特征 判断结论
正常传输 递增 递增 正常
超时重传 与之前相同 与之前相同 重传
序列号回绕 与之前相同 更大 新数据,非重传
重复 ACK 触发重传 与之前相同 与之前相同 快速重传

2.6 实战视角:抓包时怎么看 SEQ/ACK

纸上谈兵差不多了,说说实际怎么用。我一般用 Wireshark 分析重传问题时,会关注这几个点:

  1. 看 ACK 是否停滞:如果连续几个包的 ACK 都是同一个值,说明对端没收到新数据。停滞超过 3 次,大概率丢包了。
  2. 看 SEQ 是否跳跃:正常传输 SEQ 是连续的。如果 SEQ 突然跳了一大段,说明中间有数据被重传了(Wireshark 会标记为 TCP Retransmission)。
  3. 看时间戳差值:Wireshark 的 TSval 和 TSecr 字段能帮你判断是原始包还是重传包。时间戳相同的两个包,一定是重传。
一个小工具:Wireshark 的“分析 → 专家信息”里会直接列出重传、重复 ACK、零窗口等事件。我每次排查性能问题,第一件事就是打开专家信息窗口,扫一眼有没有红色标记。