UDP报文格式:源端口与目的端口、长度字段、校验和、封装与解封装

好,咱们直接切入正题。UDP的报文格式,说白了就是8个字节的头部加上数据部分。结构非常简单,但每个字段都有它的设计意图。我在项目中见过不少开发者觉得UDP头简单就忽略细节,结果排查问题时反而走了弯路。

UDP报文头部结构

UDP头部固定为8字节,由四个字段组成,每个字段2字节。我习惯把这四个字段分成两组:一组是寻址用的(源端口、目的端口),另一组是管理用的(长度、校验和)。

字段 长度(字节) 作用
源端口 2 标识发送方的应用程序
目的端口 2 标识接收方的应用程序
长度 2 UDP头部 + 数据的总长度
校验和 2 检测报文在传输中是否出错

核心要点:UDP头部只有8字节,而TCP头部是20字节。少了将近三分之二的开销,这也是UDP在实时场景下更受欢迎的原因之一。

源端口与目的端口

这两个字段决定了数据该交给哪个进程。你想想看,一台服务器上跑着DNS、DHCP、SNMP好几个服务,网卡收到一个UDP包后,靠什么区分?就是靠目的端口。

源端口呢?它其实是个“回信地址”。如果发送方希望接收方回复,就会填一个临时端口号。如果不关心回复——比如某些日志上报场景——源端口可以直接填0。

我的经验:我曾经排查过一个DNS解析超时的问题,抓包发现客户端发的DNS请求源端口是0,服务器直接丢弃了。虽然RFC允许源端口为0,但很多实现会直接过滤掉。所以实际开发中,我建议源端口一定要填一个有效的临时端口。

长度字段

长度字段记录的是整个UDP报文的大小——头部加数据。最小是8(只有头部,没有数据),最大是65535字节。但这里有个坑:IP层本身也有长度字段,为什么UDP还要重复记录?

嗯,原因其实很简单。UDP报文可能被IP分片,每个分片到达时IP头里的总长度只是当前分片的长度。UDP头里的长度字段能帮你确认整个报文是否完整到达。我在做嵌入式网络协议栈时,就靠这个字段判断是否需要等待后续分片。

注意:UDP长度字段的值如果小于8,接收方会直接丢弃该报文。这是协议规定的硬性检查,不要试图发送长度小于8的UDP包。

校验和

校验和是UDP里唯一的数据完整性保护机制。它覆盖了UDP头部、数据部分,还有一个叫“伪头部”的东西。

伪头部不是真正传输的字段,它只是计算校验和时临时拼出来的12字节结构,包含源IP、目的IP、协议类型和UDP长度。为什么要加这个?说白了就是为了防止IP层把数据送错主机。

我举个例子:如果IP层路由出错,把一个本该发往A主机的UDP包送到了B主机,B主机计算校验和时会发现伪头部里的目的IP和自己不匹配,直接丢弃。这是跨层保护的一种设计思路。

重要:IPv4下UDP校验和是可选的(可以填0),但IPv6下UDP校验和是强制的。我个人建议无论IPv4还是IPv6,永远不要关闭校验和。我在生产环境见过因为校验和为0导致数据静默损坏的案例,排查起来非常痛苦。

UDP报文封装过程

封装就是从上往下逐层加头的过程。我画了一张图,你可以直观地看到数据是怎么一层层包起来的。

UDP报文封装过程 应用层数据 UDP头部(8字节) 应用层数据 IP头部(20字节) UDP头部(8字节) 应用层数据 以太网头部(14字节) IP头部(20字节) UDP头部(8字节) 应用层数据 以太网尾部(4字节 FCS) 封装顺序:应用层 → UDP层 → IP层 → 链路层,每层添加自己的头部

封装步骤其实就四步:

  1. 应用层生成数据,比如一个DNS查询请求。
  2. UDP层加上8字节头部,填入源端口、目的端口、长度和校验和。
  3. IP层加上20字节IP头部,填入源IP、目的IP等。
  4. 链路层加上以太网头部和尾部,准备在物理介质上传输。

我的习惯:在调试网络问题时,我经常用tcpdump抓包,然后对照着这个封装层次一层层看。比如看到以太网头里的类型字段是0x0800,就知道里面是IP包;IP头里的协议字段是17,就知道里面是UDP。这种逐层验证的方法,帮我快速定位过好几次问题。

UDP报文解封装过程

解封装就是封装的逆过程。数据到达接收方后,从下往上逐层剥掉头部。我再用一张图说明。

UDP报文解封装过程 以太网头部(14字节) IP头部(20字节) UDP头部(8字节) 应用层数据 IP头部(20字节) UDP头部(8字节) 应用层数据 UDP头部(8字节) 应用层数据 应用层数据 解封装顺序:链路层 → IP层 → UDP层 → 应用层,每层剥掉对应的头部

解封装的关键步骤:

  1. 链路层收到原始比特流,检查以太网尾部FCS校验。通过后剥掉以太网头部和尾部,把剩余部分交给IP层。
  2. IP层检查IP头部,验证目的IP是否匹配本机。通过后剥掉IP头部,把剩余部分交给UDP层。
  3. UDP层检查校验和(如果开启),验证数据完整性。通过后剥掉UDP头部,把数据部分交给应用层。
  4. 应用层根据目的端口号,把数据交给对应的应用程序。

避坑指南:我曾经遇到过一个案例,应用层收到的UDP数据总是少几个字节。排查后发现是UDP校验和计算错误,导致接收方认为数据损坏直接丢弃了。但发送方没有收到任何错误通知——UDP没有重传机制。所以如果你发现应用层丢数据,先检查校验和实现是否正确。

UDP伪头部详解

前面提到校验和计算时会用到伪头部。这里我展开说一下伪头部的结构,因为很多人在实现UDP协议栈时在这里栽过跟头。

字段 长度(字节) 内容
源IP地址 4 IPv4地址
目的IP地址 4 IPv4地址
保留 1 固定为0
协议类型 1 UDP固定为17(0x11)
UDP长度 2 与UDP头中的长度字段一致

伪头部不参与传输,只在计算校验和时临时构造。计算完成后就丢弃。这样做的好处是:即使IP层路由出错,接收方也能通过伪头部里的IP地址发现数据送错了地方。

一句话总结:UDP报文格式虽然简单,但每个字段都有它的设计意图。源端口和目的端口解决进程寻址,长度字段解决报文边界,校验和解决数据完整性。封装和解封装的过程,就是数据在协议栈中逐层穿衣服和脱衣服的过程。