4. TCP 重传机制:快速重传与超时重传、重复 ACK 与 SACK 选项、重传对吞吐量的影响
TCP 重传,说白了就是网络世界的「后悔药」。数据包丢了,TCP 得想办法补发。但怎么补、什么时候补、补多少,这里面门道很深。我见过不少性能问题,根子就出在重传策略上。
今天咱们把重传机制拆开揉碎了讲。你理解了这个,Wireshark 里那些红红绿绿的包,就不再是乱码了。
4.1 两种重传:超时重传 vs 快速重传
TCP 重传分两种:超时重传和快速重传。它们触发条件不同,对性能的影响也天差地别。
4.1.1 超时重传(RTO)
发送方发了一个包,然后启动一个定时器。如果定时器到期还没收到 ACK,它就认为包丢了,直接重传。这个定时器的时间叫 RTO(Retransmission Timeout)。
RTO 不是固定的。TCP 会动态计算 RTT(往返时间),然后根据 RTT 的波动来调整 RTO。公式大致是:
RTO = SRTT + max(G, 4 × RTTVAR)
其中 SRTT 是平滑后的 RTT,RTTVAR 是 RTT 的偏差。说白了,网络越不稳定,RTO 就越大。
超时重传还有个坏毛病:指数退避。第一次超时等 200ms,第二次等 400ms,第三次 800ms……一直翻倍到上限(通常 60s 或 120s)。
我曾经排查过一个跨洋传输慢的问题。抓包一看,RTO 已经退避到 12.8 秒了。每丢一个包,链路就「发呆」十几秒。吞吐量能高才怪。
4.1.2 快速重传
快速重传就聪明多了。它不傻等定时器,而是靠接收方的「反馈」来触发。
接收方收到乱序包时,会立即回复一个 ACK,确认它期望的下一个序号。比如接收方期望 Seq=100,结果来了个 Seq=200,它就会回复 ACK=100。如果后续又收到 Seq=300、Seq=400,它还是回复 ACK=100。
发送方收到 3 个或以上的重复 ACK 时,就认为包丢了,不等超时直接重传。这就是快速重传。
关键点:快速重传的触发条件是「收到 3 个重复 ACK」。为什么是 3 个?因为 1 个可能是乱序,2 个也可能是乱序,3 个基本可以确定是丢了。这是 TCP 设计者的经验值。
快速重传的好处是快。它不需要等 RTO,通常在几毫秒内就能触发。这对吞吐量的影响小得多。
4.2 重复 ACK 与 SACK 选项
重复 ACK 是快速重传的基础。但光有重复 ACK 还不够——它只能告诉发送方「丢了」,但说不清「丢了哪些」。这就引出了 SACK。
4.2.1 重复 ACK 的工作原理
咱们看个例子。发送方发了 4 个包:Seq=100、200、300、400。接收方收到了 100、200、400,但 300 丢了。
- 收到 100 → 回复 ACK=200
- 收到 200 → 回复 ACK=300
- 收到 400(乱序)→ 回复 ACK=300(重复)
- 后续收到任何包 → 只要 300 没收全,就一直回复 ACK=300
发送方收到 3 个 ACK=300 后,就知道 Seq=300 丢了,立即重传。
但问题来了:发送方重传 300 之后,要不要重传 400?接收方其实已经收到 400 了。如果发送方不知道,就会浪费带宽。
4.2.2 SACK:选择性确认
SACK(Selective Acknowledgment)就是解决这个问题的。它在 TCP 选项字段里,告诉发送方:「我收到了哪些不连续的数据块」。
还是上面的例子。如果启用了 SACK,接收方回复的 ACK 会带上:
ACK=300, SACK=400-500
意思是:「我期望 300,但我已经收到了 400 到 500」。发送方一看就明白了:只重传 300 就行,400 不用管。
我的建议:现在的主流操作系统都默认开启 SACK。但有些老设备或嵌入式系统可能没开。如果你发现重传效率低,先检查 SACK 是否启用。在 Wireshark 里看 TCP 握手包,如果有「SACK permitted」选项,就说明双方都支持。
SACK 的好处很明显:
- 避免不必要的重传,节省带宽
- 减少接收方的重复处理
- 在丢包率高的场景下,吞吐量提升显著
4.3 重传对吞吐量的影响
重传是 TCP 的保底机制,但它也是吞吐量的杀手。咱们算笔账。
4.3.1 超时重传的代价
假设 RTT=10ms,RTO=200ms。一个包丢了,发送方要等 200ms 才能重传。这 200ms 里,发送方不能发新数据(因为窗口可能已经满了)。
如果丢包率是 1%,那每 100 个包就有 1 个要等 200ms。算下来,有效吞吐量会下降 20% 以上。如果丢包率到 5%,吞吐量直接腰斩。
更可怕的是指数退避。连续丢两个包,RTO 变成 400ms;连续丢三个,800ms。我见过一个极端案例:某跨国专线丢包率 3%,RTO 退避到 6.4 秒,吞吐量从 100Mbps 掉到不到 1Mbps。
4.3.2 快速重传的代价
快速重传的代价小得多。它只需要等 3 个重复 ACK 的时间。在 RTT=10ms 的网络里,这大概就是 10-20ms。
但快速重传也有代价:它会触发拥塞控制。TCP 会把拥塞窗口减半,然后进入拥塞避免阶段。这意味着即使重传很快,吞吐量也会下降。
举个例子:窗口是 100 个包,丢一个包后窗口减到 50。然后每收到一个 ACK,窗口增加 1/50。要恢复到 100,需要 50 个 RTT。这期间吞吐量只有原来的一半。
核心结论:重传对吞吐量的影响,不光是重传本身消耗的带宽,更是它触发的拥塞控制导致的窗口收缩。窗口越小,吞吐量越低。所以减少丢包,比优化重传策略更重要。
4.3.3 如何用 Wireshark 分析重传
抓包时,Wireshark 会用颜色标记重传包:
- 黑色:超时重传
- 浅绿:快速重传
- 红色:重复 ACK
我一般先看「Expert Info」里的重传统计。如果重传率超过 0.1%,就要警惕了。超过 1%,基本可以断定网络有问题。
具体步骤:
- 打开抓包文件,点 Statistics → TCP Stream Graph → Time-Sequence Graph
- 看图上有没有「阶梯状」下降。如果有,说明发生了重传和窗口收缩
- 点 Statistics → TCP Stream Graph → Throughput Graph,看吞吐量曲线
- 对比重传发生的时间点和吞吐量下降的时间点
我曾经帮一个客户排查视频卡顿问题。抓包一看,每 3 秒就有一次超时重传,RTO 从 200ms 退避到 1.6 秒。吞吐量从 50Mbps 掉到 5Mbps。最后发现是中间路由器的 buffer 太小,突发流量导致丢包。换了路由器后,问题解决。
4.4 本章知识体系
下面这张图总结了 TCP 重传的核心逻辑:
4.5 避坑指南
最后分享几个我踩过的坑:
- 别把乱序当丢包:有时候 Wireshark 显示重复 ACK,但不一定是丢包。可能是多路径传输导致的乱序。先看 RTT 和重传时间戳,确认是否真的超时。
- SACK 不是万能的:SACK 能减少不必要的重传,但不能避免拥塞控制。丢包后窗口该减半还是减半。
- 小心 RTO 最小值:有些系统把 RTO 最小值设成 200ms。在低延迟网络(比如 1ms)里,这个值太大了。可以尝试调低,但别低于 100ms,否则可能引发虚假重传。
- 我曾经遇到过一个问题:某应用频繁超时重传,但网络延迟只有 5ms。抓包发现,是服务器端的 TCP 参数没调,RTO 最小值还是 200ms。改成 100ms 后,重传率降了 60%。
嗯,重传机制就讲到这里。你理解了这些,再看 Wireshark 里的重传包,就能读出背后的故事了。