第13章:UDP(User Datagram Protocol)
用户数据报协议
一、导读
1.1 本章学习目标
第13章讲解UDP(用户数据报协议)——TCP/IP协议族中与TCP并列的另一个传输层协议。UDP提供简单的、无连接的数据报服务,不建立连接、不保证可靠交付、不进行流量控制和拥塞控制。UDP的设计哲学是最小化:只提供最基本的功能(端口复用和差错检测),将复杂性留给应用层。
本章的核心学习目标包括:
- 掌握UDP报文段的格式:极其简单的8字节头部
- 理解UDP与TCP的核心区别:无连接vs面向连接、不可靠vs可靠
- 了解UDP的适用场景:DNS、DHCP、视频流、在线游戏、VoIP
- 理解UDP校验和的计算方法:覆盖伪头部、UDP头部和数据
- 掌握UDP套接字编程:与TCP套接字的关键区别
- 了解基于UDP的可靠传输协议:QUIC、KCP等
1.2 为什么本章如此重要
虽然TCP是Internet上使用最广泛的传输层协议,但UDP在很多关键场景中不可替代:
- DNS:域名查询使用UDP,因为查询报文很小,UDP的开销更低
- DHCP:主机在获取IP地址之前无法使用TCP
- 实时应用:视频通话、在线游戏、VoIP对延迟敏感,可以容忍丢包但不能容忍重传延迟
- 简单请求-响应:NTP、SNMP等协议使用UDP,因为交互简单
- 现代协议基础:Google的QUIC协议在UDP之上实现了可靠传输和加密,是HTTP/3的基础
理解UDP的优缺点,有助于你在网络编程中做出正确的协议选择。
1.3 核心问题引导
在阅读本章时,请带着以下问题思考:
UDP头部只有8字节,它提供了哪些功能?省略了哪些功能?
UDP的校验和在IPv4中是可选的,为什么?在IPv6中为什么变成强制的?
什么场景下UDP比TCP更合适?判断标准是什么?
如果应用层需要可靠传输,为什么不直接用TCP,而是在UDP上自己实现?
QUIC协议为什么选择在UDP之上构建,而不是直接修改TCP?
UDP有没有流量控制?如果发送方发送过快,会发生什么?
二、核心概念详解
2.1 UDP的特点
| 特性 | UDP | TCP |
|---|---|---|
| 连接 | 无连接 | 面向连接 |
| 可靠性 | 不可靠 | 可靠 |
| 有序性 | 不保证顺序 | 保证顺序 |
| 流量控制 | 无 | 有 |
| 拥塞控制 | 无 | 有 |
| 头部开销 | 8字节 | 20-60字节 |
| 传输方式 | 数据报 | 字节流 |
| 连接数 | 一对多/一对一 | 一对一 |
UDP的设计哲学:尽可能少做事。它只做两件事:
通过端口号区分不同的应用进程
通过校验和检测数据错误
其他所有功能(可靠传输、流量控制、拥塞控制、连接管理)都由应用层自行实现(如果需要的话)。
2.2 UDP报文段格式
UDP头部极其简单,只有4个字段,共8字节:
0 7 8 15 16 23 24 31
+--------+--------+--------+--------+
| Source Port | Dest Port |
+--------+--------+--------+--------+
| Length | Checksum |
+--------+--------+--------+--------+
| Data (variable) |
+-------------------------------------+| 字段 | 长度 | 说明 |
|---|---|---|
| Source Port | 16位 | 源端口号(可选,可以为0) |
| Destination Port | 16位 | 目的端口号 |
| Length | 16位 | UDP报文段总长度(头部+数据),最小为8 |
| Checksum | 16位 | 校验和(IPv4可选,IPv6强制) |
2.2.1 UDP校验和
UDP校验和覆盖三个部分:
伪头部(12字节):源IP、目的IP、零、协议号(17)、UDP长度
UDP头部(8字节)
UDP数据
伪头部的目的是让UDP校验和能够验证数据报确实到达了正确的目的主机(通过检查IP地址)。
伪头部:
+--------+--------+--------+--------+
| Source IP Address |
+--------+--------+--------+--------+
| Destination IP Address |
+--------+--------+--------+--------+
| Zero |Protocol| UDP Length |
+--------+--------+--------+--------+如果校验和计算结果为0,在IPv4中表示"未计算校验和"(因为0的补码仍然是0,无法区分"校验和为0"和"未计算")。因此,如果计算结果确实为0,使用全1(0xFFFF)代替。
2.3 UDP与TCP的对比
2.3.1 何时选择UDP
选择UDP的典型场景:
| 场景 | 原因 | 示例 |
|---|---|---|
| 简单请求-响应 | 不需要连接建立的开销 | DNS、SNMP、NTP |
| 实时应用 | 不能容忍重传延迟 | VoIP、视频会议、在线游戏 |
| 广播/多播 | TCP不支持一对多 | DHCP、服务发现 |
| 隧道协议 | 需要封装其他协议 | VPN、隧道 |
| 自定义可靠协议 | 需要自定义的可靠传输策略 | QUIC、KCP |
2.3.2 UDP的"不可靠"意味着什么
- 不保证交付:数据报可能丢失(网络拥塞、缓冲区满)
- 不保证顺序:数据报可能乱序到达
- 不保证不重复:数据报可能重复到达
- 没有流量控制:发送方可能淹没接收方
- 没有拥塞控制:可能导致网络拥塞加剧
应用层如果需要这些保证,必须自己实现。
2.4 UDP套接字编程
UDP套接字与TCP套接字的关键区别:
// TCP客户端
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
connect(sockfd, ...); // 建立连接
write(sockfd, data, len); // 发送数据
read(sockfd, buf, len); // 接收数据
close(sockfd); // 关闭连接
// UDP客户端
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 不需要connect
sendto(sockfd, data, len, 0, (struct sockaddr *)&dest, sizeof(dest));
recvfrom(sockfd, buf, len, 0, (struct sockaddr *)&src, &srclen);
close(sockfd);关键区别:
- UDP使用
SOCK_DGRAM(数据报套接字),TCP使用SOCK_STREAM - UDP不需要
connect(但可以使用connect来简化后续操作) - UDP使用
sendto/recvfrom(需要指定/获取地址),TCP使用write/read - UDP是消息边界的,每次
sendto对应一次recvfrom
2.4.1 UDP的connect
虽然UDP是无连接的,但可以调用 connect 来指定对端地址。之后可以使用 write/read 代替 sendto/recvfrom,并且内核只接收来自该对端的数据报。
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
connect(sockfd, (struct sockaddr *)&dest, sizeof(dest));
// 之后可以使用write/read
write(sockfd, data, len);
read(sockfd, buf, len);2.5 UDP的常见应用
2.5.1 DNS
DNS查询通常使用UDP(端口53),因为:
- 查询和响应报文通常很小(<512字节)
- UDP的开销比TCP小(不需要三次握手)
- 如果响应超过512字节,DNS会回退到TCP
2.5.2 DHCP
DHCP使用UDP(客户端68,服务器67),因为:
- 客户端在获取IP地址之前无法使用TCP
- DHCP报文需要广播,UDP支持广播
2.5.3 实时多媒体
VoIP、视频会议、在线游戏使用UDP,因为:
- 实时性比可靠性更重要
- 旧的数据重传没有意义(已经过时)
- 应用层可以处理丢包(如插值、前向纠错)
2.5.4 NFS
NFS(Network File System)早期版本使用UDP,因为:
- 简单、低延迟
- 应用层实现可靠传输(针对文件操作)
- 现代NFS(v4+)使用TCP
2.6 基于UDP的可靠传输
很多现代协议选择在UDP之上构建可靠传输,而不是直接使用TCP:
2.6.1 QUIC(HTTP/3)
Google设计的QUIC协议在UDP之上实现了:
- 可靠传输(类似TCP)
- 多路复用(无队头阻塞)
- 加密(内置TLS 1.3)
- 快速连接建立(0-RTT或1-RTT)
- 连接迁移(使用Connection ID而非四元组)
QUIC被标准化为HTTP/3的传输协议,是Web的未来方向。
2.6.2 KCP
KCP是一个基于UDP的自定义可靠传输协议,特点是:
- 比TCP更快的重传策略
- 可选择SACK(选择性确认)
- 非退让流控(适合游戏等场景)
2.6.3 为什么不在TCP上实现这些功能
TCP是操作系统内核的一部分,修改和升级非常困难。在UDP上构建新协议,可以:
- 快速迭代和部署
- 不受操作系统限制
- 灵活定制传输策略
三、实战案例
3.1 案例一:使用tcpdump观察UDP报文
sudo tcpdump -i eth0 -nn 'udp port 53'输出示例(DNS查询):
12:34:56.789 IP 192.168.1.100.50000 > 8.8.8.8.53:
12345 [1au] A? www.example.com. (34)
12:34:56.815 IP 8.8.8.8.53 > 192.168.1.100.50000:
12345 1/0/0 A 93.184.216.34 (50)分析:
- UDP头部只有源端口、目的端口、长度、校验和
- 与TCP相比,没有序号、确认号、窗口大小等字段
- 每个UDP报文段是独立的,没有连接概念
3.2 案例二:编写UDP回声服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 65535
int main() {
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(9999);
bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
char buffer[BUF_SIZE];
struct sockaddr_in cli_addr;
socklen_t cli_len = sizeof(cli_addr);
while (1) {
int n = recvfrom(sockfd, buffer, BUF_SIZE, 0,
(struct sockaddr *)&cli_addr, &cli_len);
if (n > 0) {
buffer[n] = '\0';
printf("Received %d bytes from %s:%d: %s\n",
n, inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), buffer);
sendto(sockfd, buffer, n, 0, (struct sockaddr *)&cli_addr, cli_len);
}
}
close(sockfd);
return 0;
}3.3 案例三:测试UDP的不可靠性
# 发送大量UDP数据报,观察是否有丢失
# 发送端
for i in $(seq 1 1000); do
echo "packet $i" | nc -u -w0 192.168.1.200 9999
done
# 接收端统计收到的报文数
# 如果少于1000,说明有丢包四、常见误区与难点
4.1 误区一:"UDP不可靠所以不如TCP"
UDP和TCP各有适用场景。UDP的"不可靠"在某些场景中反而是优势——没有重传延迟、没有拥塞控制的限速、没有连接建立的开销。
4.2 误区二:"UDP没有校验和"
UDP有校验和(IPv4可选,IPv6强制),只是不提供可靠传输保证。校验和用于检测数据传输过程中的错误,与可靠传输是两个不同的概念。
4.3 难点:理解为什么QUIC选择UDP而不是TCP
TCP是操作系统内核的一部分,升级困难(需要更新所有操作系统)。UDP是用户空间协议的基础,可以在应用层实现任何自定义逻辑。这是QUIC能够快速部署的关键原因。
五、本章小结
第13章讲解了UDP协议:
UDP头部只有8字节:源端口、目的端口、长度、校验和。
UDP是无连接的、不可靠的:不建立连接、不保证交付、不保证顺序、没有流量控制和拥塞控制。
UDP适用于简单请求-响应、实时应用、广播/多播、隧道协议等场景。
UDP校验和覆盖伪头部、UDP头部和数据,IPv4可选,IPv6强制。
现代协议(QUIC、KCP)在UDP之上构建可靠传输,避免修改TCP内核实现的限制。
六、思考与练习
思考题
如果让你设计一个基于UDP的可靠传输协议,你会如何实现可靠传输?与TCP相比,你会做哪些不同的设计?
UDP的源端口号是可选的(可以为0)。如果源端口为0,接收方如何回复?
为什么DNS主要使用UDP而不是TCP?如果DNS完全迁移到TCP(或QUIC),会有什么影响?
QUIC的多路复用解决了TCP的什么问题?它是如何实现的?
练习题
编写一个UDP客户端和服务器,使用tcpdump观察报文交互过程。
测试UDP在不同丢包率下的传输性能,分析UDP的局限性。
研究QUIC协议的规范(RFC 9000),分析它如何在UDP之上实现可靠传输和加密。