先讲一个我自己的排障经历,可能不少搞网络的人都有同感。前两年帮客户排查一个内网大文件传输慢的问题,应用层写的是TCP,两端都是千兆网卡,按理说应该能跑到八九百兆,但实际只有两三百兆。抓包一看,吓一跳,几十秒的流量里有一大片Dup ACK和TCP Retransmission。再仔细数一数,发送窗口里的数据段丢了三四个,接收端反复说“我缺某一段”,发送端却只能一段一段地重传,等完这个等那个,整个传输就像堵车一样走不动。那一刻我意识到,理解TCP的ARQ自动重传机制,真的不是背几个概念那么简单。
这也就是我今天想和你聊透的话题。ARQ,全称Automatic Repeat reQuest,自动重传请求,是TCP保证可靠传输的核心手段。TCP在IP这个“尽力而为”的网络上跑,丢包、乱序、重复都是家常便饭,所以必须靠确认和重传来兜底。而这套兜底机制里,实际可以拆成三种层层递进的实现方式:超时重传、快速重传,以及选择性确认加选择性重传,也就是SACK。本文会把这三种方式的工作原理、参数计算、优缺点和抓包表现全部掰开揉碎,最后附上我踩过的坑和排查命令。
不管你是刚学TCP传输控制协议的新人,还是经常需要在现场分析Modbus TCP、S7通信、通用socket程序慢或卡问题的工程师,这篇文章应该都能给你一些直接可用的参考。
1. 重传机制为什么是TCP的“保命符”
1.1 可靠传输的最后一公里是“重传”
TCP要解决的核心问题,是IP层只提供“尽力而为”的传输,也就是说数据包从A到B的路上可能被路由器丢弃、可能因为网络拥塞被主动甩掉、也可能在队列里被超时清空。正常网络环境下丢包率可能不到千分之一,但在弱网、Wi-Fi、跨运营商线路、或者现场电磁干扰严重的工业环境里,丢包率飙到百分之一甚至更高都很常见。
既然底层会丢,TCP就必须自己做两件事:第一,让接收方告诉发送方“我收到了哪些数据”,这就是确认(ACK);第二,让发送方知道“哪些数据对方没收到”,然后把这些数据再发一遍,这就是重传。确认加重传,合在一起就是ARQ。
具体到TCP的实现,确认机制用的是“累计确认”,意思是接收方回复的ACK序号不是某一段的编号,而是“我下一个期望收到的字节序号”。举个例子,接收方收到了字节1000到1999,它就会回复ACK 2000,表示“2000之前的字节我都收到了,你接着发2000”。如果接下来又收到了2000到2999,就会回复ACK 3000。这个累计机制有个副作用,如果中间丢了一段,后面的数据先到了,接收方也只能上报“我最前面的缺口在哪”,而不能完整表达“哪些我先收到了”,于是就有了后续的各种扩展机制,我们后面慢慢讲。
1.2 教科书的三种ARQ模型,TCP实际上走了第三条路
大学计算机网络课里讲的ARQ一般有三种理想模型:停等ARQ、回退N帧ARQ(Go-Back-N)、选择性重传ARQ(Selective Repeat)。停等协议是发一个等一个确认,效率太低;回退N帧是发现丢包后把后面已经发出去的数据全部重发;选择性重传则只重发真正丢失的那几段,效率最高。
但TCP并没有像教科书里那样直接实现某一种。它的实际路线是:以超时重传为最终兜底,用快速重传把丢包检测提前,再用SACK实现“近似选择性重传”。你可以这么理解:超时重传是“不知道对方收没收到,等得太久就全量补发”,快速重传是“听到对方连续喊了好几遍没收到,就赶紧补发最早缺的那一段”,SACK则是“对方直接画了一张地图,告诉你哪几块收到了、哪几块没收到,你按图索骥精确补发”。
所以下面三章,我们就按TCP实际的演进路径,把超时重传、快速重传、SACK这三种实现方式逐个拆开。
2. 超时重传:最朴素但最耗时的兜底方案
2.1 工作原理与定时器:到期没确认就重发
超时重传是TCP最古老、也最基础的重传方式,从1981年的RFC 793就有。它的逻辑非常简单:发送端发出一个数据段后,启动一个重传定时器,如果在定时器到期之前没有收到对这个段的有效确认,就认为这个段丢了或者确认丢了,于是重新发送一遍。如果重传后还是没有收到确认,就再重传,而且每次重传的等待时间都会翻倍,这就是所谓的指数退避。
定时器的时间间隔,就叫RTO(Retransmission Timeout,重传超时时间)。RTO不能是一个固定值,因为网络状况是动态变化的,所以TCP要不断测量RTT(Round-Trip Time,往返时间),再根据RTT的波动情况动态调整RTO。
在内网通畅时RTT可能只有0.2毫秒,在跨省公网上RTT可能是30毫秒,在卫星链路上RTT甚至可能到500毫秒以上。如果RTO设得太小,数据还在路上就超时了,结果造成大量虚假重传;如果设得太大,真的丢包了又迟迟发现不了,吞吐量会严重下降。
2.2 RTT估计和RTO计算:一个公式看懂它
RTO的计算在1988年的Jacobson算法里定型,后来经过RFC 2988和RFC 6298的微调,核心思想是使用指数加权移动平均(EWMA)来平滑RTT波动。公式是这样的:
SRTT = (1 - α) × SRTT + α × RTT // 平滑后的RTT估计值 RTTVAR = (1 - β) × RTTVAR + β × |RTT - SRTT| // RTT的方差估计 RTO = SRTT + max(G, 4 × RTTVAR) // 最终的超时时间其中α取1/8,β取1/4,G是定时器的时钟粒度。可以看到,RTO并不是简单等于“平均RTT”,而是在平均RTT基础上加上4倍的抖动偏差。这样做是为了自适配:网络越稳定,RTT方差越小,RTO越接近SRTT,丢包后能快速发现;网络越抖动,RTO越要放大,否则容易误判超时。
另外还有一个特别重要的规则,叫Karn算法:如果某个段发生了重传,那么收到这个段的ACK时,不能用这次RTT样本去更新SRTT和RTTVAR。原因是这个ACK可能是针对第一次发送的段回的,也可能是针对重传的段回的,你无法区分,贸然用来更新RTT估计会让RTO算得不准。
标准文档里RTO的最小值是1秒,但Linux内核在实际实现中允许调低,比如在低时延数据中心网络里可以把tcp_rto_min设置到200毫秒甚至更低。要注意,超时重传时的等待代价是非常高的,尤其在RTO已经因为连续超时翻倍到好几秒的情况下,整个连接可能陷入“假死”状态,明明链路恢复了,TCP也得等好一阵子才能反应过来。
2.3 超时重传的代价:带宽空转与指数退避
超时重传最大的问题,就是它“发现丢包”非常迟钝。设想一个场景,RTT是10毫秒,RTO算出来是200毫秒,现在第5个数据段丢了。发送端会一直发送后续数据,直到窗口用完,然后停下来干等。接收端因为没有第5个段,后续就算收到了第6、7、8段,也只能反复回复“我期望第5段”,但发送端此时并不能立刻确定第5段丢了,一定要等到RTO超时那一刻才反应过来。
这段时间里,发送窗口是停摆的,网络带宽在空转,吞吐量直接掉一个数量级。更要命的是指数退避,第一次超时等200毫秒,重传后再超时等400毫秒,再超时等800毫秒,如果链路持续不稳定,几次之后RTO就膨胀到好几秒,用户体验就是“传输卡死了,过几秒又动一下”。
我在实际抓包里见过不少类似案例,一个明明只有几百MB的文件,因为网络丢包率偏高,RTO膨胀到4秒以上,传输时间从几秒拉长到十几分钟。这种场景如果只靠超时重传,是完全不能接受的。所以TCP必须有一种更快速发现丢包的机制,这就是快速重传。
3. 快速重传:用重复ACK把“丢包通知”提前
3.1 三个重复ACK:乱序和丢包的博弈平衡
快速重传的出发点是:与其等RTO超时,不如利用接收端回复的重复ACK来判断丢包。TCP里有个规律,接收端每收到一个“不是自己当前期望序号”的数据段,就会回复一个包含“当前期望序号”的ACK。也就是说,如果发送端一直收到重复的ACK,就说明接收端已经收到了某个缺口之后的数据,而这个缺口很可能就是丢了。
但事情没有这么简单。在网络中,数据段乱序到达是一种很常见的现象,前面的段还没到,后面的段先到了,接收端同样会回复重复ACK。如果发送端一看到重复ACK就立刻重传,就可能把一个只是“稍微迟到”的数据段误判为丢失,造成大量无谓重传,反而加剧网络拥塞。
所以TCP采用了一个阈值:当发送端连续收到3个重复ACK(也就是除了第一个原始ACK之外,再收到3个相同的ACK,一共连续4个相同ACK)时,才认为某个段确实丢了,触发快速重传。这个“3”是经过实践验证的平衡点:正常情况下网络乱序深度很少超过3个段,超过3个重复ACK仍然等不到那个缺失的段,那么它大概率是丢了。
3.2 运作细节:它只在“最老”的缺口上补刀
快速重传触发时,发送端会立即重传当前窗口内序号最小的那个未确认段,也就是“最老的缺口”。同时,这套机制和拥塞控制是联动的,进入快速恢复(Fast Recovery)流程,把拥塞窗口减半而不是降到1,这样丢包后吞吐量不会断崖式下跌。
快速重传最大的价值在于,它把丢包发现时间从“一个RTO”缩短到“约等于一个RTT”。假设RTT是20毫秒,RTO是200毫秒,不用快速重传时发现丢包要200毫秒,用了之后只要大概20到40毫秒就能重传。这个时间差在高速长肥网络里非常可观,直接决定吞吐量是几百兆还是几十兆。
举一个具体数字。发送端连续发出序号1000、2000、3000、4000、5000五个段,其中2000这段丢了。接收端会先确认段1000,回复ACK 2000。接着段3000到了,接收端还是回复ACK 2000。段4000到了,仍然回复ACK 2000。段5000到了,依然是ACK 2000。发送端在收到第3个重复ACK 2000时,就能断定段2000丢了,立刻补发。这个流程在抓包里看得非常清楚,你会看到一大堆“dup ack 2000”,然后跟着一个“Retransmission for segment 2000”。
3.3 两个致命短板:每次精确处理一个丢包
但快速重传并不是万能的。它有两个很明显的短板。
第一个短板是,快速重传一次只能精确处理一个丢失段。当一个发送窗口内同时丢了多个段时,比如上面的例子里段2000和段4000都丢了,快速重传只会补发段2000。段4000呢?它要等到重传的段2000到达接收端后,接收端才有可能把ACK推进到3000,然后发送端才能从后续的重复ACK里发现“段4000也丢了”,再触发一次快重传。也就是说,多个丢包场景下,快速重传需要多轮RTT才能把所有缺口补完,严重时吞吐量依然惨不忍睹。
第二个短板是,快速重传本身并不能让发送端得知“缺口之外还有哪些数据已经安全到达”。接收端能表达的信息只有“我最期待的序号是哪个”,至于后面那些乱序到达的数据块,它收了没、收了哪些,发送端一概不知。于是发送端就只能靠猜,猜测错误就会导致不必要的重传,浪费带宽。
这也引出了咱们的重头戏,SACK。SACK的出现,就是为了补上“接收端到底收到了哪些数据”这块信息拼图。
4. SACK:让对端把“我已收到的”画成一张地图
4.1 快重传的局限,就是SACK的出发点
SACK的全称是Selective Acknowledgment,选择性确认。它的思路很直接:接收端在ACK里额外携带一个选项,告诉发送端“我已经收到的所有乱序数据块的边界”,发送端收到后,就能精确知道自己哪些段还需要重传,哪些段已经在接收方手里,不用再靠猜。
在SACK出现之前,TCP能表达的信息确实太少。接收端面对一个乱序到达的包群,只能反复重复“我最期望的是X号段”,至于X之后的那些段它其实已经收到了,但因为累计确认机制的限制表达不出来。发送端不知道这个信息,就会做出两种错误行为:要么把已经到达的数据又发一遍,浪费带宽;要么漏掉某个丢失的段,一直等它超时。SACK就是来解决这两类问题的。
4.2 握手协商:SACK Permitted在三次握手里敲定
SACK不是一个默认开启就能用的东西,它需要在TCP三次握手阶段进行能力协商。协商的原理是:发起方在SYN报文里带上SACK Permitted选项;如果接收方也支持SACK,就在SYN+ACK报文里也带上SACK Permitted选项;第三次握手的ACK报文里是否携带则没有严格要求。
注意,SACK的支持是“双向独立”的。也就是说,A到B这个方向能不能用SACK,和B到A这个方向能不能用,取决于握手时双方各自声明的情况。如果有一方不支持,那么这条TCP连接里SACK就不会启用。
我在现场排障时经常遇到一种情况:客户端和服务器都是现代操作系统,看起来都支持SACK,但抓包发现双方成交后完全没有SACK块。后来仔细一查,发现是中间链路上的某种设备或者某些安全策略把TCP选项给剥掉了。这里提醒大家,如果确认通信双方都开启了SACK,但抓包看不到SACK,那就要往中间网络设备上去排查了。
4.3 SACK块:用左右边界画出一张已收地图
SACK选项在TCP头里的格式,Kind值为5,长度字段可变。每个SACK块由两个32位的序号组成:左边界和右边界。左边界表示这个已收数据块的第一个字节序号,右边界表示这个数据块最后一个字节序号加1,使用的是左闭右开区间。
一个SACK选项最多可以携带4个SACK块,这是因为TCP头选项区域总共只有40字节,除去2字节的选项头和可能存在的其他选项(比如时间戳),4个块已经是一个比较优化的上限。如果多个块同时存在,它们的排列顺序也有讲究:第一个块是接收端最近收到的新数据块,后面的块则按左边界升序排列。这个排序规则对发送端判断“是否有新数据到达”很有用。
用抓包示例来解释,假设接收端已经收到序号1000到1999,然后收到了3000到3999、4000到4999、5000到5999三段,由于2000到2999这一段缺失,它回复的报文大概是这样的:
ACK 2000 <nop,nop,sack 1 {3000:6000}>这个SACK块的意思是“我虽然下一个期望的字节是2000,但3000到5999这些字节我已经收好了”。等一下,三个块为什么只显示一个SACK块?因为3000到3999、4000到4999、5000到5999在序号上是连续的,接收方在整理时会把它们合并成一块,报告为3000:6000。只有真正不连续的多个洞,才会被报告成多个SACK块。
如果又有6000到6999到达,那么接收方就可以报告两组块,比如:
ACK 2000 <nop,nop,sack 1 {3000:6000} {7000:8000}>当然,这是比较理想的情况。实际抓包里你会看到SACK块常常伴随着时间戳选项一起出现,tcpdump会显示成“nop,nop”这种东西,那些是为了字节对齐填充的无操作选项,不影响语义。
4.4 Scoreboard:从通知机制到选择性重传的落地
接收端提供了SACK信息,发送端怎么用起来?这里的关键概念是Scoreboard,我习惯叫它“重传记账本”。发送端在内部维护每个数据段的发送状态:已确认、已SACK、未确认未SACK、已重传。收到一个SACK块之后,发送端就把对应序号范围的数据段标记为“已SACK”,意思是“对端说收到这块了,不用再发”。
当需要重传时,发送端遍历记账本,只重传那些既没有收到ACK、也没有被SACK标记的段。这样一来,多丢几个包也没关系,只要SACK信息足够,发送端就可以在一个RTT内同时补发所有缺失的段,真正实现了教科书意义上的“选择性重传”。
还要提一个经常被忽视的扩展机制:DSACK,定义在RFC 2883。它的作用是让接收端告诉发送端:“你重传的这段数据,我其实之前已经收到过了。”DSACK能帮助发送端发现虚假重传、网络重复包、以及某些异常路径问题,对链路的“自我认知”很有价值。
4.5 手把手拆一个抓包案例
下面用一个实际场景把SACK的价值串起来。假设发送端窗口里有5个数据段,序号分别是1000、2000、3000、4000、5000,每段1000字节。现在第2段(2000)和第4段(4000)都丢了。
先看超时重传。发送端发完5个段后,接收端会收到1000、3000、5000,于是连续回复ACK 2000,并附上SACK信息(如果启用)。但发送端在不启用SACK的情况下,只能按快重传逻辑,收到3个重复ACK后重传2000。重传第2段到达后,接收端继续回复ACK 3000,附上SACK块,表明5000甚至后面更多段已经收到。这时候发送端才能知道第4段也丢了,再等一轮重复ACK触发第二次重传。整个流程至少需要2到3个RTT,而且一旦某个重传段再丢失,成本还要翻倍。
再看启用了SACK的情况。接收端收到1000、3000、5000后,会回复:
ACK 2000 <sack 1 {3000:4000} {5000:6000}>发送端一看这两块,立刻就知道2000和4000都丢了,于是同时重传这两段。整个恢复过程只需要1个RTT左右,效率天差地别。
我实测过一个场景:内网环境用iperf3打流,人为注入1%的随机丢包。关闭SACK时,TCP吞吐量从接近线速掉到原来的十分之一以下,抓包基本全是Retransmission和Dup ACK;开启SACK后,同样1%丢包,吞吐量虽然也下降,但能稳定保持在不丢包时的一半以上。别小看这个差距,在长肥网络或者弱网环境里,SACK开启与否常常就是“能不能用”和“完全不可用”的区别。
5. 三种实现方式的横向对比与选型建议
5.1 一张表看透三种实现
三种重传方式不是替代关系,而是同时存在、互相配合的关系。为了让你一眼看清差异,我做了一个对比表:
| 对比维度 | 超时重传 | 快速重传 | SACK重传 |
|---|---|---|---|
| 触发信号 | RTO定时器到期 | 收到3个重复ACK | 握手协商后,通过ACK中的SACK块反馈 |
| 发现丢包速度 | 慢,通常数百毫秒到数秒 | 较快,约等于1个RTT | 较快,约等于1个RTT |
| 反馈信息粒度 | 只有“没收到ACK” | 知道“最老的缺口在哪” | 知道“所有缺口和已收块” |
| 一次能重传几个缺口 | 理论上一个段,重传完再等 | 通常一个段,多处丢包时要多轮 | 多个缺口,一个RTT内批量补发 |
| 额外开销 | 无选项开销 | 无选项开销 | 占用TCP选项空间,最多4个块 |
| 误判风险 | 不会误判,但反应太慢 | 乱序深度大时可能误判 | 能修正大部分误判,配合DSACK更好 |
| 适用场景 | 任何TCP连接的兜底 | 单点丢包为主的网络 | 多丢包、高带宽时延积网络 |
表格看下来就很清楚,超时重传是永远不能关掉的最后一道保险,快速重传是把丢包发现时间从RTO压缩到RTT的提速器,SACK则是精细化重传的放大器。三者层层递进,协同工作。
5.2 内核参数怎么调,以及一个容易混淆的“busy”场景
在Linux服务器上,这几个机制基本都是默认开启的,对应的内核参数如下:
| 参数 | 作用 | 默认值 |
|---|---|---|
| net.ipv4.tcp_sack | 是否启用SACK | 1(开启) |
| net.ipv4.tcp_dsack | 是否启用DSACK | 1(开启) |
| net.ipv4.tcp_fack | 是否启用FACK重传 | 1(开启) |
| net.ipv4.tcp_retries1 | 决定放弃前尝试重传的次数 | 3 |
| net.ipv4.tcp_retries2 | 决定超时前尝试重传的次数 | 15 |
我的建议很简单:不到万不得已,不要关闭SACK。我在不少生产环境里看到有人因为某个“优化指南”把tcp_sack改成0,结果多丢包场景下吞吐直接崩溃。SACK是经过几十年验证的成熟机制,现代TCP协议栈对它的管理已经非常精细。
最后再纠正一个容易踩坑的认知。有时候你在现场遇到类似“博图S7-1500通过TSEND_C发送TCP数据太慢,一直返回busy”这种问题,本能反应会怀疑是不是重传机制有问题,抓了一堆包发现网络很干净,没有丢包也没有重传。这种情况大概率不是ARQ的问题,而是发送缓冲区或者对端接收窗口满了,TSEND_C在等待缓冲区释放,所以一直busy。排查顺序一定不要乱:先看对端窗口是否为零窗口,再看发送缓冲区大小,最后才轮到检查重传统计。丢包导致的重传和流控导致的busy是两回事,混在一起查会绕很多弯路。
6. 排查实录:抓包里的Dup ACK和SACK怎么判
6.1 重复ACK不一定是丢包,先判断乱序
这是很多新手最容易翻车的地方。抓包看到连续几个Dup ACK,就断定“有丢包了”,其实不一定。乱序到达同样会触发重复ACK。判断方法有两个参考维度。
第一,看乱序的幅度。如果缺失的那个段的序号很快就到了,比如只落后两三个段,那多半是网络乱序,不是真的丢包。第二,看SACK块的演变。如果后续SACK块里那个“缺口”很快被填充,并且ACK序号很快往前推进,说明只是乱序;如果ACK序号在某个位置停住了,SACK块里的边界不断增加,但那个缺口始终没有被填上,那才是真的丢了。
简单说,重传与否不能只看Dup ACK的数量,还要结合时间窗口和后续ACK推进情况综合判断。
6.2 快重传没触发、SACK没生效的常见原因
快重传没有触发,最常见的原因是重复ACK数量不够。默认阈值是3,如果你抓包只看到1到2个重复ACK,然后发送端就超时了,那说明重复ACK数量不足以触发快重传。这经常发生在窗口比较小、在途数据本来就少的场景下。发送端等不到足够的Dup ACK,就只能靠超时。
SACK没生效的原因就要多排查几个环节了:一是TCP握手时没有协商SACK Permitted,双方或中间设备把它屏蔽了;二是传输过程中某些网络设备修改或剥离了TCP选项;三是应用本身用了原始socket且没有启用SACK扩展,比如某些嵌入式协议栈默认关闭;四是本地或对端内核参数被改过,把tcp_sack设成了0。排查时可以先用两端抓包确认握手包,再逐段核对中间设备,不要一上来就怀疑内核。
6.3 排查命令与统计解读
实际排查时,我用的最多的命令是tcpdump和netstat。抓包时建议加几个关键过滤条件:
# 抓指定端口的TCP流量,只看重传和重复ACK sudo tcpdump -i eth0 -nn tcp port 8080 -w /tmp/tcp.pcap # 打开pcap后过滤显示重传 tcpdump -nn -r /tmp/tcp.pcap 'tcp[tcpflags] & (tcp-tcpflags) == tcp-syn or tcp[tcpflags] & (tcp-tcpflags) == tcp-ack'更推荐的做法是抓完包后用Wireshark打开,直接看Expert Info里的“Retransmission”“Duplicate ACK”“Out-Of-Order”这些提示。不要只看颜色标记,要展开TCP头里的SACK选项,看SACK块的左右边界和ACK序号之间的关系。
查看当前连接的重传统计,可以用netstat:
netstat -s | grep -E "retrans|sack"这个命令会显示系统级别的重传次数、SACK恢复次数等。如果sack recovery count长期为0,而retrans长尾又特别多,就说明SACK实际没有起到作用,值得深入分析。
6.4 用tc netem做一次可控的丢包实验
如果你想在本地环境验证三种重传方式的差异,强烈推荐用tc的netem模块做可控丢包实验。这个工具在Linux上可以直接用,不需要专门搭一台丢包器。
比如,我想给eth0网卡注入随机1%的丢包率:
sudo tc qdisc add dev eth0 root netem loss 1%实验做完后记得删掉,否则会影响正常网络:
sudo tc qdisc del dev eth0 root接下来用iperf3或scp在两个主机间传输大文件,分别执行“关闭SACK”和“开启SACK”两组对比,抓包对比重传次数和吞吐量。关闭SACK的命令是:
sudo sysctl -w net.ipv4.tcp_sack=0跑完再开启:
sudo sysctl -w net.ipv4.tcp_sack=1我做过一组数据:不丢包时吞吐接近1Gbps,注入1%丢包且关闭SACK后吞吐掉到80Mbps左右,重传包占整个流量的比例高得吓人;开启SACK后,同样1%丢包,吞吐能回到450Mbps以上,重传包比例也大幅下降。自己做一遍这个实验,对三种重传机制的理解会扎实很多。
细心的朋友可能会问,netem只模拟了随机丢包,没模拟RTT和带宽,确实是这样。但作为机制验证,它已经足以说明SACK的价值。如果要更贴近真实弱网,可以再加上延迟和抖动参数,比如:
sudo tc qdisc add dev eth0 root netem loss 1% delay 50ms 10ms distribution normal这样就把“丢包+延迟”两个因素同时注入,测出来的效果更接近公网传输的实际情况。
说了这么多,最后分享一个我自己的体会。每次看到抓包里有大面积的Dup ACK,我都会先压住“这个网络很烂”的判断,把SACK块一个个展开看。SACK块就是接收端写给发送端的情报,它清清楚楚地告诉你哪些字节已经安全落袋,哪些字节还有一个洞没补上。多盯几次,你对TCP重传机制的理解就会从“知道概念”变成“能预判行为”。如果手头有条件,强烈建议自己开两个虚拟机,用tc netem注入丢包,亲手把三种重传方式的抓包对比做一遍。理解ARQ,最好的老师不是博客,是你自己抓的那份pcap。