📚 第六章 TCP与UDP
📄 一句话概括

UDP 只做两件事:加个端口、算个校验和,剩下的全交给应用。 它省掉的是「连接」和「可靠性」,换回来的是「没有建立延迟、没有队头阻塞」。

📝 本节和前面几节的关系

6.1.3 讲了 UDP 的三个特征,这一节讲「这三个特征意味着应用要自己做什么」。 6.6 会逐字节讲它的 8 字节首部,这一节先讲「为什么这么短」。

一、UDP 只做两件事

第一件是「加端口」:让接收方知道该交给哪个程序。 第二件是「算校验和」:让接收方能发现「这个包坏了」。 就这两件,没有第三件。 所以它没有「连接」这个概念:没有握手、没有状态、没有关闭。 发一个包就是发一个包,发完就当没发生过。 这也是为什么「UDP 的 socket 不需要 connect」—— 虽然你可以调 connect,但那只是「记住默认目的地址」,不是真建连接。

二、它换来了什么

省掉的东西换来三样好处:

省掉什么 换来什么 谁受益
握手 发第一个包不用等往返 DNS、游戏
重传和排序 不会队头阻塞 语音、视频
连接状态 服务器能扛更多客户端 广播、多播

第一行在「一问一答」的场景里价值最大: DNS 一次查询如果走 TCP,要先握手一个往返,再传数据一个往返,直接翻倍。 而走 UDP 就是一个往返搞定——这就是「为什么 DNS 默认走 UDP」的算术。 第三行常被忽略:TCP 服务器要为每条连接维护状态(序号、窗口、定时器), 一万条连接就是一万份状态。 而 UDP 服务器不用为「客户端」存任何东西, 所以「同一个进程扛十万个客户端」在 UDP 上是可行的。

三、代价:应用要自己补四样

缺的四样,用的人自己补:

缺什么 后果 应用怎么补
确认 丢了不知道 超时重问
重传 丢了不会补 自己重发
排序 到了可能乱序 带序号自己排
去重 可能收到两份 带序号自己丢

四行都是「应用自己」: 这就是「UDP 简单」的真实代价——协议省事,但每个用它的程序都要把这套东西再写一遍。 DNS 的做法最简单:超时没回就再问一次,问三次还没回就报错。 而视频会议要做的是「补一帧新的,而不是补那一帧旧的」——同样是「补」,策略完全不同。

四、为什么它反而越来越重要

因为「改传输层」太难,而「在 UDP 上重做一遍」很容易。 TCP 实现在操作系统内核里,想改一个参数要等系统升级,想让所有人用上要等好几年。 而 UDP 只是一个「裸的收发通道」,在上面想实现什么都可以,而且「随应用一起发布」。 QUIC 就是这么来的:它在 UDP 上实现了「可靠、按序、拥塞控制、加密」, 还顺手解决了 TCP 的「队头阻塞」(一条流卡住不影响别的流)。 所以现在说「UDP 不可靠」已经不太准确了—— UDP 只是「不替你保证」,不代表「用它的应用不可靠」。

五、动手 Demo:UDP 到底「不可靠」在哪

本机回环发三个 UDP 包,看能不能收到。 这个实验的结论可能和你想的相反。

import socket

a = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
a.bind(('127.0.0.1', 0))
b = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
b.bind(('127.0.0.1', 0))
a.settimeout(1)
for i in range(3):
    b.sendto(b'pkt%d' % i, a.getsockname())
    print(a.recvfrom(100)[0])

实跑输出:

--------------------------------------------------------------------
UDP 到底「不可靠」在哪
--------------------------------------------------------------------
**UDP 只做两件事:加个端口、算个校验和**,**剩下的全交给应用。**

**本机回环发了 3 个包,收到 3 个**:pkt0 pkt1 pkt2

**这个实验说明不了「UDP 可靠」**:**回环接口不经过任何真实链路,不会丢、不会乱序、不会重复。**
**所以「UDP 会丢包」这件事,在本机上是测不出来的**——
**要测必须在真实网络上跑,而且要有足够的样本量。**

**UDP 真正缺的四样东西**:
  缺什么       后果                 谁来补
  -------------------------------------------------------
  **确认**     **丢了不知道**       **应用自己超时重问**
  **重传**     **丢了不会补**       **应用自己重发**
  **排序**     **到了可能乱序**     **应用自己按序号排**
  **去重**     **可能收到两份**     **应用自己按序号丢**

**四行都是「应用自己」**:
**这就是「UDP 简单」的代价**——**协议本身省事,但每个用它的应用都要把这几件事重新写一遍。**
**DNS 的做法最简单**:**超时没回就再问一次,问三次还没回就报错。**

三个包一个不少、顺序也对,但这完全不能说明「UDP 可靠」:回环接口不经过任何真实链路, 不会丢、不会乱序、不会重复。 所以「UDP 会丢包」这件事,在本机上是测不出来的—— 要测必须在真实网络上跑,而且要有足够的样本量。 这也是「本地测试通过、上线就出问题」的典型来源:本机测不出网络层的任何异常。 所以涉及丢包、乱序、延迟的功能,必须有「在真实网络上测」这一环, 或者用「人为注入丢包」的工具模拟。 最后提醒一个容易忽略的点: UDP 的「报文边界」是有上限的—— 一个 UDP 报文最大 65507 字节(65535 减 IP 头 20 减 UDP 头 8),超过就得应用自己切。 而「发一个 100 KB 的 UDP 包」在很多系统上会直接报错,因为 IP 层没法把它塞进一个包里。

30 秒实验

  1. 想一下:为什么本机测不出 UDP 丢包?
  2. 算一下:一个 UDP 报文最大能装多少字节的应用数据?
  3. 想一下:QUIC 为什么选 UDP 而不是「改 TCP」?

常见误区

  1. 以为「UDP 一定丢包」。 它只是不保证,链路好的时候一样不丢。
  2. 以为「UDP 不能传大数据」。 能传,但一个报文有 65507 字节上限,更大要自己切。
  3. 以为「UDP 上不能做可靠传输」。 能,QUIC 就在 UDP 上做了全套。
自测三问(点开对答案) 1. UDP 只做哪两件事? —— **加端口号(分用)和算校验和(发现损坏)**,别的都不管。 2. UDP 省掉连接换来什么好处? —— **发第一个包不用等握手、没有队头阻塞、服务器不用为每个客户端存状态。** 3. 一个 UDP 报文最多装多少数据? —— **65507 字节**:65535 减 IP 头 20 减 UDP 头 8。

下一节进入 TCP 的细节,先看它凭什么算「可靠」。

相关笔记