开头先讲个很多人初学网络时都会有的困惑:我们的文件有自己的 IP 地址,网卡也能识别"对方在网络里的位置",那数据送到对方主机,这不就够了吗?为什么还要在 IP 之上再挖一层"传输层"?

答案藏在一个很具体的问题里:一台服务器上可能同时跑着 Web 服务、SSH 服务、数据库服务、邮件服务——它们的 IP 地址是同一个。数据到达这台主机后,内核凭什么知道"这个包是给 Web 服务的,那个包是给 SSH 的"?正是传输层解决了这件"把数据精确交到某个应用手上"的最后一公里。而今天我们重点讲的 UDP,就是传输层里那个"身轻如燕、快人一步"的激进派选手。

这篇博客是"Linux 网络编程"系列中关于传输层 UDP 的一篇。我们会从传输层的职责讲起,重新认识端口号,然后一头扎进 UDP 的报文段格式、校验和、三大特点,接着回答"一个 UDP 数据报到底能装多大"这个经典问题,把它和 TCP 放到一张桌上比个高下,最后落到谁能用、哪里该用,用直播、游戏、DNS 三个真实场景把每个知识点串起来。文末我为每个知识点都配了带详解答案的思考题,建议一口气读完再动手自测。

传输层:数据搬运的"最后一公里"

在 TCP/IP 四层模型中,自上而下是应用层、传输层、网络层、网络接口层(也有说法叫数据链路层)。网络层(IP 协议所在的层)负责的是一件事:把数据包从源主机一路路由到目的主机。它关心的是"包怎么走、往哪个 IP 送",但完全不关心"这个包到了之后,该交给哪个应用程序"。

这就是传输层的舞台。传输层负责的是端到端(进程到进程)的数据交付——它站在两个通信的应用程序之间,负责把数据从发送端进程顺利地送到接收端进程。可以这样类比:网络层像邮政干线,负责把包裹从一个城市运到另一个城市;传输层则像是最后那个分拣员,负责把包裹投递到城市里那栋大楼的某个具体房间,也就是某个具体的应用。

为什么必须有传输层这一层?因为一台主机上同时在跑的进程太多了。如果我们只能把数据送到"某台主机",那作用相当于"把信扔到小区门口",完全没法区分这封信该给谁。传输层的价值正在于此:它给进程编了号,用"端口号"这个门牌号,让数据能被精确地投递给目标进程,同时也能指明数据是从哪个进程发出来的,方便对方回信。

当前主流的传输层协议有两个:TCP(Transmission Control Protocol,传输控制协议)和 UDP(User Datagram Protocol,用户数据报协议)。今天的主题是 UDP。为了讲清楚 UDP,我们得先把"端口号"这个地基打牢——因为无连接、面向数据报的 UDP,连通信时最依赖的也就是"IP + 端口号"这一套定位信息。

端口号:给主机上的应用排个号

我们刚刚说了,传输层要靠端口号来定位应用。那么端口号(Port)到底是什么?一句话:端口号是一个 16 位的无符号整数,用于标识主机上参与通信的某一个应用程序。

端口号的取值区间是 0 到 65535,一共 65536 个。这个数量对一台主机来说绰绰有余——你想想,正常一台服务器上同时开着的服务,几十个算多了,65536 个端口完全够用。

在 TCP/IP 协议体系中,要唯一标识"一段通信",需要的其实是一组信息。这组信息叫五元组(five-tuple):

  • 源 IP 地址:数据从哪台主机发出;
  • 源端口号:数据从主机上哪个进程发出;
  • 目的 IP 地址:数据要送到哪台主机;
  • 目的端口号:数据要交给主机上哪个进程;
  • 协议号:用哪个传输层协议(TCP 是 6,UDP 是 17)。

只有这五个信息凑齐了,通信双方才能精确地"对上线"。换句话讲,光有 IP、没有端口,或者光有端口、不知道对方 IP,都不足以定位一次具体的通信。你可以用 netstat -n 命令在 Linux 上看当前主机的连接,它打印出来的每一行,本质上就是一组五元组信息。

端口号的范围划分

按使用场景,端口号被分成了两段:

  • 0 ~ 1023:知名端口号(Well-Known Port)。 这一段留给被广泛应用、已经约定俗成的应用层协议。比如 HTTP、FTP、SSH 这些老牌协议,它们的端口号是固定的,谁都不能乱抢。为什么要固定?因为客户端默认就要连这些固定的端口,如果你把 HTTP 服务的端口随便改掉,那全世界的浏览器都不知道该连哪了。
  • 1024 ~ 65535:操作系统动态分配的端口。 这一段的端口是"自由身"。客户端程序的端口号,通常就是操作系统从这段范围里临时挑一个分配出去的。当你的浏览器去连服务器的 80 端口时,浏览器自己会随机挑一个 1024 以上的端口作为"源端口",这样对方回包时才有地方送回来。

这里有个容易忽略但有价值的点:同一个端口号,在 TCP 和 UDP 里是两个完全独立的资源。也就是说,TCP 的 53 端口和 UDP 的 53 端口是两回事,互不抢占。这是后文一个经典面试题的地基,我们先按下不表。

知名端口号:一张约定俗成的门牌表

为了解决"客户端该连哪"的问题,人们把一些极其常用的服务器端口固定了下来。下面是几个必须刻进脑子里的:

服务默认端口作用
SSH(安全外壳协议)22远程登录 Linux 主机的安全通道
FTP(文件传输协议)21传统文件上传下载(控制连接)
Telnet23早期的远程终端协议(明文,不安全)
HTTP(超文本传输协议)80普通网页访问
HTTPS(安全超文本传输协议)443加密网页访问

你可以在 Linux 上执行 cat /etc/services(或按翻页键 cat /etc/services | more)看到一张系统自带的端口服务对照表。它只是"约定",不是"强制"——技术上你完全可以强行让服务监听别的端口,但那样客户端就得显式指定,很不方便,所以实际工程里都遵循约定。

还有一条工程铁律:我们自己写程序选端口时,要主动避开这些知名端口号。一方面避免和你自己机器上的既有服务冲突,一方面也为了尊重行业约定。一般开发调试程序,选一个 1024 以上的生僻端口(比如 8000、9000、23333 之类)是稳妥的习惯。

讲端口号时绕不开的两个经典问题

讲到这里,端口号的两个"灵魂拷问"就浮出水面了,这两个问题几乎是网络编程面试里的常客:

  1. 一个进程是否可以 bind(绑定)多个端口号?
  2. 一个端口号是否可以被多个进程 bind?

先别急着看答案,你可以自己在脑中推断一下:进程和端口到底是什么样的对应关系?是"一对一"还是"一对多"?是"一主"还是"多主"?

我先给出结论,详解放到后面"思考题"里,因为这两个问题的答案需要结合 socket 的机制来讲,现在你只需要带着问题往下读——读完 UDP 的报文结构和缓冲区之后,再回头看这两个答案,你会理解得更透。简单说:第一个问题答案"可以",第二个问题答案"默认不行,但有开关可以让它行"。

UDP:用户数据报协议

铺垫了这么多,主角终于登场。UDP(User Datagram Protocol,用户数据报协议)是一种无连接、不可靠、面向数据报的传输层协议。这一句话几乎概括了它的全部性格,我们接下来逐字拆解。

在展开之前,先给我们反复要用到的三个词立好定义,因为它们是 UDP 区别于 TCP 的根基:

  • 无连接:通信双方在发送数据之前不需要先建立连接。只要发送方知道对方的 IP 和端口号,就可以直接把数据扔出去,接收方也不承诺"我一定准备好了才收"。
  • 不可靠:UDP 本身不做任何保证送达的努力——没有确认机制(收到后不通知对方)、没有重传机制(丢了不补发)、没有差错重排(乱序了不管)。数据丢没丢、顺不顺序,UDP 一概不负责。
  • 面向数据报(Datagram):UDP 以"整个报文"为传输单位,应用层一次交给 UDP 多长的数据,UDP 就原样发送多大的一整块,发送端不会拆分,接收端也不会合并。每个数据报都是独立、完整的。

这里我想让你先建立一个整体的直觉:UDP 的传输过程,像极了寄平信。你知道对方在哪儿(IP + 端口),就把信投进邮筒,剩下的就交给命运了。邮局不会打电话告诉你"信到了没",信可能在半路丢了,可能顺序乱了(你先寄出 B 再寄出 A,对方收到的可能是 A 再 B),邮局也绝不会因为没送到就退给你另寄一遍。它唯一的好处是:省事、快、过程简单到近乎没有。

这句话听起来有点丧气——"那我干嘛要用这么不靠谱的协议?"别急,等我们讲完报文格式、校验和,再把它和 TCP 一比,你会明白"不靠谱"恰恰是它在某些场景下的巨大优势。

UDP 报文段格式:短短 8 字节的首部

数据在传输层会被封装成"报文段"(segment)的形式。UDP 的报文段分两部分:8 字节的固定首部(header)+ 数据部分(payload)。它是整个 TCP/IP 体系里首部最短的传输层协议之一,短小到让人怀疑它是不是忘穿了衣服:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          源端口号 Source Port      |       目的端口号 Dest Port   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|            长度 Length            |         校验和 Checksum      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                                |
|                        数据 Data(可变长度)                     |
|                                                                |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

四个字段各占 2 字节,正好 8 字节。逐一拆解:

  • 源端口号(16 位、2 字节):发送方进程的端口号,供对方回信用。在不需要回应的场景(比如纯单向的广播),它可以被置为 0。
  • 目的端口号(16 位、2 字节):接收方进程的端口号。这是必填字段,否则对方内核不知道该把数据交给哪个应用。
  • 长度(16 位、2 字节):整个 UDP 数据报(首部 + 数据)的字节数。最小值为 8(也就是首部本身,没有携带任何数据时),理论最大值是 65535。这个字段决定了"一次最多装多少数据",我们下一节专门讲它。
  • 校验和(16 位、2 字节):用于检测数据在传输过程中是否被破坏。如果接收方算出来的校验和对不上,这个数据报会被直接丢弃。我们在讲解 UDP 的"可靠性"时会展开。

一个非常重要、贯穿始终的细节是:UDP 首部里没有序号、没有确认号、没有窗口、没有标志位。这些字段之所以不存在,正是因为它不需要——它不排序、不确认、不控速。一个如此"缺斤少两"的首部,换来的是极低的开销和极高的处理速度。对比一下,TCP 的最小首部是 20 字节,UDP 只有它的不到一半。

校验和:UDP 为数不多的"可靠性"

你可能觉得奇怪:UDP 不是"不可靠"吗?怎么还有校验和?

这正是很多人的认知误区。"不可靠"不等于"完全不校验"。UDP 虽然不做端到端的可靠交付(确认、重传),但它提供了最基础的差错检测能力,靠的就是这个 8 字节首部里的校验和(Checksum)字段。

所谓校验和,是一段加在报文后面的检测值,接收方用同一套算法对收到的数据重新计算,如果结果和收到的校验和不一致,就说明数据在传输途中被改动了。这是网络协议里最常见的防错手段。

UDP 的校验和有一点和其他协议不同:它的计算范围不只是 UDP 首部和数据,还覆盖一个"伪首部"(Pseudo Header)。这个伪首部并不是真实传输的字段,它只在校验和计算时临时拼装出来,算完就丢弃,其内容取自 IP 首部:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        源 IP 地址(32位)                       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        目的 IP 地址(32位)                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  0(8位)    |   协议号(17)    |          UDP 长度(16位)        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

伪首部里包含了源 IP 地址、目的 IP 地址、一个 8 位的填充零、协议号(UDP 是 17)、UDP 长度。为什么要"多管闲事"地把 IP 地址也拉进来一起算校验和?

因为这样能检测出"数据被投错地方"的情况。举个例子:如果数据在传输途中被某个路由器错误地转发到了错误的 IP,或者出了错,光校验 UDP 数据本身是发现不了的——接收方算 UDP 数据部分,结果依然是合法的。而把源/目的 IP 一起卷进校验和,一旦 IP 错了,校验和就对不上,接收方就能判断"这不是给我的东西",直接丢弃。这是用一点计算开销换取"数据既能防内容损坏、又能防投错位置"的双保险。

校验和的完整性,还保证了端到端的语义:伪首部把传输层和网络层的信息缝合在了一起,这样即便网络层(IP)自身只对 IP 首部做校验侦测(IPv4 的 IP 层对数据部分不做校验),传输层也能兜底把关数据内容。

补充两个工程上容易踩的细节,都是我在查证时确认过的(来源:RFC 768 与主流资料整理):

  • 在 IPv4 下,UDP 校验和是"可选"的。当发送端把校验和字段设为全 0 时,表示"本次不计算校验和",接收方会跳过校验。但这非常不推荐——任何一份正经的业务代码都不该省这个校验和。
  • 在 IPv6 下,UDP 校验和是"强制"的,不能置 0。这是 IPv6 协议设计时做出的硬性要求,因为 IPv6 移除了 IPv4 首部中那点可怜的校验侦测,必须由上层补上。

如果接收方计算校验和发现不匹配,UDP 会怎么做?——直接悄悄丢弃。注意是"悄悄":它不会重传,也不会向应用层返回任何错误信息。发送端那边同样一无所知,还以为对方已经收到了。这正是"不可靠"最扎心的一面的缩影:错误是静默发生的。

UDP 的三大特点:无连接

我们把校验和"不可靠但能自检"这本书合上,正式进入 UDP 性格最核心的三章。

无连接是整个 UDP 最大的性格标签。它的意思是:UDP 的两个端点之间从来不会建立一个"连接"。

对比之下,TCP 的通信分为明显的三步:先打招呼"我要和你通信"(三次握手建立连接),再正式传数据,最后告别"我传完了"(四次挥手断开连接)。这套流程保证了通信双方在传数据之前,彼此都知道对方还在、愿意谈、状态就绪。

而 UDP 完全没有这套仪式感。只要发送端知道目的端的 IP 地址和端口号,不管对方开机没有、在不在线、愿不愿意收,它立刻就把数据发出去。 一个典型的例子:当你用命令向一个根本不存在的主机发一条 UDP 报文,发送方是发不报错的——它只负责把数据交给内核、扔给网络,之后的事一概不问,也一概不知。

这么说有帮助吗?有,而且巨大。正因为不需要建立连接,UDP 省掉了三次握手、四次挥手这种来回确认的开销,这是 UDP 极其重要的一个优势:连接延迟几乎为零,且没有连接状态需要维护。对实时性要求高的场景(比如语音、视频、游戏操作指令),哪怕多花一个 RTT(网络往返时延)在"握手"上,都可能让体验断崖式下降。UDP 直接把这一步砍掉了,拿到数据就能发。

UDP 的三大特点:不可靠

第二个特点是不可靠。这指的是 UDP 不提供可靠传输的服务保证。具体拆开看,它"不可靠"在这么几个层面:

  1. 没有确认机制:接收方收到 UDP 数据后,不会回送一个"我已经收到了"的确认(ACK)。所以发送方根本无法判断数据到底到了没有。
  2. 没有重传机制:正因为不知道有没有收到,所以丢失的报文也不会被重新发送。如果因为网络拥塞或链路故障,一个报文段没能到达对方,UDP 协议层不会做任何补救。
  3. 不通知应用层:这一点最有欺骗性——即使数据真的发丢了,UDP 也不会给应用层返回任何错误信息。应用层调用 sendto 把数据交出去,UDP 就认为"送出了",哪怕这条数据在路由器上被丢弃、在半路上蒸发,应用层也浑然不觉。它不会像文件 IO 那样报"写入失败"。
  4. 不保证顺序:数据报发出的顺序和到达的顺序可能不一致,UDP 不做排序。先发的可能后到,后发的可能先到。

那"不可靠"是不是等于"没法用"?不是。这句话的真正含义是:UDP 把"可靠"的活,甩给了上面的应用层自己干。如果你需要可靠性,那是你应用层的事——你自己实现"确认—超时—重传",自己维护"序号—排序",自己处理"去重"。UDP 只提供一条最低成本的传输通道,要把这条通道变得可靠,全靠你自己在通道上面盖房子。这也是"面向数据报、无连接"这些设计意图的最终指向:它追求的是最小,把选择权完全交给应用层和开发者。

UDP 的三大特点:面向数据报与数据报边界

第三个特点面向数据报,相比前两个更隐蔽,也更常被人遗忘,但它恰恰是 UDP 程序里最容易写出 bug 的地方。

面向数据报的意思是:应用层一次交给 UDP 多长的报文,UDP 就把它当成一个完整、独立的整体原样发送,发送端既不会拆分,接收端也不会合并。你写的数据,和对方收到的数据,是"一击一收"的完整对应关系。

为了把它讲明白,我们要引出**数据报边界(Datagram Boundary)**这个概念。它说的是:UDP 的消息是一条一条的,每条消息有明确的起止边界,一条消息就是一个 data报,谁也不能把它拆得七零八落,也不能把两条合并成一条。

看一个最经典的例子。假设我们想用 UDP 传输 100 个字节的数据:

  • 发送端调用了 1 次 sendto,一次性发送这 100 个字节;
  • 那么接收端也必须调用 1 次 recvfrom,一次性接走这 100 个字节;
  • 你绝对不能在接收端循环调用 10 次 recvfrom,妄想每次只收 10 个字节——因为 UDP 不会把一条 100 字节的数据报拆成 10 块给你。你 1 次 recvfrom 要么整条收走这 100 字节,要么一次只读走其中一小部分,剩下的就被丢弃了(大多数实现的默认行为是:缓冲区装不下的部分直接丢掉)。

这正是"面向数据报"和"面向字节流"的根本区别。反过来,TCP 就是反面教材——TCP 是面向字节流的,它没有消息边界,应用层写 100 字节,对方可能会收到 1 条 100 字节,也可能收到 2 条 50 字节,全看网络拥塞和 TCP 自己的分段策略,所以 TCP 在应用层常要为"粘包/拆包"费尽心机。而 UDP 天生不存在这个问题——一条 sendto 就是一条,边界由协议自己保证,应用层处理起来反而省心。

数据报边界的坑,藏在"发送次数"与"接收次数"的错位上

这个边界的坑,在实际编码里以另一种形态频繁出现。UDP 的沙盒特点是:发送端调用多少次 sendto,接收端就要调用多少次 recvfrom(在都不丢包、都能接下的前提下次次对应)。

比如发送端循环发了 10 条数据报,接收端就必须循环收 10 次 recvfrom 才能把它们都接完。如果你只调了一次 recvfrom,那只能拿到第一条数据报,剩下 9 条还躺在接收缓冲区里——当然,它们不会因此就消失了,而是等在你下次 recvfrom 时陆续出来,但"一次对应一条"的中枢规律始终不变。

还有一处经常让新手栽跟头:recvfrom 给的缓冲区大小,必须 >= 数据报的真实大小。如果对方发来一条 200 字节的数据报,而你接收缓冲区只开了 100 字节,那么这 100 字节会被取走,剩下的 100 字节会被直接丢弃,并且你很难立即察觉。这是 UDP 面向数据报一个最隐蔽、也最经典的失分点——日常收到"我 UDP 收到的数据怎么老被截断",十有八九就是这个原因。经验上,接收缓冲区要按你能接受的最大数据报去开,宁可大,不可小。

UDP 的缓冲区与全双工

聊完报文和边界,我们把镜头拉到一个更宏观的视角:UDP 在内核里的缓冲机制,以及 socket 的读写能力。

UDP 的发送缓冲区:几乎没有

一个反常识的结论是:UDP 没有真正意义上的发送缓冲区。当你调用 sendto 发送数据时,UDP 不会先把数据攒在一个应用层可见的缓冲区里"择机发送",而是直接把数据交给内核,由内核立即转交给网络层协议,进行后续的传输动作。

这意味着两件事:

  • 数据从你的应用程序出来,几乎是"即时"地被打包送走,不会在发送端排队积压。它强调的正是低延迟。
  • 正因为没有发送缓冲来消化流量,你再发得快、发得多,UDP 也顶不住"比它还快"的洪峰——因为它没有缓冲区替你缓存。这也是为什么 UDP 面对大量突发的数据时,丢包率会裸奔式地上升。

UDP 的接收缓冲区:有,但有三个限制

接收方向则完全是另一幅景象:UDP 具有接收缓冲区。对方发来的数据报先进入这个缓冲区排队,应用层再通过 recvfrom 一条条取走。这是内核帮你做的一次缓存和缓冲。

但这个接收缓冲区有三个硬伤,你必须记牢:

  1. 不保证顺序:接收缓冲区不能保证"收到的 UDP 数据报顺序"与"发送端的发送顺序"一致。乱序是 UDP 的常态,缓冲区只是暂存,不做排序。
  2. 满了就丢:如果缓冲区已满,再有新的 UDP 数据到达,这个新到的数据报就会被直接丢弃。注意,丢的是"新到的",缓冲区内已排队的旧数据还在,但新来的进不来。而且这种丢弃是静默无声的,双方都难以及时得知。
  3. 容量有限:接收缓冲区的默认大小是有限的,超额的数据报没有任何"扩容兜底",说丢就丢。要让程序健壮,就得在应用层合理安排接收速率,或者调大内核缓冲区(通过 socket 选项,比如 SO_RCVBUF)。

这三条合起来,本质上还是在讲同一个问题:UDP 说到底是把"可靠性"完全还给了应用层。接收快不过来,别指望 UDP 帮你稳住,是你的事。

全双工

还有一个 UDP socket 的固有属性值得单独一提:UDP 的 socket 既能读,也能写。一个 socket 既可以用来 sendto 发送数据,也可以用来 recvfrom 接收数据。这个"既能收、又能发"的能力,叫做全双工(Full-Duplex)。

为什么要强调它?因为在做 UDP 通信(尤其是 C/S 架构)时,我们往往只用同一个 socket 文件描述符,既 serve 请求、又发响应。理解了全双工,你就明白:服务端并不需要为"收发"各开一个 socket,一个就够。同一个端口既能收客户端的请求,也能往客户端地址回发响应。

一个 UDP 数据报最多能装多少数据:64K 与 65507

前面的"长度"字段我们只说了"16 位、理论最大 65535",现在是时候把"一个 UDP 数据报最大能装下多少数据"这个经典问题也算清楚、算准了。

先看"传说":很多教材会告诉你,"UDP 能传的数据最大长度是 64K(即 64KB,包含 UDP 首部)"。这句话在教材语境下大致成立——因为 UDP 首部里的"长度"字段是 16 位,理论最大值就是 2^16 − 1 = 65535,也就是 64KB 出头一点点。

但严谨地讲,UDP 能装下的"应用层数据"实际最大值,在 IPv4 下是 65507 字节。这个数字不是拍脑袋,而是一条精确的推导链,我把每一步都列出来,这是一道标准结论,也经过我核对,完全可以放心记:

IPv4 数据包总长度的上限(IP 首部的总长字段是 16 位) = 65535 字节
减去 IP 首部(通常 20 字节,不含可选字段)              - 20
= IP 里能塞下的最大 UDP 数据报(含 UDP 首部)             65515 字节
再减去 UDP 首部(固定 8 字节)                          - 8
= UDP 能装下的最大应用层数据                            65507 字节

所以:65507 = 65535(IP 总长上限)− 20(IP 首部)− 8(UDP 首部)。把这条公式背下来,比死记一个孤零零的数字靠谱得多,因为考试和面试考的都是它的推导逻辑。

这里要特别提醒一个容易说错的点:65507 是 IPv4 数据报套 IPv6 下是不同的。IPv6 的载荷长度字段最大也是 65535,但 IPv6 首部是固定的 40 字节,所以 IPv6 下 UDP 数据的最大值是 65535 − 8(UDP 首部)= 65527 字节——注意 IPv6 这里不需要再减 IP 首部,因为 65535 算的就是"IP 载荷"的容量,UDP 数据报(含头部)恰好就是 IP 载荷。这两个数(IPv4 下的 65507 和 IPv6 下的 65527)极易混淆,务必分清。

数据太大怎么办:应用层手动分包

那么问题来了:64K(严格说是 65507 字节)这个额度,在今天这个动不动就要传几百 MB、几 GB 文件的时代,简直是杯水车薪。如果有人想通过一条 UDP 数据报传输一个几百 MB 的文件,门儿都没有。

更现实的问题是:即使你能塞进 65507 字节,在真实网络上 99% 也送不出去。因为网络链路有 MTU(最大传输单元)限制——最常见以太网的 MTU 是 1500 字节,也就是说一个 UDP 数据报只要超过约 1472 字节(1500 − 20 IP 首部 − 8 UDP 首部),在网络层就可能被IP 分片拆成好几片。而 IP 分片是 UDP 的噩梦:分出的多片中只要有一片丢了,整个 UDP 数据报就被整条丢弃,重传还得应用层自己来。因此 UDP 实战中的铁律是:保持数据报小于 MTU,通常把 UDP 载荷控制在 1400 字节上下,避免触发 IP 分片。

所以,"传输超过 64K 的大数据"这件事,UDP 自己无能为力,只能靠应用层手动分包:发送端把大文件拆成多个大小合适的数据报,多次 sendto 发出去;接收端再多次 recvfrom 接收,并在应用层付出额外的心血来手动拼装成完整的数据。缺点立现:分包、编号、排序、校验完整性、处理丢包后的重传……这一切本该由可靠的传输层协议替你包办的脏活累活,现在全得你亲自写。这也是为什么大文件的可靠传输几乎永远属于 TCP 的领地,而不是 UDP。

UDP 与 TCP:一对性格迥异的兄弟

UDP 讲了这么多,如果不把它放进与 TCP 的对比里,你很难真正掂量出它的分量。这两个都是传输层的协议,一个求"稳",一个求"快",把它们的差异摆在一张表上一目了然:

对比维度TCPUDP
连接性面向连接,先握手后传无连接,直接发
可靠性可靠:需确认、重传、排序不可靠:不确认、不重传、不排序
传输单位面向字节流,无消息边界面向数据报,有明确消息边界
首部开销20~60 字节固定 8 字节
传输效率较低(要维护大量状态)较高(轻量、免状态)
拥塞控制有(会主动放缓发速)无(发多快是多快)
广播/组播不支持支持
是否通知应用层丢包会(超时重传、报错)不会(静默丢弃、静默丢失)
典型应用文件传输、网页浏览、电子邮件直播、游戏、DNS、语音视频

从这张表能读出两件事。一是开销:TCP 首部最小 20 字节、最大 60 字节,UDP 固定 8 字节,差距一目了然;二是态度:TCP 把"可靠"当作自己的天职包到底,UDP 把"可靠"明确地推给你的应用层。没有谁绝对更好,只有"谁更合适"。

UDP 不做拥塞控制,意味着什么

这张表里有一项特别值得单独展开,那就是拥塞控制(Congestion Control)——因为这个差异在真实业务里产生的后果,往往超出新手的想象。

拥塞控制是传输层为了防止"把网络堵死"而做的一种自我调节:TCP 会监视网络的拥塞程度,一旦发现网络繁忙、丢包增多,就会主动放慢自己的发送速度,等网络缓过来再逐步提速。这是 TCP 一种"利己也利他"的克制,也是整个互联网能稳定运行的重要基石,因为它保证了不会出现"所有主机同时狂发,把网络塞爆"的悲剧。

而 UDP 完全不做拥塞控制。它意味着:

  • UDP 不会因为"网络上已经很堵了"就主动降速。你发多快,它就往前线送多快,打得越多、丢得越多,UDP 毫不在意。
  • 如果一个用 UDP 的应用不加克制地猛发数据,它足以把网络带宽挤占殆尽,甚至"饿死"同一链路上守规矩的 TCP 流量。这也是为什么很多公网环境会对 UDP 做 QoS 限制(降低优先级甚至限流)的原因——防止 UDP 流量失控波及整个网络。
  • 但反过来,"不做拥塞控制"恰恰是直播、游戏这类实时应用梦寐以求的。因为它们要的正是"立即发送"而不是"等网络同意再发"。我们很快会展开这一点。

为什么直播、游戏、DNS 偏偏要用 UDP

前面铺垫了那么多"UDP 不可靠"的"缺点",现在轮到它扬眉吐气了。UDP 的不可靠,在实时性优先的场景里,恰恰是最需要的品质。我们用三个渗透到日常生活的场景,把每个知识点串成线。

场景一:直播与音视频通话

看直播、打视频电话时,你用的是 UDP(或其衍生协议,如 RTMP 的传输、WebRTC)。

为什么?因为音视频是一个"实时 > 完整"的场景。想象一下卡顿的直播最难受的是什么?不是画面不完美,而是"等"——一帧画面卡住了,你等 2 秒才放出来,延迟感拉满。视频数据有极强的"时效性":旧的一帧错过了,重传它是没有意义的——反正下一帧马上就到了,没有人想看 3 秒前的画面重新播一遍。

这正是 TCP 的死穴。TCP 为了保证可靠,一旦丢包就会重传,重传会造成排队和延迟堆积,这在音视频里表现为"越卡越延迟、越延迟越卡"的恶性循环。而 UDP 丢了就丢,直接用下一帧顶上,画面可能偶尔花一下、糊一下,但全程流畅、延迟极低。对观众而言,一帧画面的短暂损伤,远比 2 秒的等待更能接受。用一句话概括:直播宁可"丢帧也不重传",UDP 完美契合这种取舍。

场景二:在线游戏

游戏对实时性的要求更极端。你在游戏里按一个技能键,操作指令必须立刻送到服务器,服务器的响应也要最快传回来,哪怕晚 10 毫秒,玩家都能"卡顿感"拉满。

在游戏里,少传一个高频操作指令,比"迟到的准确指令"好得多。如果你用 TCP,网络稍有抖动触发重传,操作指令就会延迟,伴随"延迟补偿"导致的瞬移、穿墙,体验雪崩。而 UDP 直接以最快的速度把指令打出去,丢了这一次,用下一次补上——反应快永远是第一位的。绝大多数在线游戏(尤其是竞技类和 MOBA)的服务端都把 UDP 作为核心传输协议,正是因为要保住这条"实时抢先"的生命线。

顺带一提,为了在两全其美,业界走出了一条折中路线:在 UDP 之上模拟 TCP 的那套可靠机制,最典型的就是 QUIC(也就是 HTTP/3 的底层协议)。它保留 UDP 的低延迟、低开销,又自己实现连接建立、重传、拥塞控制……这恰恰印证了我们前面那句话:"把可靠自己做在 UDP 上面",正是 UDP 交给应用层(乃至协议栈)的自由。你嫌 UDP 裸奔不可靠?没问题,你在它上面盖一座"可靠的楼"就行。

场景三:DNS 域名解析

DNS 是每个人上网都会碰、却又最容易被忽略的 UDP 用户。当你输入网址回车,浏览器第一件事就是发起 DNS 查询,把域名换成 IP。

DNS 查询用 UDP 的原理再简单不过:查询本身就是一问一答,天然就是一条数据报。客户端发一个小的查询报文,服务器回来一个更小的响应报文——这种"一锤子买卖"的场景,用 TCP 先去握手反而是浪费(多一次 RTT 延迟)。UDP 直接一步到位:发请求、收响应,快且足够。只有在响应特别大(大到超过一个 UDP 数据报能装下)时,DNS 才会退而求其次切换到 TCP 来保证完整传输——这也从侧面印证了我们对 65507 和报文大小那节的理解。

小结:什么时候该选 UDP

我们把"选择逻辑"压缩成一句判断:当一个业务"延迟低"比"绝对可靠"更重要,也就是你能容忍丢弃、不能容忍等待时,选 UDP。反之,当必须"完整、按序、不丢"地送达,比如文件下载、网页加载、电子邮箱时,请让 TCP 来承担。判断标准不再于协议本身的"好坏",而在于"你的业务到底更怕延迟,还是更怕丢数据"。

基于 UDP 的应用层协议

理解了 UDP 的取舍,我们再回到课程核心,看看那些大家耳熟能详的应用层协议,其实都有一个 UDP 版本,或者干脆就建在 UDP 上。列举如下,每个都一句话交代它"为什么用 UDP/UDP 的强项在哪里":

协议全称/含义用 UDP 的原因
DNS域名解析协议一问一答、报文小、要求低延迟(超大数据才切 TCP)
DHCP动态主机配置协议主机刚开机还没 IP 时,需要个"轻量、能广播"的方式拿配置
TFTP简单文件传输协议极简的文件传输,简化到只做基本可靠性
BOOTP启动协议(用于无盘设备)无盘设备上电引导时快速获取启动映像,重实时轻开销
NFS网络文件系统早期版本基于 UDP,追求局域网内的高吞吐、低开销
SNMP简单网络管理协议网络设备状态上报,单条小报文即可,要求及时
QUIC / HTTP/3基于 UDP 的新一代可靠传输在 UDP 上自建可靠与拥塞控制,兼得低延迟与可靠性

当然,这一栏里还应该加上一项,也是最贴近你当前的一类:你自己编写 UDP 程序时自定义的应用层协议。当你决定在应用层需要"可靠但低延迟"时,你完全可以自己设计一套简单的确认/超时/重传协议,把它架在 UDP 之上——很多实时系统的私有传输协议正是这样诞生的。

边界与坑:实战中必须绕开的雷区

知识点讲完了,我们把 UDP 在实战里最容易踩的雷集中列出来。这些不是锦上添花,而是几乎每个 UDP 新手都会交学费的地方。

  1. 数据报边界:一击必一收,缓冲区要够大。 发送一次 sendto 对应接收一次 recvfrom,两者次数必须对应;且接收缓冲区大小必须 >= 数据报真实大小,否则超出的部分会被静默丢弃,数据莫名"被截断"。这几乎是 UDP 最常见的 bug 源头。

  2. UDP 不做拥塞控制。 UDP 不会因网络繁忙而自动降速,突发流量下丢包率直线上升。若应用层不加节制地狂发,可能挤占带宽、影响同网络的其他流量,公网还可能被 QoS 限流。高性能 UDP 应用必须在应用层自己做流量控制。

  3. UDP 丢包是静默的。 数据丢了、顺序乱了、缓冲区满了,UDP 一概不告诉你。所以但凡对可靠性有哪怕一点要求的 UDP 应用,都必须自己实现"序号 + 确认 + 超时重传 + 排序",不能指望协议层给任何反馈。

  4. 别发超过 MTU 的大包。 UDP 载荷建议控制在 1400 字节上下(以太网 MTU 1500 − IP 头 20 − UDP 头 8 附近),避免触发 IP 分片。IP 分片下任一片丢失都会导致整条数据报被丢弃。

  5. UDP 不做实时性之外的任何保证。 它不保证不重复(可能收到重复数据)、不保证顺序、不保证一定到达。把它当"尽力而为的信使",你就不会做出错误的可靠性假设。

  6. 自己写 UDP 服务时避开知名端口。 选一个 1024 以上、生僻的端口,别和 HTTP/FTP/DNS 等约定端口撞车。

一个 UDP 数据报最大能装多少:一个完整推导

这一节是前面"64K 与 65507"那部分的精确展开与加固,把最容易丢分的两步补齐。

很多资料上一句话"UDP 最大 65507"就完事了,但考试和面试真正检验的是你是否理解它为什么是两个 16 位字段在接力:

  • UDP 首部的"长度"字段是 16 位,它的作用是描述"整个 UDP 数据报"的长度。单看它,理论上限是 65535;
  • 但 UDP 数据报不是天上掉下来的,它是被装进一个 IP 数据包里传输的。而 IP 首部里也有一个 16 位的"总长度"字段,描述整棵 IP 包(含 IP 首部)的大小,上限也是 65535;
  • 于是真正的瓶颈在 IP 层这一环:一个 IP 数据报最多 65535 字节,其中 IP 首部就占了 20 字节,剩下的 65515 字节才是 UDP 的份额(整个 UDP 数据报);
  • 再从 65515 里减去 UDP 首部的 8 字节,就是留给应用层数据的最大空间:65507 字节。

如果换到 IPv6:IPv6 没有"总长度"字段,而是有"有效载荷长度"字段,它算的就是 IP 可以携带的上层(即整个 UDP 数据报)的字节数,最大 65535。减去 UDP 首部 8 字节,得到 65527——这就是 IPv6 下 UDP 数据的理论最大值。注意这个 65527 不需要二次减 IP 头,因为 IPv6 的载荷长度字段定义里就已经不含 IPv6 首部了。

思考题 · 自测与详解

前面我把各节埋下的问题汇总到这里。请先自己思考,再对照详解;每道题我都不只给答案,还给出了完整的推理过程。

思考题 1:一个进程是否可以 bind 多个端口号?一个端口号又是否可以被多个进程 bind?

答案:第一个可以;第二个默认不行,但通过 socket 选项可以。(这也是我们在"端口号"一节预留的两个经典题。)

详解(第一问): 进程与端口号并不是"一对一"的关系。一个进程完全可以创建多个 socket,每个 socket 绑定不同的端口号。比如说,一个服务器进程可以同时创建两个 socket,一个 bind 到 80 端口负责 HTTP,一个 bind 到 443 端口负责 HTTPS,这在服务器程序里是极其常见的写法。所以答案是"可以"。本质原因是端口号的归属粒度绑在"socket 文件描述符"上(而非"进程"上),一个进程手握多个 descriptor,自然就能占多个端口。

详解(第二问): 这题要分两层。

  • 默认情况下:不行。 当一个端口已经被某个 socket 绑定后,另一个进程再来 bind 同一个 IP + 端口,内核会返回 "Address already in use"(地址已被占用)。这是默认的保护行为,防止两个进程兴趣莫名地抢同一个服务端口。
  • 但协议是可以打开的:SO_REUSEPORT。 Linux 3.9 之后引入 SO_REUSEPORT 这个 socket 选项,它允许多个进程/线程绑定完全相同(相同 IP + 相同端口)的地址,由内核在你(同一用户)的多个 socket 之间做负载均衡——TCP 连接到来时内核挑一个 socket 交付,UDP 数据报到来时也是挑一个 socket 交付。这在 Nginx、Redis、DNS 服务器等多进程高并发模型里广泛使用。需要补充的两点:一是 SO_REUSEPORT 是 Linux 3.9+ 才有的能力(Windows、macOS 不支持或用别的方案);二是还有另一个更温和的 SO_REUSEADDR,它的用途主要不是"多进程抢同一端口",而是允许在端口处于 TIME_WAIT 状态时快速复用,方便服务重启。这两兄弟名字相近、作用不同,别记混。

思考题 2:TCP 和 UDP 能否使用同一个端口号?

答案:可以,而且很常见。 最经典的例子就是 DNS 服务器,同时占用 TCP 53 与 UDP 53:UDP 53 处理日常的小查询(快),TCP 53 处理超大响应和区域传输(可靠)。你可以用 netstat -tuln | grep 53 亲自验证——会同时列出 tcp 的 53 和 udp 的 53 两条监听。

详解: 原因回到我们讲端口号时的那个伏笔——TCP 和 UDP 各自拥有独立的 65535 个端口空间,是互不相干的两栋楼。协议号(TCP 是 6,UDP 是 17)本身就是五元组的一部分,内核正是靠"协议号 + IP + 端口"这套组合来唯一区分一条通信的。所以 TCP 的 80 端口和 UDP 的 80 端口在原理上是两个独立的资源,完全可以让一个 HTTP(TCP)服务和一个 UDP 服务同时占用数字相同的端口。

思考题 3:UDP 的"不可靠"具体体现在哪几个方面?既然不可靠,为什么还要加上校验和?

答案: "不可靠"体现在四个层面:无确认、无重传、丢包不通知应用层、不保证顺序;而"校验和"与"不可靠"并不矛盾,它提供的是最基础但独立的差错检测能力。

详解: 第一部分上面已展开(确认、重传、静默、乱序四件事都没有)。第二部分要认清:"可靠传输"(Reliability)和"差错检测"(Error Detection)是两个不同维度的问题。"可靠传输"关心的是"数据有没有完整、按序送达,没到就设法补齐"——这是 UDP 明确不做的;"差错检测"关心的是"这坨数据有没有在传输途中被改坏、被投错"——这是校验和的职责。UDP 虽然不做重传,但接收方仍然可以用校验和识别"这是一条坏掉的数据报",然后直接丢弃,避免把脏数据喂给应用层。所以校验和是"不可靠的 UDP 里为数不多的一点自保把戏"。

思考题 4:65507、65527、1472 这三个数分别是怎么算出来的?它们各代表什么?

答案: 65507 = IPv4 下 UDP 数据的理论最大值;65527 = IPv6 下 UDP 数据的理论最大值;1472 = 标准以太网上 UDP 载荷为避免 IP 分片的建议上限。

详解: 推导链前面已完整给出,这里把三步公式一次列清。

  • 65507:IPv4 下 UDP 数据的理论最大值。65507 = 65535 − 20 − 8,即 IP 总长上限(65535)减去 IP 首部(20 字节)再减去 UDP 首部(8 字节);
  • 65527:IPv6 下 UDP 数据的理论最大值。65527 = 65535 − 8,即 IPv6 有效载荷长度上限(65535,它本身不含 IPv6 首部 40 字节)减去 UDP 首部(8 字节);
  • 1472:标准以太网(MTU = 1500)上为避免 IP 分片的 UDP 载荷建议上限。1472 = 1500 − 20 − 8,即 MTU 减去 IP 首部(20 字节)再减去 UDP 首部(8 字节)。

前两个是"协议理论上限",第三个是"物理链路的现实上限"——一旦超过 1472 就容易触发 IP 分片,而 UDP 对 IP 分片极不友好(任一分片丢失则整条丢弃且无人重传)。所以 UDP 载荷的工程推荐值,是取"理论允许"与"链路现实"交集的保守一侧,也就是尽量压到 1400 字节上下。

思考题 5:为什么同样丢包,文件下载用 TCP、直播却用 UDP?

答案: 因为两者的核心诉求不同——文件下载要"完整",直播要"及时";而 UDP 恰恰放弃了完整、换来了及时。

详解: 文件下载是一个"不完整就不能用"的场景。一个文件缺一个字节就是损坏,就得重传,所以 TCP 重传带来的额外延迟是完全值得的——反正你在等一个完整的东西。而直播是"过时就没用"的场景。重传一个 3 秒前的视频帧没有任何意义,观众要的是此刻流畅的实时画面,宁可花一帧画面、也不要等上半秒。所以直播选择 UDP:丢了就丢,用下一帧顶上,牺牲片段完整、换取全程流畅。判断标准就是那句:你的业务更怕"丢",还是更怕"等"。

思考题 6:用 UDP 传一个 10 MB 的文件,应用层需要自己做什么?

答案: 需要自己做一整套可靠传输机制的"应用层替代品":分包、编号、发送端确认与重传、接收端排序与去重、以及(可选)拥塞控制与流量控制。

详解: 这正是"基于 UDP 的应用层协议"的用武之地,也是一道很好的综合题。要传 10 MB,远超过 65507 的单个数据报上限,也没有跨 1500 字节 MTU 的保证,所以必须分两个层级处理:

  1. 分包与编号:把文件拆成若干约 1400 字节左右的数据报(防止 IP 分片),给每个数据报编上序号;
  2. 可靠送达:接收端每收到一个就回 ACK;发送端超时未收到 ACK 就重发对应序号的包;
  3. 排序与去重:接收端维护接收窗口,按序号处理乱序、丢弃重复;
  4. 端到端完整性:最后校验接收到的所有分片拼接后是否等于原始文件(比如用校验和或摘要比对)。

你会发现,你亲手在应用层复刻蓝图大半套 TCP 的能力。这也是为什么真正靠谱的大型文件传输从不裸用 UDP,要么直接用 TCP,要么用微架在 UDP 上、能临时拼装可靠性的中间方案(如 QUIC)。UDP 给你的,只是一条最便宜的路;让这条路既对又快,是开发者自己的工程分内事。

收尾

从传输层的"最后一公里"出发,我们重新认识了标识应用的端口号、它的范围划分与知名端口号,也回答了"谁可以 bind 谁的端口"这两个经典问题;随后我们一头扎进 UDP:8 字节的首部、覆盖伪首部的校验和、无连接 / 不可靠 / 面向数据报的三大特点、一击一收的数据报边界、几乎没有的发送缓冲和满是硬伤的接收缓冲、64K 与 65507 的完整推导、与 TCP 的逐项对比、以及直播、游戏、DNS 各自的"为什么选它"。我们把"不可靠"和"校验和"这对看似矛盾的概念讲清楚了,也把"大数据要应用层分包"和"MTU/IP 分片"这两个实战巨坑上了紧箍咒。

如果你把每个知识点都亲手推一遍,尤其是那道 65507 的推导链和两个端口问题,这堂 UDP 课就真正拿下了。UDP 这个协议看似"缺斤少两",但它用最少的承诺换来了最大的自由——它把"怎么做可靠、怎么做快"的选择权,全部交还给了你那双手。这种"少即是多"的设计哲学,正是理解它的钥匙,也是你写网络程序时最该学会的取舍之道。下一回,我们把镜头切到它的老对手 TCP,看看"可靠"二字,究竟要用怎样的身段和诚意去实现。