3、TCP重传机制详解:超时重传(RTO)、快速重传(DupACK)、选择性确认(SACK)、重传定时器
TCP 的可靠性,说白了就靠两样东西:确认和重传。
数据发出去,对方没回 ACK,怎么办?重传。但怎么重传、什么时候重传、重传哪些数据,这里面的门道可不少。我刚开始做网络排查时,一看到抓包里有重传就紧张,后来才发现——重传本身不可怕,可怕的是你不知道它为什么重传。
这一章,咱们就把 TCP 的四种重传机制掰开揉碎讲清楚。
3.1 超时重传(RTO)—— 最后的防线
超时重传,就是 TCP 给每个数据段设了一个定时器。如果定时器到期还没收到 ACK,就认为这个段丢了,直接重传。
听起来简单,但这里有个关键问题:超时时间设多长?
设短了,网络稍微慢一点就乱重传,浪费带宽。设长了,真丢了包要等半天,用户体验极差。
RTO 的计算公式
TCP 用一套动态算法来算 RTO,核心是跟踪 RTT(往返时间)的变化。
| 参数 | 含义 | 计算公式 |
|---|---|---|
| SRTT | 平滑后的 RTT | SRTT = (1 - α) × SRTT + α × RTT |
| RTTVAR | RTT 的偏差 | RTTVAR = (1 - β) × RTTVAR + β × |SRTT - RTT| |
| RTO | 超时重传时间 | RTO = SRTT + 4 × RTTVAR |
α 通常取 1/8,β 取 1/4。你想想看,这个公式的好处是:RTT 波动越大,RTO 就越大,避免误判。
指数退避
重传后还没收到 ACK 怎么办?TCP 会把 RTO 翻倍,再等。还不行?再翻倍。
这就是指数退避。我见过一个极端案例:某个服务器连续重传了 15 次,RTO 从 200ms 涨到了 6 分钟。嗯,这时候基本可以断定对端已经挂了。
3.2 快速重传(DupACK)—— 不等超时,立刻行动
超时重传有个明显的缺点:慢。要等一个 RTO 周期,通常几百毫秒甚至几秒。
快速重传的思路是:让对方「催」我重传。
怎么催?用重复的 ACK。
举个例子:
- 发送方发了 Seq=1, 2, 3, 4, 5
- 接收方收到了 1,回了 ACK=2
- 收到了 3,但 2 丢了,所以回 ACK=2(重复)
- 收到了 4,还是没等到 2,继续回 ACK=2(重复)
- 收到了 5,依然回 ACK=2(重复)
发送方连续收到 3 个重复的 ACK(即 4 个相同的 ACK),就知道 Seq=2 丢了。不等超时,立刻重传。
快速重传 vs 超时重传
| 对比项 | 超时重传 | 快速重传 |
|---|---|---|
| 触发条件 | 定时器到期 | 收到 3 个 DupACK |
| 响应速度 | 慢(RTO 级别) | 快(RTT 级别) |
| 适用场景 | 严重丢包、对端无响应 | 轻度丢包、网络正常 |
| 对拥塞控制的影响 | 重置拥塞窗口为 1 | 拥塞窗口减半 |
说白了,快速重传是「轻量级」的补救措施,超时重传是「核武器」。我在抓包分析时,如果看到大量快速重传,说明网络有轻微丢包;如果看到超时重传,那问题就严重了。
3.3 选择性确认(SACK)—— 精准打击,不浪费一颗子弹
快速重传有个问题:它只知道某个段丢了,但不知道后面哪些段已经收到了。
举个例子:发送方发了 Seq=1~10,其中 3 丢了,但 4~10 都到了。快速重传只重传 3,没问题。但如果 3、5、7 都丢了呢?快速重传只能一个一个来,效率很低。
SACK 就是来解决这个问题的。
SACK 的工作原理
接收方在 ACK 里带上 SACK 选项,告诉发送方:「我收到了哪些不连续的数据块」。
TCP 选项示例(SACK):
SACK: 1001-2000 (已收到)
SACK: 3001-4000 (已收到)
SACK: 5001-6000 (已收到)
// 说明 2001-3000 和 4001-5000 丢了
发送方一看,就知道要重传哪些段。一次重传搞定,不用等一轮又一轮的 DupACK。
SACK 的局限性
- SACK 选项最多携带 4 个块(Linux 默认),如果丢包太多,也覆盖不全
- SACK 需要双方协商,在 TCP 三次握手的 SYN 中声明支持
- SACK 不能替代 ACK,它只是补充信息
3.4 重传定时器——TCP 的「闹钟」系统
TCP 内部维护着多个定时器,重传只是其中之一。我整理了一下,TCP 的定时器家族是这样的:
| 定时器名称 | 作用 | 触发动作 |
|---|---|---|
| 重传定时器(RTO) | 监控未确认的数据段 | 超时重传 |
| 坚持定时器 | 防止窗口通告丢失 | 发送窗口探测 |
| 保活定时器 | 检测空闲连接是否存活 | 发送保活探测 |
| 2MSL 定时器 | TIME_WAIT 状态计时 | 关闭连接 |
重传定时器是其中最核心的一个。每个发送出去但未确认的段,都会启动一个重传定时器。收到 ACK 就取消,没收到就触发重传。
重传定时器的实际行为
我在抓包中经常看到这样的模式:
时间戳 源IP → 目的IP 信息
0.000 A → B Seq=1, Len=100
0.200 A → B Seq=101, Len=100
0.400 A → B Seq=201, Len=100
0.600 B → A ACK=101
0.800 B → A ACK=201
1.000 B → A ACK=301
// 一切正常,RTT 约 200ms
// 突然丢包:
2.000 A → B Seq=401, Len=100
2.200 A → B Seq=501, Len=100
2.400 A → B Seq=601, Len=100
2.600 B → A ACK=401 (重复)
2.800 B → A ACK=401 (重复)
3.000 B → A ACK=401 (重复)
// 触发快速重传
3.000 A → B Seq=401, Len=100 (重传)
3.200 B → A ACK=701
// 恢复
你看,从丢包到恢复,只用了 1 秒。如果等超时重传,RTO 可能是 3 秒,差距很明显。