UDP 协议详解
UDP(User Datagram Protocol,用户数据报协议)是传输层的另一个重要协议,由 RFC 768 定义。与 TCP 不同,UDP 提供无连接、不可靠的数据报传输服务,以其简单高效著称。
UDP 概述
UDP 的设计理念是"做尽可能少的事情",将复杂的可靠性机制留给应用层实现。这种设计使得 UDP 具有极低的协议开销和快速的传输速度。
UDP 的核心特性
无连接:发送数据前不需要建立连接,直接发送即可。这省去了 TCP 三次握手的时间开销,适合快速发送少量数据的场景。
不可靠传输:不保证数据到达,不保证顺序,不处理重传。发送方发送数据后不知道是否到达,需要应用层自己处理可靠性。
面向报文:保留报文边界,应用层给什么就发什么。不会像 TCP 那样合并或拆分数据,接收方收到的就是发送方发送的完整报文。
无拥塞控制:网络拥塞时不会降低发送速率。这对实时应用很重要,但也可能导致网络拥塞加剧。
支持多播和广播:可以一次发送给多个接收者。TCP 只支持一对一通信,UDP 支持一对一、一对多、多对多。
首部开销小:UDP 首部只有 8 字节,而 TCP 首部至少 20 字节。
UDP 与 TCP 的对比
| 特性 | UDP | TCP |
|---|---|---|
| 连接 | 无连接 | 面向连接 |
| 可靠性 | 不可靠 | 可靠 |
| 顺序 | 无序 | 有序 |
| 流量控制 | 无 | 滑动窗口 |
| 拥塞控制 | 无 | 慢启动、拥塞避免等 |
| 首部大小 | 8 字节 | 20-60 字节 |
| 传输方式 | 数据报 | 字节流 |
| 多播/广播 | 支持 | 不支持 |
| 适用场景 | 实时通信、DNS、游戏 | 文件传输、邮件、Web |
UDP 数据报格式
UDP 数据报由首部和数据两部分组成,首部固定 8 字节:
0 16 31
+----------------+--------------------------------+
| 源端口 | 目的端口 |
+----------------+--------------------------------+
| 长度 | 校验和 |
+----------------+--------------------------------+
| 数据 |
+------------------------------------------------+
字段详解
源端口(16 位):发送方的端口号,可选字段。如果不需要回复,可以置 0。
目的端口(16 位):接收方的端口号,必填字段。数据到达目的主机后,根据端口号交付给对应的应用进程。
长度(16 位):UDP 数据报的总长度,包括首部和数据,单位是字节。最小值是 8(只有首部),最大值是 65535。由于 IP 首部占用 20 字节,UDP 数据的最大载荷是 65535 - 8 - 20 = 65507 字节。
校验和(16 位):用于检测 UDP 数据报在传输过程中是否出错。计算时包含伪首部、UDP 首部和数据。伪首部包含源 IP、目的 IP、协议号和 UDP 长度,用于验证数据报是否到达正确的目的地。
伪首部
伪首部不是 UDP 数据报的一部分,只在计算校验和时使用:
0 16 31
+----------------+--------------------------------+
| 源 IP 地址 |
+------------------------------------------------+
| 目的 IP 地址 |
+--------+-------+--------------------------------+
| 0 | 协议 | UDP 长度 |
+--------+-------+--------------------------------+
协议字段值为 17,表示 UDP。通过包含 IP 地址,校验和可以验证数据报是否到达正确的目的地。
校验和计算
校验和的计算步骤:
- 构造伪首部,添加到 UDP 数据报前面
- 如果数据长度不是 16 位的整数倍,填充一个全 0 字节(不发送)
- 将所有 16 位字按二进制反码求和
- 结果取反即为校验和
如果计算结果为全 0,校验和字段应设为全 1(因为全 0 表示不使用校验和)。
UDP 的工作过程
发送数据
UDP 发送数据的过程非常简单:
- 应用程序将数据传递给 UDP
- UDP 添加 8 字节首部
- 将 UDP 数据报传递给 IP 层
- IP 层封装后发送
没有连接建立,没有确认机制,发送即完成。
接收数据
UDP 接收数据的过程:
- IP 层收到数据报,检查目的 IP
- 如果是本机 IP,将数据传递给 UDP
- UDP 检查校验和,如果出错则丢弃
- 根据目的端口,将数据传递给对应的应用进程
- 如果没有进程监听该端口,返回 ICMP 端口不可达消息
端口复用和分用
UDP 使用端口号实现多路复用和分用:
复用:多个应用进程可以使用 UDP 发送数据,UDP 通过端口号区分不同的进程。
分用:UDP 收到数据后,根据目的端口号将数据分发给对应的应用进程。
端口号范围 0-65535,其中 0-1023 是知名端口,由 IANA 分配;1024-49151 是注册端口;49152-65535 是动态端口。
UDP 的应用场景
UDP 的特点决定了它适合特定的应用场景。
实时音视频
视频会议、直播、网络电话等实时音视频应用首选 UDP。
原因:
- 实时性比可靠性更重要,丢包可以接受,延迟不能接受
- 音视频编解码器可以处理一定的丢包
- TCP 的重传机制会增加不可预测的延迟
例子:WebRTC、Zoom、微信视频通话
在线游戏
多人在线游戏需要快速响应,UDP 更适合。
原因:
- 游戏状态更新频繁,旧数据很快过时,重传没有意义
- 延迟直接影响游戏体验
- 游戏可以在应用层实现必要的可靠性
例子:王者荣耀、英雄联盟、绝地求生
DNS 查询
DNS 查询通常使用 UDP,响应快速。
原因:
- 查询请求很小,通常一个数据包就够了
- 不需要建立连接的开销
- 快速响应对用户体验很重要
注意:当响应超过 512 字节(传统限制)或需要安全传输时,DNS 也会使用 TCP。
DHCP
DHCP(动态主机配置协议)使用 UDP 进行地址分配。
原因:
- DHCP 客户端还没有 IP 地址,无法建立 TCP 连接
- 需要广播通信,UDP 支持广播
- 简单的请求-响应模式,不需要连接
DHCP 服务器监听端口 67,客户端使用端口 68。
SNMP
SNMP(简单网络管理协议)使用 UDP 进行网络设备监控。
原因:
- 网络设备可能无法处理大量 TCP 连接
- 监控数据丢失可以接受,下一个周期会重新发送
- 减少网络管理流量
流媒体
视频流、音频流等流媒体传输常用 UDP。
原因:
- 流媒体可以容忍一定的丢包
- 实时性要求高
- 应用层可以实现自适应码率
IoT 物联网
物联网设备资源有限,UDP 更适合。
原因:
- 设备计算能力和内存有限,TCP 的复杂开销太大
- 很多场景只需要发送少量数据
- CoAP(受限应用协议)基于 UDP
UDP 的可靠性增强
虽然 UDP 本身不可靠,但应用层可以实现可靠性机制。
应用层确认机制
发送方发送数据后等待确认,超时重传:
import socket
import time
def reliable_send(sock, data, addr, max_retries=3, timeout=1.0):
sock.settimeout(timeout)
for i in range(max_retries):
try:
sock.sendto(data, addr)
ack, _ = sock.recvfrom(1024)
if ack == b'ACK':
return True
except socket.timeout:
continue
return False
序号机制
为每个数据包分配序号,接收方可以检测丢包和乱序:
import struct
def pack_with_seq(seq, data):
return struct.pack('!I', seq) + data
def unpack_with_seq(packet):
seq = struct.unpack('!I', packet[:4])[0]
data = packet[4:]
return seq, data
前向纠错(FEC)
发送冗余数据,接收方可以根据冗余信息恢复丢失的数据:
- 异或 FEC:每 N 个数据包发送一个异或包,丢失一个可以恢复
- Reed-Solomon 编码:可以恢复多个丢失的数据包
拥塞控制
应用层可以实现拥塞控制,避免网络过载:
- 基于丢包:检测到丢包时降低发送速率
- 基于延迟:延迟增加时降低发送速率
- BBR:基于带宽和 RTT 的拥塞控制
QUIC 协议
QUIC(Quick UDP Internet Connections)是 Google 开发的基于 UDP 的可靠传输协议,用于 HTTP/3。
QUIC 在 UDP 之上实现了:
- 可靠传输(类似 TCP 的确认和重传)
- 拥塞控制
- 多路复用(一个连接多个流)
- 前向纠错
- 连接迁移(网络切换不断开)
UDP 编程接口
基本 UDP 通信
服务器端:
import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_socket.bind(('0.0.0.0', 8080))
print('UDP 服务器启动,等待数据...')
while True:
data, client_addr = server_socket.recvfrom(1024)
print(f'收到来自 {client_addr} 的数据: {data.decode()}')
server_socket.sendto(b'Hello from server', client_addr)
客户端:
import socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_addr = ('127.0.0.1', 8080)
client_socket.sendto(b'Hello from client', server_addr)
data, _ = client_socket.recvfrom(1024)
print(f'收到响应: {data.decode()}')
client_socket.close()
UDP 广播
UDP 支持广播通信:
import socket
# 发送广播
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
client_socket.sendto(b'Broadcast message', ('<broadcast>', 8080))
client_socket.close()
import socket
# 接收广播
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
server_socket.bind(('0.0.0.0', 8080))
while True:
data, addr = server_socket.recvfrom(1024)
print(f'收到广播: {data.decode()} from {addr}')
UDP 组播
UDP 支持组播通信,比广播更高效:
import socket
import struct
# 发送组播
multicast_group = ('224.1.1.1', 8080)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
ttl = struct.pack('b', 1)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)
sock.sendto(b'Multicast message', multicast_group)
sock.close()
import socket
import struct
# 接收组播
multicast_group = '224.1.1.1'
server_address = ('', 8080)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(server_address)
group = socket.inet_aton(multicast_group)
mreq = struct.pack('4sL', group, socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
while True:
data, addr = sock.recvfrom(1024)
print(f'收到组播: {data.decode()} from {addr}')
UDP 常见问题
数据包丢失
UDP 不保证数据到达,丢包是常态。应对方法:
- 应用层实现确认和重传
- 使用前向纠错
- 接受一定程度的丢包
数据包乱序
UDP 不保证顺序,数据可能乱序到达。应对方法:
- 添加序号,接收方根据序号重排
- 接受乱序(某些场景可以)
数据包过大
UDP 数据报过大可能导致 IP 分片,增加丢包概率。应对方法:
- 限制 UDP 数据报大小,不超过 MTU(通常 1500 字节)
- 考虑 IP 首部(20 字节)和 UDP 首部(8 字节),数据不超过 1472 字节
安全问题
UDP 容易受到攻击:
- UDP 洪泛攻击:发送大量 UDP 数据包消耗带宽
- 放大攻击:利用 UDP 服务放大攻击流量
应对方法:
- 限制 UDP 服务的响应速率
- 验证请求来源
- 使用防火墙过滤
小结
UDP 是简单高效的传输协议:
- 特点:无连接、不可靠、面向报文、支持多播广播
- 格式:8 字节首部,包含端口、长度、校验和
- 应用:实时音视频、在线游戏、DNS、DHCP、IoT
- 可靠性:应用层可以实现确认、重传、FEC 等机制
- 编程:使用 SOCK_DGRAM 类型的 Socket
UDP 适合对实时性要求高、可以容忍一定丢包的场景。如果需要可靠性,可以在应用层实现或使用 QUIC 等协议。
练习
- 比较 UDP 和 TCP 的主要区别
- 说明 UDP 校验和的计算过程
- 列举 UDP 适合的应用场景及其原因
- 编写一个简单的 UDP 客户端和服务器程序