2171 字
约 7 分钟
5
S02-B04 · TCP 可靠传输与 UDP:谁在保证"不丢、不乱、不重"

适用读者:知道"TCP 可靠"但说不清"靠什么可靠"的人;被"偶发超时""吞吐上不去"困扰过的人。
前置:S02-B03(握手与首部字段)。
实测环境:Ubuntu 24.04.1 / Linux 6.8 / iproute2 + tc netem;实测日期 2026-11-05。
覆盖旧文:A-1035(TCP 如何保证可靠传输,101 点)、A-1038(TCP 与 UDP 的区别,89 点)= 190 点。
覆盖扩展:E-S02-006(拥塞控制)、E-S02-008(QUIC)。
上一篇:S02-B03 · TCP 三次握手与四次挥手:为什么是三次不是两次
下一篇:S02-B05 · 粘包与拆包。


1. 一句话结论

TCP 的可靠性来自四件事:序号定位、确认回执、超时重传、窗口限流。 四者缺一不可。

机制 解决什么 对应首部字段
序号(seq) 给每个字节编号 → 能发现丢/乱 seq
确认(ack) 告诉对方"我收到了到第几字节" ack
重传 没收到确认就再发 RTO、SACK
窗口 别发太快把我撑爆 window、Window Scale
(另加)拥塞控制 别发太快把网络撑爆 cwnd

"乱"和"重"的处理方式不一样:乱序靠序号重排(接收缓冲区),重复靠序号去重(已收的直接丢弃并再确认)。


2. 实测:丢包对 TCP 有多致命

2.1 无损基线

本机 loopback 传 12.5 MB:

[无损] 接收 12.50 MB,耗时 0.03s,吞吐 472.3 MB/s

2.2 注入 20% 丢包后(同一个程序、同一个方向)

tc qdisc add dev lo root netem loss 20%
接收 12.50 MB,耗时 46.80s
新增重传段 = 67
场景 耗时 吞吐
无损 0.03 s 472 MB/s
20% 丢包 46.80 s 0.27 MB/s

吞吐掉了约 1,700 倍。 这不是夸张,是 TCP 的设计后果:

  1. 丢包 → 触发重传(RTO 通常是几百毫秒级);
  2. 丢包被当作"网络拥塞"信号 → 拥塞窗口被砍半甚至回到慢启动;
  3. 于是"丢一个包,整体速度断崖式下降"。

这条实测解释了两个真实故障:

现象 原因
"带宽明明够,为什么慢" 有丢包,TCP 自己在限速
"长距离跨国调用特别慢" RTT 大 + 轻微丢包,吞吐被 BDP 与拥塞控制双重限制

3. 四个机制逐一拆

3.1 序号与确认:连续字节流的记账

发送方                               接收方
seq=1000, len=100  ───────────────►   收到 1000–1099,回 ack=1100(期望下一个字节)
seq=1100, len=100  ────── 丢了 ───X
seq=1200, len=100  ───────────────►   收到 1200–1299,但 1100–1199 缺 → 回 ack=1100(重复确认)

重复 ACK 是关键信号:收到 3 个对同一位置的重复 ACK → 快重传(不必等超时)。

3.2 超时重传与 RTO

  • RTO(Retransmission Timeout)根据 RTT 动态计算(见 B03 的 ss -ti 里 rto:204)。
  • RTO 太小 → 无谓重传(浪费带宽);太大 → 丢包后长时间卡住。
  • RTO 的最小值在 Linux 上是 200 ms 级——所以任何一次超时重传,至少损失 200 ms。

这就是"高延迟链路最怕丢包"的量化解释:一次重传 = 200 ms 起。

3.3 流量控制(接收方别被撑爆)

接收方在 ACK 里带 window(还能收多少字节)。发送方受此限制。

窗口为 0 时:发送方停止,定期发"窗口探测"询问。

3.4 拥塞控制(网络别被撑爆)

阶段 行为
慢启动 cwnd 从 1 开始指数增长(每 RTT 翻倍)
拥塞避免 到阈值后改为线性增长
快重传/快恢复 3 个重复 ACK → 立即重传,cwnd 减半
超时 cwnd 回到 1(最狠的惩罚)

算法演进:Cubic(Linux 默认,实测 ss -ti 里显示 cubic)→ BBR(基于带宽与 RTT 建模,弱网表现更好)。

3.5 BDP:为什么"窗口小"会限制吞吐

吞吐上限 ≈ 窗口大小 / RTT
场景 窗口 RTT 理论吞吐
内网 64 KB 0.2 ms 极大(不是瓶颈)
同城 64 KB 5 ms 12.8 MB/s
跨国 64 KB 200 ms 0.32 MB/s
跨国(窗口放大到 4 MB) 4 MB 200 ms 20 MB/s

所以 B03 里说的 Window Scale 选项不是可选项——没有它,长肥管道(long fat network)上吞吐上不去。


4. UDP:把复杂性交回应用层

维度 TCP UDP
连接 面向连接(握手) 无连接
可靠性 确认 + 重传 不保证
顺序 保证有序 不保证
边界 字节流(无消息边界) 数据报(保留边界)
首部 20 字节起 8 字节
拥塞控制 有(自动降速) 无(可以打满,也可能打死网络)
适用 网页、API、数据库连接 音视频、游戏、DNS、QUIC

4.1 UDP 什么时候更好

场景 为什么 UDP
实时音视频 宁可丢一帧也不要等重传(重传来的帧已经过期)
游戏 位置更新只关心"最新",旧包无用
DNS 一问一答,包很小,握手成本占比高
QUIC / HTTP3 在 UDP 上自己实现可靠传输 + 多路复用(E-S02-008)

判据:"迟到的数据还有用吗?" 没用 → UDP(或 QUIC);有用 → TCP。

4.2 QUIC 的思路(E-S02-008)

QUIC = UDP + 用户态实现的可靠性 + 加密 + 多路复用:

特性 解决 TCP 的什么问题
0-RTT / 1-RTT 握手 TCP+TLS 要两轮,QUIC 合并
流级多路复用 TCP 的队头阻塞(一个包丢,所有流都等)
连接迁移(Connection ID) 换 WiFi/4G 时 TCP 连接会断,QUIC 不断
用户态实现 升级不用改内核

代价:UDP 在部分企业网络被封;CPU 开销高于内核 TCP。


5. 排障:怎么判断"是网络慢还是应用慢"

# ① 看连接级指标(RTT、重传、拥塞窗口)
ss -ti state established

# ② 看全系统重传统计
nstat -az | grep -iE "TcpRetransSegs|TcpExtTCPLostRetransmit"

# ③ 看丢包与错误
ip -s link show <网卡>     # RX/TX errors, dropped
指标 正常 异常含义
retrans 占比 < 0.1% 高 → 链路质量差或拥塞
rtt 稳定 抖动大 → 路由不稳/中间设备排队
cwnd 随流量增长 长期很小 → 一直被判拥塞

J01 会把这套流程做成完整的排障 SOP(含 tcpdump 替代方案、内核参数调优)。


6. 五个误区

误区 真相
"TCP 可靠 = 应用不会丢数据" 只保证"字节流到达",连接断了照样丢;应用层仍需确认与幂等
"UDP 不可靠所以不能用" 音视频/游戏/DNS/QUIC 大量使用;不可靠≠不好用
"带宽够就不慢" RTT 与丢包才是主因(§3.5 的 BDP)
"重传是异常" 轻微重传正常;持续 > 1% 才要查
"关掉拥塞控制能更快" 短期快,长期造成全局拥塞崩塌(还会被中间设备限速)

7. 自测

  1. 三个重复 ACK 触发什么?比等超时重传快多少(量级)?
  2. 吞吐上限公式是什么?跨国 200 ms RTT、窗口 64 KB,理论吞吐多少?
  3. 为什么音视频更愿意用 UDP?
  4. QUIC 用 UDP 实现,解决了 TCP 的哪三个具体问题?
  5. 线上"带宽够但慢",你会先看哪三个指标?

8. 下一篇

S02-B05 · 粘包与拆包:应用层为什么必须自己划边界

可靠传输保证"字节都到了、顺序也对",但它不保证"你的两条消息是两次 recv"。下一篇用一段真实代码复现"hello + world 变成一次收到 helloworld",并给出三种划边界方案与判定方法。


本篇属于系列《S02 · 计算机与网络基础》。旧文来源:A-1035、A-1038;扩展点 E-S02-006/008。丢包对照(472 MB/s vs 0.27 MB/s)为 2026-11-05 实测。

S02-B04 · TCP 可靠传输与 UDP:谁在保证"不丢、不乱、不重"
https://www.clxhxhhr.top/posts/5185/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。