3、TCP可靠传输机制:确认应答、超时重传与SACK
TCP 号称可靠传输,靠的是什么?说白了,就是三把斧头:确认应答(ACK)、超时重传(RTO),还有快速重传与选择性确认(SACK)。这三者配合,才让数据在不可靠的 IP 网络上跑得稳稳当当。
我刚开始调 TCP 栈的时候,总觉得这些机制是协议栈自动搞定的,不用管。直到有一次线上服务出现大量超时重传,我才意识到——不理解这些细节,出了问题你连日志都看不懂。
3.1 确认应答机制(ACK)
ACK 是 TCP 最基础的可靠性保证。发送方发一个数据段,接收方必须回复一个 ACK,告诉对方“我收到了”。如果发送方没收到 ACK,它就认为数据丢了。
这里有个关键点:ACK 是累积确认的。什么意思?你发 seq=1、2、3 三个包,接收方如果只收到了 1 和 3,它不会 ACK 3,而是会重复 ACK 1,告诉发送方“我还在等 2”。
核心原则:TCP 不确认乱序到达的包,只确认连续收到的最大序号。
我在项目中遇到过一个问题:客户端频繁重传,抓包一看,服务端一直在回 ACK=1,但客户端明明发了 seq=2、3、4。后来发现是服务端接收缓冲区满了,把乱序包丢了。嗯,这其实是接收窗口的问题,后面会细讲。
3.2 超时重传机制(RTO)
发送方发出数据后,会启动一个定时器。如果定时器超时还没收到 ACK,它就认为数据丢了,重新发送。这个超时时间就是 RTO(Retransmission Timeout)。
RTO 不是固定的。TCP 会根据网络延迟动态计算。公式大致是:
SRTT = (1 - α) × SRTT + α × RTT
RTO = SRTT + 4 × RTTVAR
其中 SRTT 是平滑后的往返时间,RTTVAR 是 RTT 的偏差。说白了,网络越不稳定,RTO 就越大,避免不必要的重传。
| 场景 | RTO 行为 | 我踩过的坑 |
|---|---|---|
| 正常网络 | RTO 稳定在 200ms 左右 | —— |
| 高延迟链路 | RTO 自动增大 | 跨洋链路 RTO 经常到 1s 以上 |
| 网络抖动 | RTO 快速调整 | 我曾经遇到过 RTO 从 300ms 跳到 3s,导致应用层超时 |
注意:RTO 最小值通常是 200ms(RFC 6298 建议),最大值不超过 60s。如果你的应用对延迟敏感,这个下限可能会成为瓶颈。
3.3 快速重传
超时重传有个问题:它要等 RTO 超时,太慢了。如果网络只是轻微丢包,等 RTO 白白浪费几百毫秒。
快速重传就是为了解决这个问题的。它的逻辑很简单:发送方收到 3 个重复的 ACK,就立即重传,不等超时。
举个例子:
- 发送方发 seq=1、2、3、4、5
- seq=2 丢了,接收方收到 3、4、5,但都回复 ACK=2
- 发送方收到 3 个 ACK=2,立刻重传 seq=2
你想想看,这比等 RTO 快了多少?几乎就是立即响应。
个人经验:我曾经调优过一个文件传输服务,把快速重传阈值从 3 改成 2,确实减少了传输延迟,但代价是增加了误重传的概率。建议非极端场景不要动这个参数。
3.4 选择性确认(SACK)
快速重传虽然快,但它有个缺陷:它不知道哪些包丢了,哪些包到了。比如上面那个例子,发送方重传了 seq=2,但 seq=3、4、5 其实已经到了。如果接收方只回复 ACK=2,发送方还得猜后面是不是全丢了。
SACK 就是来解决这个问题的。它允许接收方在 ACK 里告诉发送方:“我收到了哪些不连续的包”。
SACK 的格式是这样的:
TCP Option - SACK
Kind: 5
Length: 10 (每个块占 8 字节,最多 4 个块)
Left Edge: 已收到块的起始序号
Right Edge: 已收到块的结束序号
有了 SACK,发送方就知道只需要重传丢失的那几个包,不用把后面的全重传一遍。这在高速网络里效果特别明显。
| 特性 | 无 SACK | 有 SACK |
|---|---|---|
| 丢包恢复效率 | 低,可能重传已收到的包 | 高,只重传丢失的包 |
| 带宽利用率 | 低,浪费带宽 | 高,精准重传 |
| 兼容性 | 所有 TCP 都支持 | 需要双方协商 |
避坑指南:我曾经在一个嵌入式设备上遇到过 SACK 协商失败的问题。设备内核太老,不支持 SACK,结果丢包后性能直接腰斩。解决方案是升级内核,或者在应用层做补偿。
3.5 三种机制的关系
这三种机制不是孤立的,它们配合工作:
- ACK 是基础,所有重传都依赖它
- RTO 是兜底,防止 ACK 丢失导致死等
- 快速重传 + SACK 是优化,减少不必要的等待和重传
我习惯把这三者比作一个团队:ACK 是普通员工,每天汇报工作;RTO 是项目经理,定期检查进度;快速重传和 SACK 是应急小组,出了问题立刻响应。
从图上你能看到,数据从发送方出发,接收方回复 ACK。如果丢包了,快速重传和 SACK 会优先介入,只有它们都失效了,RTO 才会出手。这个优先级设计,就是为了在可靠性和效率之间找到平衡。
我的建议:如果你在做 TCP 优化,优先检查 SACK 是否开启。很多老旧系统默认关闭 SACK,丢包后性能惨不忍睹。用 sysctl net.ipv4.tcp_sack 看一眼,1 就是开启。