计算机网络 17:何时重传才合理,RTT 估计、RTO、快速重传与恢复
一个已发送的数据段迟迟没有得到确认,应该再等多久?立刻重发会把正常延迟当成丢失;一直等待又无法从真正丢包中恢复。发送端看到的是确认进度和时间,不是网络内部每个队列的完整情况。
第 15 篇定义了序号与累计确认,第 16 篇区分了连接状态和应用结果。本篇讨论发送端根据什么证据决定重传。超时、重复 ACK 和现代丢失检测各有条件,不能把抓包工具显示的“重传”直接当成某个内核分支已经被证明。
没有确认不等于数据一定丢了
同一段数据迟迟没有确认,可能是数据在去程丢失,也可能是确认在回程丢失,或者任一方向发生了排队延迟。接收端还可能采用延迟确认。只在发送端观察不到 ACK,无法在这些解释之间作唯一选择。
设发送时间为零,原始段于 80 ms 到达接收端,但确认丢失;发送端在 200 ms 重发,随后收到覆盖该数据的 ACK。接收应用可能早已取得数据,而发送端直到重发后才确认完成。这是一个假设时序,用来说明观察不充分,不是本篇实测延迟。
重传也不能代替应用层幂等设计。TCP 在同一连接的字节流中处理重复数据,上层在连接失败后重新发起一次业务请求,仍可能造成重复操作。跨连接请求标识、执行结果和持久化边界属于另一层问题。
RTO 同时考虑典型往返时间和波动
RFC 6298 §2使用平滑往返时间 SRTT、变化估计 RTTVAR 和时钟粒度 G 计算重传超时 RTO。RTTVAR 的更新使用绝对偏差,并不是把统计学的方差直接带入公式。
采用规范建议的系数,第一份有效样本 R 建立初值,后续样本 R’ 更新旧估计:
1 | |
更新次序不能交换。RTTVAR 要使用更新前的 SRTT;先平滑 SRTT 会缩小当前样本相对旧估计的偏差。RTT 上升也不保证原始 RTO 每次上升,因为平滑值和变化估计共同影响结果。
以下手算设 G=1 ms,连续两个有效样本为 100 ms 和 140 ms,没有重传样本歧义。所有数值都是题设,不是从本机 TCP 读取的内部变量。
| 样本 | 更新后 SRTT | 更新后 RTTVAR | 原始 RTO | 应用 1 s 下限后 |
|---|---|---|---|---|
| 100 ms | 100 ms | 50 ms | 300 ms | 1000 ms |
| 140 ms | 105 ms | 47.5 ms | 295 ms | 1000 ms |
第二行中,RTTVAR = 0.75×50 + 0.25×40 = 47.5。如果误用已更新的 SRTT=105,便得到 46.25 ms。精确分数校验脚本断言这两种结果不同,并检查表格全部数值。
1 | |
RFC 6298 对尚无样本时的初始 1 s,以及计算值小于 1 s 时向上取整,使用 SHOULD 要求。表格明确采用这个下限,不能据此断言所有操作系统、所有连接阶段实测都会在一秒后重传。核对具体实现时,还需要内核版本、配置与连接内部计时证据。
重传之后怎样选择 RTT 样本
如果一个序号区间在时间 t0 发出,又在 t1 重发,随后 t2 收到 ACK,往返时间究竟是 t2-t0 还是 t2-t1?仅有累计确认号通常不能消除这份歧义。
RFC 6298 §3要求采用 Karn 算法,排除重传造成歧义的 RTT 样本;时间戳选项在能识别对应发送实例时可以解除这一限制。不能见到一个 ACK 就把它与最近一次同序号发送相减,随后声称得到了发送端采用的 RTT。
这里还有测量位置差异。抓包进程取得时间戳时,可能已经晚于内核接收或发送事件;虚拟接口两端的用户态捕获也会受调度影响。它们可以提供有边界的观察间隔,不能无条件替代 TCP 内部采样时刻。
超时重传与快速重传使用不同证据
RFC 6298 §5规定计时器管理及超时后的退避。超时处理重传最早尚未确认的数据,并把 RTO 加倍;从 1 s 开始连续两次超时,退避值为 2 s、4 s。这是没有新有效测量等介入时的算例,不表示一次连接全部重传间隔必然构成等比数列。
累计确认推进、所有未确认数据得到确认、新 RTT 样本等事件会影响计时器或重新计算的 RTO。发送端不是给每个应用 sendall() 都分配一个互不相关的一秒倒计时。
RFC 5681 §3.2描述传统快速重传:三个符合条件的重复 ACK 可以在 RTO 到期之前触发对缺口的重传。这里的重复 ACK 有协议判定条件,不是抓包表里任意三行恰好显示相同确认号;数据、控制位和窗口变化也要检查。
后续数据到达而累计确认不前进,提供了缺口仍存在的证据。但乱序也能造成这种现象,所以重复 ACK 本身不是丢包的直接证明。第 15 篇已经观察到乱序与 SACK,不能再把那份记录自动改称真实丢包。
快速恢复还包括发送控制的调整,与“补发一段数据”不是同一个动作。本篇只说明触发依据和恢复边界,拥塞窗口如何限制后续发送在第 19 篇展开。
现代实现不只数三个重复 ACK
RFC 8985 §3、§6–7描述 RACK-TLP。RACK 结合较晚发送数据的交付证据、发送时间与重排序等待范围判断丢失;TLP 则通过尾部探测争取在长时间等待之前得到反馈。短流尾部缺失时,可能没有足够后续数据来产生三个重复 ACK。
因此,同一序号的再次发送可能与不同恢复机制有关。观察到“没有三个重复 ACK却发生重发”,并不能直接认定实现违反协议;反过来,看到等待了某个毫秒数,也不能只凭它接近某个经验值便认定一定是 RTO 或 TLP。
确定内核走了哪条路径,需要对应版本源码与运行时计时器、状态或跟踪事件。本篇保留抓包可见事实,不把未采集的内核事件补成确定结论。
用两个注入点区分延迟和丢包
实验使用专用 Linux 虚拟机,每组重新创建 A、B 两个命名空间,以 veth 相连。A 为 10.17.0.1,B 为 10.17.0.2:46017。两端先建立 TCP 连接,再通过父进程屏障等待注入配置完成,随后传送固定 32 字节并回显;客户端和服务端都逐字节检查实际接收内容。
1 | |
脚本需要创建命名空间和原始 socket 的权限,只操作带随机前缀的自有对象。不要在宿主或生产节点执行;完成后由 finally 清理子进程和命名空间。八秒 socket 超时及捕获上限是实验保护,不是 TCP 的 RTO 参数。
延迟组只在 A 的自建 eth0 出站安装 netem limit 100 delay 80ms,并记录 tc -s qdisc show 的读回结果。它影响该方向的排队,不能写成双向各延迟 80 ms,也不能把配置值当作精确实测 RTT。tc-netem(8)还指出计时粒度及放置位置会影响结果;本篇不使用这份短流轨迹推导吞吐性能。
丢包组在 B 的 input hook 安装专用 nft 规则,匹配 A 发往服务端端口、IP 长度大于 60 且不含 SYN/FIN/RST 的包,再用 numgen inc mod 100 == 0 选择并计数丢弃。这个表达式是周期选择,不是永久只丢一次;必须结合本次匹配数量说明实际发生了什么。长度条件也只适用于本实验的报文形态,不能当成通用的 TCP 数据判定器。nftables 手册说明了数值生成器和模运算范围。
A、B 的 AF_PACKET 捕获在建连前就开始。B 捕获到一个包,并不证明本机 TCP 已接受它:packet(7)明确接收副本在送入内核协议处理前交给 packet socket。因此 B 上被 input 规则丢弃的包仍可能出现在捕获记录中,必须把防火墙计数与后续 ACK、应用读取合起来解释。
当次结果与可确认的触发依据
本次于 2026-09-20 运行,环境为 Linux 6.18.10-200.fc43.aarch64、Python 3.14.3、iproute2 6.14.0、nftables 1.1.3。两组均完成 32 字节请求和同内容回显,收发进程核对的是实际接收字节。下表仅报告这一次有限运行。
| 观察项 | 延迟组 | 丢包组 |
|---|---|---|
| 注入读回 | A 出站 delay 80ms,dropped 0 | B input counter packets 1 bytes 84 |
| A→B 数据序号 | 1674782394 | 3973772351 |
| 每个捕获点看到该 32 字节段的次数 | A、B 各一次 | A、B 各两次 |
| A 点同序号数据观察间隔 | 无第二次观察 | 205.735622 ms |
| 客户端发送及接收回显的操作时长 | 83.313680 ms | 205.910747 ms |
延迟组在应用完成时,qdisc 统计为已发送 98 字节、一包,另有 66 字节、一包积压。统计口径包含该接口观察到的帧长度,不能与 32 字节应用内容混用;这份快照在关闭之前取得,也不能写成队列已经清空。
丢包组规则计数从零增为一,字节数 84 对应本次匹配 IP 包。两端原始帧都保留相同序号和相同 32 字节内容的两次观察,B 点间隔为 205.714080 ms。应用只执行一次数据发送,本实验没有配置报文复制,规则读回、重复序号与载荷、随后确认和实际读取共同支持“注入丢失后发生了重发并完成交付”。
本次没有捕获到支持传统“三个重复 ACK”触发的序列,也没有采集内核计时器、TCP_INFO 或恢复跟踪事件。因此可以确认注入和重发事实,不能进一步唯一指定此次由 RTO、TLP 或哪个内核恢复分支触发。规范部分给出这些机制各自需要的依据,实际记录不具备的依据不能补写。
延迟组还有一个测量反例:首个数据段的 B 点用户态采样时间比 A 点早 0.004167 ms。两个捕获进程在 recv 之后调用 monotonic_ns(),调度次序和捕获位置会影响结果;A 点也不能直接假定处于 qdisc 延迟之前。这个负差不能解释成负链路延迟,更不能用两点差值否定已读回的排队配置。应用操作时长同样含有调度影响,不是纯 TCP RTT 样本。
验证边界
原始记录包含所有捕获帧、规则读回、应用接收值及清理记录;两组均未捕获到 RST。最终四个自有命名空间已删除,虚拟机停止。最初两次执行在 nft 语法解析阶段失败,尚未发送丢包组数据;修正链结束位置的换行后,重新完整运行两组。失败经过保存在实验说明中,不作为成功输出的一部分。
这里验证的是隔离环境的短流行为,没有真实公网拥塞、长期丢包率或性能分布结论。解码器限于本次无 VLAN、未分片 IPv4/TCP,不验证校验和、完整 TCP 选项与网卡卸载。捕获时间戳是用户态取样,也没有捕获丢弃计数;“未观察到重传”仅限所保留的捕获区间。附带的 rto.py 是题设手算,不是内核 TCP 的替代实现。
练习
第一题:样本由 100 ms 增至 140 ms,为什么表中原始 RTO 从 300 ms 降到 295 ms?
校验:SRTT 增至 105 ms,但 RTTVAR 从 50 ms 降到 47.5 ms,四倍变化项下降 10 ms,合计下降 5 ms。采用一秒下限后,两行最终值相同。
第二题:接收端已经读完数据,发送端却重传同一个序号区间,是否必然说明接收端重复向应用交付?
校验:不能。ACK 丢失就可能造成该现象,TCP 接收重组需要处理重复字节。需要独立检查接收应用结果,不能用发送次数替代交付次数。
第三题:短流只有一个数据段,丢失后没有三个重复 ACK,后来还是成功交付。能否仅凭抓包把这次重发命名为传统快速重传?
校验:不能。应先排除对重复 ACK 的错误计数,再区分超时、尾部探测及实现细节;没有相应运行时证据时,只报告重复发送和最终确认的事实。
参考资料
- RFC 6298:RTT 估计、Karn 算法、RTO 与退避。
- RFC 5681 §2、§3.2:重复 ACK 判定、快速重传与快速恢复。
- RFC 8985:基于时间的丢失检测和尾部探测。






