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 就越大。

注意:RTO 最小值通常是 200ms(有些系统是 100ms)。这意味着即使网络延迟只有 1ms,丢包后至少也要等 200ms 才能触发重传。这 200ms 对吞吐量是致命的。

超时重传还有个坏毛病:指数退避。第一次超时等 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%,基本可以断定网络有问题。

具体步骤:

  1. 打开抓包文件,点 Statistics → TCP Stream Graph → Time-Sequence Graph
  2. 看图上有没有「阶梯状」下降。如果有,说明发生了重传和窗口收缩
  3. 点 Statistics → TCP Stream Graph → Throughput Graph,看吞吐量曲线
  4. 对比重传发生的时间点和吞吐量下降的时间点

我曾经帮一个客户排查视频卡顿问题。抓包一看,每 3 秒就有一次超时重传,RTO 从 200ms 退避到 1.6 秒。吞吐量从 50Mbps 掉到 5Mbps。最后发现是中间路由器的 buffer 太小,突发流量导致丢包。换了路由器后,问题解决。

4.4 本章知识体系

下面这张图总结了 TCP 重传的核心逻辑:

TCP 重传机制知识体系 TCP 重传机制核心逻辑 数据包丢失 超时重传 (RTO) 快速重传 触发条件:RTO 定时器到期 后果:指数退避,窗口收缩 触发条件:3 个重复 ACK 辅助机制:SACK 选择性确认 对吞吐量的影响 带宽浪费(重传包) 窗口减半(拥塞控制) RTO 退避(空闲等待) Wireshark 分析重点:Expert Info → 重传率 > 0.1% 需警惕

4.5 避坑指南

最后分享几个我踩过的坑:

  • 别把乱序当丢包:有时候 Wireshark 显示重复 ACK,但不一定是丢包。可能是多路径传输导致的乱序。先看 RTT 和重传时间戳,确认是否真的超时。
  • SACK 不是万能的:SACK 能减少不必要的重传,但不能避免拥塞控制。丢包后窗口该减半还是减半。
  • 小心 RTO 最小值:有些系统把 RTO 最小值设成 200ms。在低延迟网络(比如 1ms)里,这个值太大了。可以尝试调低,但别低于 100ms,否则可能引发虚假重传。
  • 我曾经遇到过一个问题:某应用频繁超时重传,但网络延迟只有 5ms。抓包发现,是服务器端的 TCP 参数没调,RTO 最小值还是 200ms。改成 100ms 后,重传率降了 60%。

嗯,重传机制就讲到这里。你理解了这些,再看 Wireshark 里的重传包,就能读出背后的故事了。