bigywater.tech
网络协议 · 46

超时重传:RTO 的计算与指数退避

TCP 靠什么判断一个包丢了?RTO 是怎么算出来的,为什么再快的网络它也有下限?重传间隔是不是真的每次翻倍?这篇用 tcp_info 读内核算出的 srtt / rttvar / rto,再在丢包的回环上抓重传间隔核对。

307 ms
RTT 稳定在 100 ms 之后内核给出的 RTO,约等于 srtt + 4×rttvar + 200 ms(rexmt_slop)
230 ms
RTT 只有 1 ms 的回环上 RTO 的下限
316 → 353 → 437 ms
30% 丢包下首次、第 2 次、第 3 次重传间隔的中位数(没有看到每次翻倍)

RTO 的组成

实测 · 本机执行输出
  net.inet.tcp.rexmt_slop: 200
  net.inet.tcp.backoff_maximum: 65536
    第几次   发完立刻读到的 rto(ms)   收到回显后 srtt(ms)  rttvar(ms)  本次测得 RTT(ms)  srtt + 4×rttvar
       1            500                    100              37             101             248
       2            451                    100              28             100             212
       3            413                    100              21             100             184
       4            385                    100              15             100             160
       5            363                    100              12             100             148
       6            348                    100               9             102             136
       7            338                    100               7             100             128
       8            329                    100               5             101             120
       9            322                    100               4             100             116
      10            317                    100               3             101             112
      11            314                    100               3             102             112
      12            312                    100               2             100             108
      13            310                    100               1             100             104
      14            308                    100               1             100             104
      15            307                    100               1             100             104
  RTT 稳定在约 100 ms 时:rttvar 从第一次的 37 ms 一路收敛到 1 ms,srtt 稳定在 100 ms;rto 从 500 ms 变到 307 ms
  对照公式:rto 并不等于 srtt + 4 × rttvar(右边最后一列),因为 RTO 有下限(常见下限是几百毫秒),还要按计时器的粒度(这里是 500 ms 一个滴答的 slow timer 还是更细,取决于实现)取整——这里只比较量级

内核维护平滑 RTT(srtt)和平均偏差(rttvar),RTO ≈ srtt + 4 × rttvar,再加上 rexmt_slop(200 ms)的松弛。RTT 抖动越小 RTO 越小,但有下限。

下限

实测 · 本机执行输出
  不加延迟(lo0 本身)的 10 次:发完立刻读到的 rto = [230, 230, 230, 230, 230, 230, 230, 230, 230, 230] ms;srtt = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] ms;rttvar = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] ms
  RTT 只有零点几毫秒,但 rto 仍然在 230 ms 量级:RTO 有下限,不会随着 RTT 小就小下去(否则正常的延迟 ACK 就会引起误重传)

回环上 RTT 只有 1 ms,RTO 仍是 230 ms,防止延迟 ACK 造成误重传。

退避

实测 · 本机执行输出
  120 次收发共用 55.5 秒完成(无丢包时每次约 0.1 秒);tcp_info 里这条连接发出的包 188 个,其中重传 68 个(6800 字节)
  抓包里 120 个 100 字节的数据段按“被发送的次数”分:发送 1 次 × 76;发送 2 次 × 26;发送 3 次 × 14;发送 4 次 × 2;发送 5 次 × 2(同一个包在 dummynet 里会被抓到多次,相隔 120 ms 以内的按一次算)
  第 1 次重传距首次发送:44 个样本,最小 301 / 中位 316 / 最大 411 ms;第 2 次重传距第 1 次重传:18 个样本,最小 308 / 中位 353 / 最大 704 ms;第 3 次重传距第 2 次重传:4 个样本,最小 420 / 中位 437 / 最大 867 ms
  第 2 次间隔 / 第 1 次间隔的中位数之比 1.12——指数退避就是每失败一次把 RTO 翻倍(在 backoff_maximum 内)
  RTO 退避的目的:网络拥塞时重传会加重拥塞,所以越失败越稀,直到 net.inet.tcp.backoff_maximum 封顶

丢包时重传间隔逐次变长,封顶在 backoff_maximum(64 秒)。这次实验里 316 → 353 → 437 ms,没有看到严格翻倍,原因没有深究。

分步演示

超时重传
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64(Apple 芯片)/ Python 3.14.8 / tcpdump(libpcap)/ scapy 2.8 / dpkt / dnspython / h2 / aioquic / OpenSSL / curl 8.7.1;抓包在 lo0(环回)上做,少数实验用真实网卡 en0(这台机器上 en0 是 Wi-Fi,tcpdump 看到的是系统转成以太网格式的帧,所以文中的“以太网头”都指这个 14 字节的头)或访问公网(会在文章里注明);需要丢包 / 延迟 / 限速时,用一个只作用于 lo0 端口 9400-9499 的 dummynet 辅助脚本(netlab);所有服务端都是本机自己起的进程;时间戳取自 tcpdump 的 -tt,数字每次运行会有小幅抖动。全部在 lo0 上,延迟与丢包由 netlab 注入;srtt / rttvar / rto 来自 TCP_CONNECTION_INFO(单位 ms,取整);第三节的抓包有 dummynet 重复,相隔 120 ms 以内的拷贝按同一次发送处理,重传次数多的样本很少(第 2 / 3 次重传间隔只有 18 个、4 个样本)
    • 没有验证:RTO 达到 backoff_maximum 之后连接被放弃的总时间;en0 真实网络上的重传;Karn 算法(重传段不参与 RTT 采样)的具体表现;尾部丢失探测(TLP)与 RACK 对重传时间的影响;rexmt_slop 能否调整(需要 root)
    京ICP备2026067502号-1京公网安备11010502063487号