一、UDP协议格式

16位源端口号:发送端端口,用于回发响应;无需回复时可置 0。
16位目的端口号:接收端端口,操作系统据此分发数据给对应进程。
16位UDP长度:UDP 数据报总字节数(首部+数据)。最小 8,最大 65535(2ⁿ - 1)。实际数据部分最大 65507 字节(因 IP 首部至少 20 字节)。
16位UDP检验和:端到端差错校验。计算结果若为 0 则发送时置为全 1;收到为 0 表示未校验(极少见)。如果校验和出错,该报文会直接丢弃。
Linux内核自带UDP协议,不需要额外安装配置。 在调用 socket(AF_INET, SOCK_DGRAM, 0) 时,内核就会自动启用 UDP 协议的支持。
数据的发送、接收、端口管理、校验和计算 等所有底层工作,都由内核中的 UDP 协议栈自动完成。我们只需要通过 sendto / recvfrom 等系统调用使用它即可。
二、UDP协议特点
1. 无连接
-
知道对方的 IP 和端口后,直接发送数据,不需要像 TCP 那样经历三次握手。
-
类比:寄信 -- 写好地址扔进邮筒,不需要提前和对方打招呼。
2. 不可靠
-
没有确认机制,没有重传机制。
-
即使网络故障导致数据包丢失,UDP 协议层也不会通知应用程序。
-
注意:UDP 只提供可选的校验和,能检测但不能修复错误 -- 损坏或错误的数据包直接被丢弃。
3. 面向数据报(保留消息边界)
-
应用程序一次
sendto多少字节,对方必须用一次recvfrom完整读取同样字节的数据(若接收缓冲区不足,则截断且剩余数据丢失)。 -
不会像 TCP 那样拆分或合并数据 -- 因此 不会粘包。
三、UDP缓冲区
发送缓冲区:UDP 没有真正的发送缓冲区
-
当应用程序调用
sendto()时,内核会:-
将数据从用户空间拷贝到内核空间。
-
立即封装成 UDP 数据报(添加 UDP 头部)。
-
直接交给 IP 层,进行后续路由、分片(如果需要)、发送。
-
-
内核不会 为这个数据报保留一份拷贝(不像 TCP 需要保留以应对丢包重传)。因此通常说:UDP 没有发送缓冲区。
-
如果 IP 层或网卡队列暂时繁忙,数据包可能在 IP 层或驱动队列短暂排队,但这不是 UDP 层独立维护的发送缓冲区,而是内核数据结构。
类比:寄信时,你把信交给邮局窗口,邮局直接分拣发走,不会为你保留副本。
接收缓冲区:UDP 有独立的接收缓冲区
每个 UDP socket 在内核中都有一个接收缓冲区(实际上是一个队列,存放收到的 UDP 数据报)。
工作流程:
-
网卡收到 UDP 数据报,经过 IP 层解包,确认协议为 UDP。
-
内核根据目的端口号找到对应的 UDP socket。
-
内核将整个 UDP 数据报(包括数据部分)拷贝到该 socket 的接收队列末尾。
-
应用程序调用
recvfrom()时,内核从队列头部取出一个完整的数据报拷贝给用户。
关键特性:
-
每个数据报独立存储 :队列中的元素是完整的 UDP 数据报(应用程序一次
sendto对应一个队列元素)。 -
不合并 / 不拆分 :接收方必须用一次
recvfrom读取一个完整数据报;若用户缓冲区小于数据报大小,多余数据被丢弃 (且返回MSG_TRUNC错误)。 -
顺序不保证:网络传输可能导致乱序,队列按到达顺序存放(即乱序到达则乱序交付)。
-
满则丢 :如果队列已满,新到达的 UDP 数据报被直接丢弃,不通知发送方。
四、细节理解
报文
在操作系统内核里,一个网络报文并不是一个简单的连续内存块,而是由两部分组成:
struct sk_buff结构体:报文的 "管理元数据",记录报文的所有信息(长度、协议类型、指针等)。- 线性数据缓冲区:报文实际存储的字节数据,包括各层协议头和应用数据。
sk_buff 里的这几个指针是核心:
struct sk_buff {
unsigned char *head; // 缓冲区起始地址
unsigned char *data; // 当前协议层的起始地址
unsigned char *tail; // 当前协议层的结束地址
unsigned char *end; // 缓冲区结束地址
// ... 其他字段
};
封装过程(发送方)
-
应用层 :内核分配
sk_buff,data指向缓冲区中部(预留头部空间),拷贝用户数据到当前位置。 -
UDP 层 :
data向前移动 8 字节,填入 UDP 头。 -
IP 层 :
data再向前移动 IP 头长度(20 字节),填入 IP 头。 -
链路层 :
data再向前移动以太网头长度(14 字节),填入 MAC 头。
最终 data 指向帧头,tail 指向数据尾,交给网卡发送。
总结 :逐层向左移动 data 指针,在空出的位置填头部,无需移动数据本身。
解包过程(接收方)
-
链路层 :网卡收到帧,
data指向帧头。识别类型后,data向右移动 14 字节 → 指向 IP 头。 -
IP 层 :校验后,
data向右移动 IP 头长度 → 指向 UDP 头。 -
UDP 层 :校验后,
data向右移动 8 字节 → 指向应用层数据。 -
应用层 :内核将
data到tail之间的数据拷贝给用户。
总结 :逐层向右移动 data 指针,跳过各层头部,最后剩下的就是应用数据。

封装和解包最核心的操作就是移动指针。
读数据到应用层
socket 作为文件描述符,其读操作本质上就是从内核中该 socket 的接收队列里取数据。当网卡收到 UDP 报文后,内核通过逐层解包(移动 sk_buff 的 data 指针)剥离掉以太网头、IP 头和 UDP 头,使 data 指针最终指向纯净的应用层数据,随后将这个 sk_buff 挂入对应 UDP socket 的接收队列。
当用户程序调用 read 或 recvfrom 时,内核只是简单地将队列中已准备好的应用层数据从内核空间拷贝到用户空间缓冲区,并释放或回收相应的 sk_buff。