TCP重传机制详解:快速重传、超时重传与SACK选择确认

说到TCP重传,很多人的第一反应就是“丢包了,重新发一次”。但实际工作中,重传远没有这么简单。我见过不少运维同事,一看到Wireshark里出现重传就慌了,觉得网络肯定有问题。其实不然——重传是TCP的保底机制,关键是要看懂它为什么重传、怎么重传的。

这一节,我就带你把这三种重传机制彻底搞明白。你以后抓包看到重传标志,心里就有数了。

3.1 超时重传:最原始、最慢的保底方案

超时重传,说白了就是“等太久,不等了”。发送方发了一个数据包,然后启动一个定时器。如果定时器超时了还没收到ACK,那就重新发一次。

这个定时器的时长不是固定的。TCP会根据RTT(往返时间)动态计算RTO(重传超时时间)。我刚开始做网络优化时,以为RTO就是个固定值,后来才发现它一直在变。

核心公式(简化版):

RTO = SRTT + 4 × RTTVAR

其中SRTT是平滑后的RTT,RTTVAR是RTT的偏差值。说白了,RTO既要考虑平均延迟,也要考虑延迟抖动。

超时重传有个大问题——慢。一次超时至少要等几百毫秒,甚至几秒。而且一旦超时,TCP会进入“慢启动”状态,把拥塞窗口砍到1个MSS,传输效率瞬间暴跌。

我曾经踩过的坑:有一次排查一个跨国业务的延迟问题,发现每次超时重传后,吞吐量直接掉到原来的十分之一。后来加了SACK和优化RTO参数,才把性能拉回来。如果你看到大量超时重传,别只盯着丢包率,还要看RTO配置是否合理。

3.2 快速重传:更聪明的“不等超时”方案

快速重传的思路很简单:发送方收到3个重复的ACK(Dup ACK),就认为某个包丢了,不等超时直接重传。

为什么会这样?你想想看,接收方收到乱序包时,会重复确认最后一个连续收到的序号。比如接收方一直没收到包#3,但收到了#4、#5、#6,那它就会连续发3次ACK说“我还在等#3”。发送方一看,连续3次都在说同一个序号,那大概率是丢了。

我的个人习惯:在Wireshark里,我一般直接看“TCP Analysis Flags”列。如果出现“TCP Dup ACK”和“TCP Fast Retransmission”,基本就是快速重传的典型特征。我会先看重复ACK的次数,少于3次的一般不算快速重传。

快速重传比超时重传快得多,因为它不需要等RTO。但它也有局限——它只能告诉发送方“哪个包丢了”,但没法告诉发送方“哪些包已经收到了”。这就引出了SACK。

3.3 SACK选择确认:精准定位,避免无效重传

SACK(Selective Acknowledgment)是TCP的一个可选扩展。它让接收方可以告诉发送方:“我收到了哪些不连续的包”。

没有SACK的时候,发送方只能靠“累计确认”来推断。比如收到ACK=100,就知道1-99都收到了。但如果中间丢了包#50,发送方收到ACK=100后,只能猜测#50之后的所有包可能都丢了,然后一股脑全重传——这里面很多包其实已经收到了,白白浪费带宽。

SACK解决了这个问题。接收方在ACK里附加一个SACK选项,列出已经收到的乱序包范围。发送方一看,就知道只需要重传真正丢失的那几个包。

SACK选项格式(Wireshark中可见):

TCP Option - SACK:
  Left Edge = 1001
  Right Edge = 2000
  Left Edge = 3001
  Right Edge = 4000

这表示接收方已经收到了1001-2000和3001-4000的数据,但2001-3000还没收到。

我在项目中遇到过好几次,SACK开启后,重传量直接减少30%以上。尤其是在高延迟、高丢包率的链路上,效果特别明显。

注意:SACK需要通信双方都支持。如果有一方不支持,SACK选项就不会生效。你可以在Wireshark的TCP握手包(SYN包)里看到“SACK Permitted”选项,确认双方是否都开启了SACK。

3.4 Wireshark中如何识别这三种重传

Wireshark对重传的标记非常清晰。我一般直接看“Info”列,或者用“Analysis Flags”过滤。

重传类型 Wireshark标记 触发条件 典型特征
超时重传 TCP Retransmission RTO超时未收到ACK 重传间隔较长(通常>200ms),且后续可能伴随慢启动
快速重传 TCP Fast Retransmission 收到3个重复ACK 重传前有3个或以上Dup ACK,重传间隔很短
SACK重传 TCP Retransmission(配合SACK选项) SACK信息指示特定包丢失 重传包在SACK选项中标记为缺失范围

我的过滤技巧:

  • 只看所有重传:tcp.analysis.retransmission
  • 只看快速重传:tcp.analysis.fast_retransmission
  • 只看超时重传:tcp.analysis.retransmission and !tcp.analysis.fast_retransmission
  • 查看SACK选项:tcp.options.sack

3.5 三种重传机制的对比与选择

这三种机制不是互斥的,而是互补的。实际传输中,TCP会优先用快速重传,如果快速重传没触发,再等超时重传兜底。SACK则是在重传时提供更精确的信息。

TCP重传机制决策流程 发送数据包 等待ACK 收到ACK了吗? 是 正常传输 继续发送下一包 否 收到3个Dup ACK? 是 快速重传 立即重传丢失包 否 超时重传 等待RTO超时后重传 SACK在快速重传和超时重传中均可提供精确的丢失范围信息,减少无效重传

从上图可以看出,TCP的重传决策是一个分层的过程。快速重传是“快车道”,超时重传是“慢车道”,SACK则是“导航系统”。

实际工作中的选择建议:

  • 内网低延迟环境:快速重传基本够用,SACK收益不大
  • 公网高延迟环境:强烈建议开启SACK,能显著减少无效重传
  • 无线/移动网络:超时重传可能频繁触发,建议优化RTO参数
  • 数据中心内部:快速重传+关闭SACK(减少CPU开销)是常见做法

3.6 实战案例:一次典型的快速重传分析

我记得有一次帮客户排查一个视频流卡顿的问题。抓包一看,Wireshark里全是“TCP Fast Retransmission”。

具体是这样的:

  • 包#100:发送Seq=1000,Len=1460
  • 包#101:发送Seq=2460,Len=1460
  • 包#102:发送Seq=3920,Len=1460
  • 包#103:发送Seq=5380,Len=1460
  • 包#104:接收方发ACK=1000(Dup ACK #1)
  • 包#105:接收方发ACK=1000(Dup ACK #2)
  • 包#106:接收方发ACK=1000(Dup ACK #3)
  • 包#107:发送方重传Seq=1000(Fast Retransmission)

看到这个序列,我基本确定是包#100丢了。接收方收到了#101、#102、#103,但一直没等到#100,所以连续发了3次Dup ACK。发送方收到第3个Dup ACK后,立即重传了#100。

我的排查思路:遇到快速重传,我一般先看Dup ACK的数量。如果只有1-2个,可能是网络抖动;如果连续出现3个以上,基本可以确认有丢包。然后我会看丢包率,如果超过1%,就需要进一步排查链路质量了。

3.7 避坑指南:常见误解与正确做法

我曾经犯过的错:

  • 误解1:看到重传就觉得是网络问题。其实重传也可能是发送方缓冲区溢出、接收方处理不过来导致的。
  • 误解2:以为SACK一定能提升性能。在低丢包率环境下,SACK的CPU开销可能超过收益。
  • 误解3:忽略RTO的配置。默认RTO在某些场景下可能过大或过小,需要根据实际RTT调整。

嗯,到这里,TCP重传的三种机制你应该已经清楚了。超时重传是保底,快速重传是效率,SACK是精准。Wireshark里怎么标记、怎么过滤,你也都看到了。下次抓包再看到重传,别慌,先看类型,再分析原因。