跳到主要内容

UDP 协议详解

UDP(User Datagram Protocol,用户数据报协议)是传输层的另一个重要协议,由 RFC 768 定义。与 TCP 不同,UDP 提供无连接、不可靠的数据报传输服务,以其简单高效著称。

UDP 概述

UDP 的设计理念是"做尽可能少的事情",将复杂的可靠性机制留给应用层实现。这种设计使得 UDP 具有极低的协议开销和快速的传输速度。

UDP 的核心特性

无连接:发送数据前不需要建立连接,直接发送即可。这省去了 TCP 三次握手的时间开销,适合快速发送少量数据的场景。

不可靠传输:不保证数据到达,不保证顺序,不处理重传。发送方发送数据后不知道是否到达,需要应用层自己处理可靠性。

面向报文:保留报文边界,应用层给什么就发什么。不会像 TCP 那样合并或拆分数据,接收方收到的就是发送方发送的完整报文。

无拥塞控制:网络拥塞时不会降低发送速率。这对实时应用很重要,但也可能导致网络拥塞加剧。

支持多播和广播:可以一次发送给多个接收者。TCP 只支持一对一通信,UDP 支持一对一、一对多、多对多。

首部开销小:UDP 首部只有 8 字节,而 TCP 首部至少 20 字节。

UDP 与 TCP 的对比

特性UDPTCP
连接无连接面向连接
可靠性不可靠可靠
顺序无序有序
流量控制滑动窗口
拥塞控制慢启动、拥塞避免等
首部大小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 地址,校验和可以验证数据报是否到达正确的目的地。

校验和计算

校验和的计算步骤:

  1. 构造伪首部,添加到 UDP 数据报前面
  2. 如果数据长度不是 16 位的整数倍,填充一个全 0 字节(不发送)
  3. 将所有 16 位字按二进制反码求和
  4. 结果取反即为校验和

如果计算结果为全 0,校验和字段应设为全 1(因为全 0 表示不使用校验和)。

UDP 的工作过程

发送数据

UDP 发送数据的过程非常简单:

  1. 应用程序将数据传递给 UDP
  2. UDP 添加 8 字节首部
  3. 将 UDP 数据报传递给 IP 层
  4. IP 层封装后发送

没有连接建立,没有确认机制,发送即完成。

接收数据

UDP 接收数据的过程:

  1. IP 层收到数据报,检查目的 IP
  2. 如果是本机 IP,将数据传递给 UDP
  3. UDP 检查校验和,如果出错则丢弃
  4. 根据目的端口,将数据传递给对应的应用进程
  5. 如果没有进程监听该端口,返回 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 是简单高效的传输协议:

  1. 特点:无连接、不可靠、面向报文、支持多播广播
  2. 格式:8 字节首部,包含端口、长度、校验和
  3. 应用:实时音视频、在线游戏、DNS、DHCP、IoT
  4. 可靠性:应用层可以实现确认、重传、FEC 等机制
  5. 编程:使用 SOCK_DGRAM 类型的 Socket

UDP 适合对实时性要求高、可以容忍一定丢包的场景。如果需要可靠性,可以在应用层实现或使用 QUIC 等协议。

练习

  1. 比较 UDP 和 TCP 的主要区别
  2. 说明 UDP 校验和的计算过程
  3. 列举 UDP 适合的应用场景及其原因
  4. 编写一个简单的 UDP 客户端和服务器程序