一句话结论(30s)
TCP 与 UDP 是传输层的两种哲学:TCP 为可靠——面向连接、确认重传、流量控制、拥塞控制,保证无差错、不丢失、不重复、按序到达;UDP 为速度——无连接、无确认、无流控,只做端口复用与校验,可靠性交给应用层。选择本质是在可靠与低延迟间权衡,而 QUIC 证明可在 UDP 之上按需定制可靠传输。
核心原理(2min)
六维对比:连接性(TCP 面向连接、三次握手,UDP 无连接)、可靠性(TCP 六大机制保证,UDP 不保证)、拥塞控制(TCP 内置,UDP 没有)、首部开销(TCP 20-60 字节,UDP 8 字节)、传输方式(TCP 字节流,UDP 数据报)、服务对象(TCP 一对一,UDP 可组播广播)。QUIC 在 UDP 上解决 TCP 的队头阻塞、连接迁移、握手延迟三大痛点。
底层深入(5-10min)
传输层的两种设计哲学
TCP 和 UDP 代表了传输层协议设计的两种根本哲学:
- TCP:一切为了可靠。面向连接、确认重传、流量控制、拥塞控制——宁可牺牲速度,也要保证数据无差错、不丢失、不重复、按序到达
- UDP:一切为了速度。无连接、无确认、无重传、无流控——只做端口复用和数据校验,剩下的全交给应用层
核心区别
连接性
TCP 是面向连接的。三次握手建立连接 → 数据传输 → 四次挥手释放连接。在建立连接时,双方协商初始序列号(ISN)、MSS(最大报文段大小)、窗口缩放因子等参数。连接状态由五元组(源 IP、源端口、目的 IP、目的端口、协议号)唯一标识,在内核中对应一个 struct tcp_sock。
UDP 是无连接的。发送方把数据封装成数据报直接扔出去,不管接收方是否存在、是否准备好。内核中一个 struct udp_sock 可以服务于多个不同目的地的数据报。
💭 思考:为什么 TCP 建连要三次握手、协商一堆参数,UDP 却能“不打招呼直接扔”?——因为 TCP 承诺了可靠性,而可靠性需要“上下文”:双方得先对齐初始序列号、MSS、窗口缩放,之后的数据编号、确认、重传才有据可依。UDP 不承诺可靠,也就不需要任何前置上下文,发就是了。所以“握手”的代价,本质是为“可靠”提前支付的成本;省掉它,也就同时放弃了可靠。
可靠性
TCP 通过六大机制保证可靠性(下一篇详述):序列号 + 确认应答 + 超时重传 + 流量控制 + 拥塞控制 + 校验和。TCP 保证数据无差错、不丢失、不重复、按序到达。
UDP 只做校验和(可选,IPv4 中可关闭),不保证任何可靠性。数据报可能在网络中丢失、到达顺序可能错乱、可能重复到达。UDP 将这些责任完全交给应用层——这正是 QUIC 的切入点。
思考:UDP 把可靠性「全丢给应用层」,这是偷懒还是智慧?答案是智慧——因为它给上层留了「按需定制」的自由。视频通话要的是低延迟,偶尔丢一帧无所谓;文件传输才需要逐字节确认。TCP 的可靠是「一刀切」,对实时场景反而过度保守,这正是 QUIC 能基于 UDP 做出更好传输的切入点。
拥塞控制
TCP 内置拥塞控制算法(CUBIC、BBR 等),通过拥塞窗口(cwnd)限制发送速率,在网络拥塞时主动退避。这是互联网稳定运行的基石——如果所有 TCP 连接都不做拥塞控制,中间路由器的队列会溢出,导致全网丢包率飙升。
UDP 没有拥塞控制。发送方可以以任意速率发送数据报,不受网络状况影响。这种 “自私” 的行为可能加剧网络拥塞,因此 QUIC 等基于 UDP 的协议在应用层实现了自己的拥塞控制。
思考:为什么「UDP 没有拥塞控制」反而是互联网稳定运行的隐患?因为拥塞控制是全网「共享的公共资源保护」——TCP 检测到拥塞会主动降速退避,大家互相谦让才不挤垮路由器。UDP 可以无视拥塞满速发包,像不排队的人一样插队,多个 UDP 流一起冲,路由器队列就会溢出、全网丢包飙升。
首部开销
TCP 首部:最少 20 字节,加上选项最多 60 字节。包含:源/目的端口(各 2 字节)、序列号(4 字节)、确认号(4 字节)、数据偏移 + 保留 + 标志位(2 字节)、窗口大小(2 字节)、校验和(2 字节)、紧急指针(2 字节)。
UDP 首部:固定 8 字节。包含:源/目的端口(各 2 字节)、长度(2 字节)、校验和(2 字节)。极简设计,开销极低。
对于小数据包(如 DNS 查询),TCP 的 20+ 字节首部加上三次握手的额外往返是非常沉重的负担。这也是 DNS 默认使用 UDP 的根本原因。
传输方式
TCP 是字节流协议。发送方连续写入的数据在 TCP 看来只是字节序列,没有消息边界。接收方从套接字读取时,可能读到部分消息或多个消息的拼接体。应用层必须自行定义消息边界(长度前缀、分隔符、固定长度)。
UDP 是报文协议。每个 sendto 对应一个独立的数据报,边界在传输过程中保持完整。接收方 recvfrom 一次收到完整的一个数据报(或完全收不到)。UDP 天然具有消息边界,应用层不需要做分包/粘包处理。
💭 思考:为什么同样是传输数据,TCP 要拆成“字节流”、UDP 却能保持“消息边界”?——这是可靠性带来的副作用。TCP 要保证字节“不丢、不乱序”,就必须把数据切成段、按序编号、按需重传,落到接收方手里的自然是一段“连续的字节序列”,哪里还有“消息”的影子?UDP 每个数据报独立成包、一次到齐或全丢,边界天然完整。所以“字节流无边界”不是 TCP 的缺陷,而是它“可靠、有序”承诺的必然结果。
服务对象
TCP 是一对一的。一个 socket 连接唯一的远端。如果需要向多个接收方发送相同数据,必须在应用层复制多份。
UDP 支持一对一、一对多、多对一、多对多。通过组播(Multicast)和广播(Broadcast),一个数据报可以同时发送给多个接收方。这在音视频直播、局域网服务发现(mDNS)中非常重要。
QUIC:基于 UDP 实现可靠传输
QUIC(Quick UDP Internet Connections)是由 Google 设计、现已标准化的传输层协议。它的核心创新是在应用层用 UDP 实现了 TCP 级别的可靠传输,甚至更好。
QUIC 解决了 TCP 的哪些痛点
1. 队头阻塞(Head-of-Line Blocking)
TCP 是严格的字节流,序列号连续。如果序列号为 N 的包丢失,序列号 N+1 即使已到达也无法交付给应用层(因为 N 还没到,字节流不能出现空洞)。
HTTP/2 在单个 TCP 连接上多路复用多个请求流,但所有流共享同一个 TCP 的字节流。任何一个流中的丢包都会阻塞所有其他流——这就是 HTTP/2 的队头阻塞问题。
QUIC 在底层使用 UDP,数据包独立传输。QUIC 内部维护多个独立的数据流(Stream),每个流有自己的序列号和重传机制。Stream A 的丢包不会阻塞 Stream B 的数据交付。
💭 思考:为什么 TCP 一个流里丢一个包,会连累所有流一起等?——因为 TCP 只有一个全局连续的字节流,序列号是“一根绳子”串起来的。第 N 个包没到,N+1 哪怕到了也不能交付,否则字节流就出现空洞;HTTP/2 的多个请求流又都复用这同一个 TCP 字节流,于是“一个流的丢包”被放大成“所有流的阻塞”。QUIC 的解法是把“一根绳子”换成“多根绳子”——每个 Stream 独立编号、独立重传,互不牵连。
2. 连接迁移
TCP 连接由五元组(源 IP、源端口、目的 IP、目的端口、协议)标识。如果客户端从 Wi-Fi 切换到 4G,IP 地址改变,TCP 连接必然中断,必须重新三次握手。
QUIC 使用 Connection ID 标识连接,不依赖 IP 地址。客户端切换网络时,后续数据报使用新的源 IP 但携带相同的 Connection ID,服务端识别后无缝继续通信。
3. 握手延迟
TCP + TLS 1.3 需要 2-RTT(往返时间)才能开始发送数据(TCP 三次握手 1-RTT + TLS 握手 1-RTT)。
QUIC 将加密握手与传输握手合并,理想情况下 0-RTT(过去连接过的服务端)或 1-RTT(新建连接)即可完成握手并发送应用数据。
QUIC 告诉我们什么
QUIC 的成功证明了一个重要观点:UDP 的”不可靠”不是缺点,而是灵活性。UDP 提供了最小化的传输层骨架(端口复用 + 校验),可靠传输的逻辑可以(也应该)在应用层根据具体需求定制。TCP 的可靠机制是”一刀切”的——对文件传输完美,但对实时视频通话过于保守。基于 UDP,每种应用可以选择最适合自己的可靠性策略。
思考:QUIC 的成功说明了什么?说明 UDP 的「不可靠」不是缺陷,而是「可编程的灵活性」。UDP 只保留最小骨架(端口复用+校验),把可靠性做成「可选的积木」,让每种应用按自己的需求拼装——实时应用拼「低延迟+容忍丢包」,可靠传输拼「确认+重传」。这比 TCP 的「固定套餐」更贴合多样化的场景。
选型对比总结
| 维度 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接(三次握手) | 无连接 |
| 可靠性 | 保证可靠交付 | 不保证 |
| 顺序 | 保证按序到达 | 不保证 |
| 速度 | 较慢(拥塞控制限速) | 快(无流控) |
| 首部 | 20-60 字节 | 8 字节 |
| 传输方式 | 字节流 | 数据报 |
| 通信模式 | 一对一 | 一对一 / 一对多 / 多对多 |
| 适用场景 | 文件传输、Web、邮件、SSH | DNS、视频通话、直播、游戏、IoT |
选择 TCP 还是 UDP,本质上是在可靠性和低延迟/低开销之间做权衡。而 QUIC 的出现模糊了这条边界——它告诉我们,你不需要在两者之间二选一,你可以在 UDP 之上构建恰好满足你需求的传输语义。
章末提问
Q1:TCP 和 UDP 的核心区别是什么?
结论先行:TCP 面向可靠(面向连接、确认重传、流量/拥塞控制),UDP 追求速度(无连接、无确认、无流控,只做端口复用与校验)。因为 TCP 宁可牺牲速度也要保证无差错、不丢失、按序到达;UDP 把可靠性完全交给应用层,只求最低开销、最低延迟。
Q2:TCP 的字节流和 UDP 的数据报有什么区别?为什么 TCP 会粘包?
结论先行:TCP 是字节流、无消息边界,UDP 是数据报、天然有边界;所以 TCP 会粘包、UDP 不会。因为 TCP 把数据看成连续的字节序列,多次 write 可能被拼在一起、一次 read 可能读半条消息,应用层必须自定边界(长度前缀/分隔符);UDP 每个 sendto 对应一个完整数据报,边界在传输中保持完整。
Q3:既然 UDP 不可靠,为什么 QUIC/HTTP3 还要基于 UDP?
结论先行:因为 UDP 提供了「最小骨架 + 可定制」的自由,可靠逻辑可以在应用层按需实现。因为 TCP 的可靠机制是「一刀切」,还带着队头阻塞、连接迁移难、握手慢的包袱;在 UDP 之上,QUIC 能给每个 Stream 独立可靠传输、用 Connection ID 切网不断、0-RTT 握手,反而比 TCP 更好。