计算机网络:TCP 是怎么保证可靠传输的?
结论:TCP 把"尽力而为"的 IP 分组,包装成有序、无重复、可重传的字节流。 手段是五件套:序号与确认、超时重传、校验和、滑动窗口、拥塞控制——每一件都对应一种具体的失败模式,不是玄学。
1. 先分清失败模式,再谈机制
| 故障 | 对应机制 | 关键点 |
|---|---|---|
| 数据丢失 | 超时重传 / 快速重传 | RTO 动态估算,收到 3 个重复 ACK 立即重传 |
| 到达乱序 | 序号 + 接收缓冲重排 | 只把连续段交付给应用 |
| 重复到达 | 序号去重 | 重复段直接丢弃并再确认一次 |
| 比特出错 | 首部校验和 | 16 位反码和,能力弱,应用层仍需自校验 |
| 接收方处理慢 | 滑动窗口 rwnd、零窗口探测 | 窗口为 0 时发送方停发,靠 persist 定时器探 |
| 网络整体拥塞 | 拥塞窗口 cwnd、慢启动 / 拥塞避免 / 快恢复 | 发送窗口取 min(rwnd, cwnd) |
所以"TCP 保证可靠"准确的说法是:在连接未断、且对端进程还活着的前提下,TCP 保证字节流不丢、不重、有序。
2. 序号与确认怎么工作
发送方 接收方
seq=1001, len=1000 ---------------------------> 收到,回 ack=2001(期望下一个字节)
seq=2001, len=1000 ------ X 丢失
seq=3001, len=1000 ---------------------------> 收到,但中间有洞,回 ack=2001
seq=4001, len=1000 ---------------------------> 再回 ack=2001(重复 ACK)
发送方收到 3 个重复 ACK,立即重传 2001 段(快速重传)
ack=n是累积确认:n 之前的字节全部收到,n 是期望的下一个字节。- 累积确认的盲区是"缺一段要重传一大片",所以有 SACK(Selective ACK)选项,把已收到的区间告诉发送方。
- RTO 由 RTT 采样动态算(Jacobson / Karels 算法),并带指数退避;重传过的样本会被 Karn 算法排除,避免把重传当往返。
3. 滑动窗口才是吞吐的来源
窗口让发送方可以"不等确认就连发",把链路从停等协议里救出来:
已发送已确认 | 已发送未确认(在途) | 可发送 | 不可发送
<---- 发送窗口 = min(rwnd, cwnd) ---->
rwnd由接收方通过首部窗口字段通告,是流量控制,防的是"发太快把接收缓冲打爆"。cwnd由发送方自己维护,是拥塞控制,防的是"发太快把网络打爆"。- 拥塞控制四阶段:慢启动(cwnd 指数增长)→ 拥塞避免(线性增长)→ 超时则 cwnd 降到 1 并重回慢启动 → 三个重复 ACK 则快重传 + 快恢复(cwnd 减半)。
4. 观测与调参
# 看单条连接的窗口、重传、RTT
ss -ti dst 10.0.0.8
# 常见内核参数
sysctl -w net.ipv4.tcp_retries2=8 # 重传次数上限,默认 15(约 15 分钟才断开)
sysctl -w net.ipv4.tcp_rmem="4096 87380 6291456"
sysctl -w net.ipv4.tcp_wmem="4096 65536 4194304"
sysctl -w net.ipv4.tcp_sack=1
ss -ti 输出里 retrans 是重传计数、rtt 是平滑 RTT、cwnd 与 ssthresh 能直接看出连接是否被限速。
若 retrans 持续上涨而 cwnd 很小,多半是链路丢包而不是应用慢。
5. 常见追问
Q:TCP 可靠,业务就不会丢数据? 不会。应用崩溃、已 ACK 但未落盘、中间设备发 RST、对端进程被 kill,都会让数据"传到了但没生效"。 所以关键链路要应用层确认、幂等重试、落盘后再回 ACK。
Q:重传会一直重试吗?
不会。tcp_retries2 默认 15 次,累计约 15 分钟后内核放弃并断开连接,应用收到 ETIMEDOUT。
这也是为什么"卡住十几分钟才报错"的故障,往往要看这个参数。
Q:为什么还需要 QUIC 在 UDP 上重做一遍? TCP 的重传是连接级的:一个段丢了,后续数据即使到达也要等它补齐(队头阻塞)。 QUIC 把流拆开,每个流独立排序与重传,还能在用户态快速迭代拥塞算法,并用 0-RTT 省握手。
Q:校验和为什么不够? 16 位反码和的碰撞概率不低,且不覆盖伪首部之外的所有中间改写。 真正的完整性保障要靠 TLS 的 AEAD 或应用层摘要,TCP 校验和只是廉价的第一道筛子。
Q:为什么窗口会突然变小甚至为 0? 接收方应用读得慢,接收缓冲被填满,通告窗口降到 0;发送方进入 persist 状态周期探测。 若长期为 0,问题在消费端而非网络。
小结
TCP 的可靠性不是单点能力,而是"序号 + 确认 + 重传 + 窗口 + 拥塞控制"的组合拳: 序号解决乱序与重复,确认与重传解决丢失,校验和解决比特错误,窗口解决两端速度不匹配,拥塞控制解决整网过载。 理解每个机制挡住哪种故障,比背名词更能应对追问。