2. UDP 报文格式:拆开来看,其实很简单

上一章我们聊了 UDP 的“快”和“不靠谱”,那它到底长什么样?

说白了,UDP 报文就是一段二进制数据,前面加了个 8 字节的头部。这个头部非常精简,只有四个字段。我刚开始学网络编程时,看到 TCP 那动辄 20 字节的头部就头疼,UDP 这个清爽多了。

2.1 UDP 报文整体结构

UDP 报文由两部分组成:UDP 头部 和 UDP 数据。头部固定 8 字节,数据部分最长可以到 65527 字节(理论值)。

先看一张整体结构图,心里有个谱:

UDP 报文结构 UDP 头部(8 字节) 源端口(2 字节) 目的端口(2 字节) 长度(2 字节) 校验和(2 字节) UDP 数据(可变长度) 应用层数据,最大 65527 字节 头部仅 8 字节,开销极小,这是 UDP 快的重要原因

嗯,结构就这么简单。下面我们逐个字段拆开讲。

2.2 源端口(Source Port)—— 2 字节

源端口标识发送方的进程。你想想看,一台机器上可能同时跑着好几个网络应用——浏览器、游戏、视频通话……操作系统怎么区分哪个数据包是发给谁的?靠的就是端口号。

  • 范围:0 ~ 65535
  • 作用:告诉接收方,“回信请寄到这个端口”
  • 注意:如果发送方不需要回复(比如单向日志上报),源端口可以填 0
我的一点经验:写 P2P 通信程序时,源端口一定要填对。我曾经调试一个文件传输工具,两边数据发出去就是收不到,查了半天才发现源端口写死了 0,对端根本不知道往哪回。后来改成系统分配的临时端口,问题就解决了。

2.3 目的端口(Destination Port)—— 2 字节

目的端口标识接收方的进程。这个字段必须填,不能为 0。

举个例子:你电脑上的 QQ 想给朋友的发消息。QQ 会把自己的端口填在源端口,把的端口填在目的端口。网络上的路由器只看 IP 地址把包送到朋友电脑,到了朋友电脑上,操作系统再看目的端口,把数据交给。

端口范围 分类 常见例子
0 - 1023 知名端口(Well-Known Ports) DNS 使用 53,DHCP 使用 67/68
1024 - 49151 注册端口(Registered Ports) 很多游戏服务器用 27015
49152 - 65535 动态/私有端口(Dynamic/Private Ports) 客户端临时分配,用完就释放
避坑提醒:我曾经在项目里用了 80 端口跑 UDP 服务,结果和本地的 HTTP 服务冲突了。虽然 UDP 和 TCP 端口空间是独立的,但很多防火墙和系统服务会默认拦截知名端口上的非标准协议。建议自定义服务用 1024 以上的端口,省心。

2.4 长度(Length)—— 2 字节

这个字段表示整个 UDP 报文的长度,单位是字节。包括头部 8 字节 + 数据部分。

  • 最小值:8(只有头部,没有数据)
  • 最大值:65535(理论值,实际受限于 IP 层)

你可能会问:“既然 IP 层已经知道总长度了,为什么 UDP 还要再算一遍?”

嗯,这是个好问题。其实这是为了冗余校验。IP 层可能会对数据包做分片重组,UDP 层拿到的不一定是完整的原始包。长度字段让接收方可以验证:“我收到的数据长度对不对?”如果对不上,直接丢弃。

核心要点:长度字段 = UDP 头部(8 字节)+ UDP 数据长度。计算数据部分长度时,记得减去 8。

2.5 校验和(Checksum)—— 2 字节

校验和是 UDP 唯一的差错检测机制。它覆盖了 UDP 头部、数据部分,以及一个伪头部。

什么是伪头部?它并不是 UDP 报文的一部分,而是从 IP 头部里“借”来的信息——源 IP 地址、目的 IP 地址、协议类型、UDP 长度。为什么要包含这些?

说白了,就是为了防止数据包被送错地方。比如,一个包本来要发给 192.168.1.10,结果网络出了问题,送到了 192.168.1.11。如果校验和不包含 IP 地址,接收方检查 UDP 校验和会发现数据没损坏,就收下了——这就出大问题了。伪头部让接收方可以确认:“这个包确实是给我的。”

校验和的计算过程:

  1. 发送方把校验和字段置为 0
  2. 将伪头部、UDP 头部、数据部分按 16 位一组进行二进制反码求和
  3. 将结果取反,填入校验和字段
  4. 接收方收到后,同样计算一遍,如果结果为 0,说明数据完整
我踩过的坑:UDP 校验和是可选的。IPv4 下,如果发送方把校验和设为 0,接收方就不做校验。我曾经为了省那一点点 CPU 开销,把校验和关了,结果线上偶尔出现数据错乱,排查了三天才发现是网卡硬件偶尔翻转了某一位。从那以后,我所有的 UDP 程序都强制开启校验和。IPv6 下校验和是强制的,不能关。

2.6 伪头部(Pseudo Header)—— 不在报文里,但很重要

伪头部是计算校验和时临时构造的 12 字节结构,不参与网络传输。它的组成如下:

字段 长度 说明
源 IP 地址 4 字节 来自 IP 头部
目的 IP 地址 4 字节 来自 IP 头部
保留位 1 字节 固定为 0
协议类型 1 字节 UDP 固定为 17(0x11)
UDP 长度 2 字节 与 UDP 头部的长度字段一致

你看,伪头部把 IP 层的地址信息“绑定”到了 UDP 校验和里。这样即使 IP 层出了错,UDP 也能发现。

2.7 各字段作用总结

最后,我用一个表格把四个字段的作用串起来,方便你复习:

字段 大小 作用 必须填?
源端口 2 字节 标识发送方进程,用于回信 可选(可填 0)
目的端口 2 字节 标识接收方进程,数据分发依据 必须
长度 2 字节 整个 UDP 报文长度(头部+数据) 必须
校验和 2 字节 检测数据在传输中是否损坏或送错 IPv4 可选,IPv6 必须