TCP 重传基础:序列号与确认号的核心作用
说到 TCP 重传,很多人第一反应就是“丢包了重发呗”。但实际抓包分析时你会发现,事情远没那么简单。为什么有的重传间隔 200ms,有的却要等 1 秒?为什么有些包明明没丢,却触发了重传?
我个人习惯,在讲重传定位之前,先把地基打牢。这个地基就是——序列号(SEQ)和确认号(ACK)。你想想看,没有这两个数字,TCP 根本不知道丢了什么、该重传什么。
2.1 序列号:数据流的“页码”
TCP 是面向字节流的协议。它不关心你发的是 HTTP 请求还是 SSH 命令,它只认字节。每个字节都有一个编号,从 0 到 2³²-1,循环使用。
序列号(SEQ)就是当前数据段中第一个字节的编号。比如你发 100 个字节,SEQ=1000,那这 100 个字节的编号就是 1000~1099。
举个例子。假设客户端要发 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 也不会往前跳。比如你发了 SEQ=1000、2000、3000 三个段,中间 2000 丢了,接收方收到 3000 后,仍然回复 ACK=2000。它不会说“我收到了 3000,请重传 2000”,而是坚持“请发 2000”。
嗯,这个机制就是 TCP 重传的根源——重复 ACK。
2.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 越大。
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 分析重传问题时,会关注这几个点:
- 看 ACK 是否停滞:如果连续几个包的 ACK 都是同一个值,说明对端没收到新数据。停滞超过 3 次,大概率丢包了。
- 看 SEQ 是否跳跃:正常传输 SEQ 是连续的。如果 SEQ 突然跳了一大段,说明中间有数据被重传了(Wireshark 会标记为 TCP Retransmission)。
- 看时间戳差值:Wireshark 的 TSval 和 TSecr 字段能帮你判断是原始包还是重传包。时间戳相同的两个包,一定是重传。