TCP 靠什么判断一个包丢了?RTO 是怎么算出来的,为什么再快的网络它也有下限?重传间隔是不是真的每次翻倍?这篇用 tcp_info 读内核算出的 srtt / rttvar / 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,没有看到严格翻倍,原因没有深究。