引言
在前面的文章中,我们深入学习了TCP协议(三次握手、四次挥手、流量控制、拥塞控制、重传机制)。今天我们来学习另一个重要的传输层协议:UDP(User Datagram Protocol)。
UDP的核心特点:
- ✅ 极简设计:没有连接建立、流量控制、拥塞控制、重传
- ✅ 高效率:开销小,延迟低
- ❌ 不可靠:不保证数据到达、不保证顺序、不保证不重复
今天我们来理解:
- ✅ UDP的头部结构(仅8字节)
- ✅ 为什么UDP是不可靠的?
- ✅ UDP的优势场景
- ✅ UDP vs TCP性能对比
UDP头部结构:极简的8字节
UDP报文段格式
0 16 31
+-------------------+-------------------+
| Source Port | Destination Port | 2字节 + 2字节 = 4字节
+-------------------+-------------------+
| Length | Checksum | 2字节 + 2字节 = 4字节
+-------------------+-------------------+
| |
| Data(应用数据) |
| ... |
+---------------------------------------+
总共:8字节头部 + 数据
字段说明:
| 字段 | 长度 | 说明 |
|---|---|---|
| Source Port | 16位(2字节) | 源端口号(可选,不需要时可以设为0) |
| Destination Port | 16位(2字节) | 目标端口号(必须) |
| Length | 16位(2字节) | UDP报文段总长度(头部8字节 + 数据长度) |
| Checksum | 16位(2字节) | 校验和(检测数据是否损坏,可选) |
示例:
UDP报文段:
源端口:54321(0xD431)
目标端口:53(DNS,0x0035)
长度:28字节(8字节头部 + 20字节数据)
校验和:0x1234
十六进制表示:
D4 31 00 35 00 1C 12 34 [数据...]
↑源端口 ↑目标端口 ↑长度 ↑校验和
对比TCP头部
TCP头部:至少20字节,包含大量控制信息
TCP头部字段(部分):
- 序列号(4字节)
- 确认号(4字节)
- 窗口大小(2字节)
- 标志位(SYN/ACK/FIN等,1字节)
- 选项(可变长度)
- ...
总共:至少20字节,最多60字节
UDP头部:仅8字节,极简
UDP头部字段:
- 源端口(2字节)
- 目标端口(2字节)
- 长度(2字节)
- 校验和(2字节)
总共:固定8字节
开销对比:
发送1000字节数据:
TCP:
头部:20字节
总长度:1020字节
开销:2%
UDP:
头部:8字节
总长度:1008字节
开销:0.8%
UDP开销降低60%!
为什么UDP是不可靠的?
UDP没有的特性
1. 没有连接建立(无握手)
TCP:
客户端 服务器
| [三次握手] |
| SYN |
|----------------------->|
| SYN-ACK |
|<-----------------------|
| ACK |
|----------------------->|
| [连接建立,开始传输] |
UDP:
客户端 服务器
| 直接发送数据 |
|----------------------->|
| 没有握手,立即传输 |
后果:
- ✅ 优点:没有连接建立延迟(省去1.5个RTT)
- ❌ 缺点:不知道对方是否可达,不知道对方是否准备好接收
2. 没有确认机制(无ACK)
TCP:
发送方 接收方
| seq=100(发送) |
|----------------------->|
| ACK=101(确认收到) |
|<-----------------------|
| ✅ 确认数据已送达 |
UDP:
发送方 接收方
| 数据(发送) |
|----------------------->|
| (没有ACK) |
| ❓ 不知道是否送达 |
后果:
- ✅ 优点:没有ACK开销,单向通信即可
- ❌ 缺点:不知道数据是否到达
3. 没有重传机制
TCP:
发送方 接收方
| seq=100(丢失) |
|----X |
| RTO超时,重传 |
| seq=100(重传) |
|----------------------->|
| ✅ 保证数据到达 |
UDP:
发送方 接收方
| 数据(丢失) |
|----X |
| 没有重传 |
| ❌ 数据永远丢失 |
后果:
- ✅ 优点:没有重传开销,不会因为丢包而延迟
- ❌ 缺点:数据可能丢失
4. 没有流量控制
TCP:
接收方告诉发送方:
"我的缓冲区还有64KB空间,你最多发送64KB"
发送方根据接收窗口控制发送速度
UDP:
发送方:
"我不管你能不能接收,我就发送"
接收方缓冲区满了?数据丢失!
后果:
- ✅ 优点:发送方可以全速发送
- ❌ 缺点:可能导致接收方缓冲区溢出,数据丢失
5. 没有拥塞控制
TCP:
发送方感知网络拥塞:
- 慢启动
- 拥塞避免
- 快速重传
- 快速恢复
动态调整发送速率,避免网络崩溃
UDP:
发送方:
"我不管网络拥塞,我就全速发送"
可能导致网络拥塞加剧
后果:
- ✅ 优点:不需要复杂的拥塞控制算法
- ❌ 缺点:大量UDP流量可能导致网络拥塞
6. 没有顺序保证
TCP:
发送方发送:seq=1, 2, 3, 4, 5
接收方收到:3, 1, 5, 2, 4(乱序)
TCP重新排序:1, 2, 3, 4, 5 ✅
UDP:
发送方发送:packet 1, 2, 3, 4, 5
接收方收到:3, 1, 5(2和4丢失)
应用程序收到:3, 1, 5 ❌
后果:
- ✅ 优点:不需要缓存和重排序
- ❌ 缺点:应用程序收到的数据可能乱序
UDP的优势场景
1. 实时性要求高的场景
场景:视频直播
问题:TCP的重传机制导致延迟
TCP视频直播:
帧1(丢失)→ 重传 → 延迟500ms
帧2(等待帧1)→ 延迟500ms
帧3(等待帧2)→ 延迟500ms
...
观众:画面卡顿,延迟累积
UDP视频直播:
UDP视频直播:
帧1(丢失)→ 不重传,继续发送帧2
帧2(到达)→ 立即播放
帧3(到达)→ 立即播放
...
观众:偶尔花屏,但实时性好
为什么UDP更适合?
- 丢失一帧画面不影响后续帧
- 实时性比完整性更重要
- 观众宁可看到花屏,也不要卡顿
场景:在线游戏
问题:TCP的顺序传输导致延迟
TCP游戏:
位置更新1(丢失)→ 重传 → 延迟300ms
位置更新2(等待1)→ 延迟300ms
位置更新3(等待2)→ 延迟300ms
玩家:角色位置延迟,操作卡顿
UDP游戏:
UDP游戏:
位置更新1(丢失)→ 不重传
位置更新2(到达)→ 立即处理
位置更新3(到达)→ 立即处理
玩家:角色位置略有跳跃,但操作流畅
为什么UDP更适合?
- 最新的位置信息最重要
- 丢失的旧位置无需重传
- 延迟比完整性更重要
2. 广播和多播场景
场景:局域网设备发现
设备A(发送方)
|
| UDP广播:"我是设备A,IP 192.168.1.100"
|
+-------------------+-------------------+
| | |
设备B(接收) 设备C(接收) 设备D(接收)
TCP无法做到:
- TCP是一对一连接
- 无法同时向多个设备发送
为什么UDP更适合?
- UDP支持广播(255.255.255.255)和多播(224.0.0.0-239.255.255.255)
- 一次发送,多个设备接收
- 不需要建立连接
场景:IPTV组播
IPTV服务器(发送方)
|
| UDP组播:239.1.1.1(视频流)
|
+-------------------+-------------------+-------------------+
| | | |
客户端1(订阅) 客户端2(订阅) 客户端3(订阅) ...(千万用户)
TCP无法做到:
- 无法同时向千万用户发送
- 每个连接都需要独立的TCP流,服务器压力巨大
为什么UDP更适合?
- 一份数据流,千万用户共享
- 节省服务器带宽(不需要为每个用户单独发送)
- 节省服务器资源(不需要维护千万个TCP连接)
3. 简单的请求-响应场景
场景:DNS查询
客户端 DNS服务器
| [TCP] |
| SYN(三次握手) |
| SYN-ACK |
| ACK |
| DNS查询 |
| DNS响应 |
| FIN(四次挥手) |
| FIN-ACK |
| ACK |
| ACK |
| |
总计:10个数据包
| [UDP] |
| DNS查询 |
| DNS响应 |
总计:2个数据包
UDP效率提升5倍!
为什么UDP更适合?
- 只有一次请求和一次响应
- 不需要建立连接
- 丢失了重新查询即可(查询成本低)
场景:SNMP网络管理
监控系统每秒查询1000台设备的CPU使用率:
TCP:
- 每台设备:三次握手(1.5RTT)+ 查询(0.5RTT)+ 四次挥手(2RTT)= 4RTT
- 1000台设备:4000 RTT
- 假设RTT=10ms,总时间:40秒
UDP:
- 每台设备:查询(0.5RTT)+ 响应(0.5RTT)= 1RTT
- 1000台设备:1000 RTT
- 假设RTT=10ms,总时间:10秒
UDP效率提升4倍!
4. 不需要可靠性的场景
场景:语音通话(VoIP)
丢包率:1%
丢包影响:偶尔听不清一个字,但不影响整体通话
为什么UDP更适合?
- 语音可以容忍少量丢包(人耳可以脑补)
- 实时性比完整性更重要
- 丢失的音频无需重传(重传的音频已经过时)
场景:传感器数据上报
温度传感器每秒上报温度:
时间0:25.1°C
时间1:25.2°C(丢失)
时间2:25.3°C
丢失1秒的数据不影响温度趋势分析
为什么UDP更适合?
- 数据频繁更新,丢失一两个数据点影响不大
- 最新数据最重要
- 历史数据无需重传
UDP vs TCP性能对比
1. 延迟对比
场景:发送1KB数据,RTT=50ms
TCP:
三次握手:1.5 RTT = 75ms
发送数据:0.5 RTT = 25ms
四次挥手:2 RTT = 100ms
总延迟:200ms
UDP:
发送数据:0.5 RTT = 25ms
总延迟:25ms
UDP延迟降低88%!
2. 吞吐量对比
场景:1Gbps网络,丢包率1%
TCP:
- 每次丢包都要重传
- 慢启动、拥塞避免
- 实际吞吐量:约700Mbps(带宽利用率70%)
UDP:
- 不重传,全速发送
- 实际吞吐量:约990Mbps(带宽利用率99%,丢包1%)
UDP吞吐量提升41%!
3. 开销对比
场景:发送1000字节数据
TCP:
头部:20字节
三次握手:60字节(3个SYN/ACK包)
数据:1020字节(20字节头部 + 1000字节数据)
四次挥手:80字节(4个FIN/ACK包)
总开销:160字节(不含数据)
总传输:1160字节
开销比例:13.8%
UDP:
头部:8字节
数据:1008字节(8字节头部 + 1000字节数据)
总开销:8字节
总传输:1008字节
开销比例:0.8%
UDP开销降低94%!
4. CPU开销对比
TCP:
- 维护连接状态(序列号、窗口、定时器)
- 计算RTO
- 重传队列管理
- 拥塞控制算法
- CPU占用:高
UDP:
- 无状态
- 只计算校验和
- CPU占用:极低
实测数据(Linux内核):
TCP:每秒处理100万个数据包,CPU占用50%
UDP:每秒处理500万个数据包,CPU占用10%
UDP性能提升5倍!
实战案例:UDP编程
Java UDP编程示例
UDP服务器
import java.net.*;
public class UDPServer {
public static void main(String[] args) throws Exception {
// 1. 创建DatagramSocket,监听9999端口
DatagramSocket socket = new DatagramSocket(9999);
System.out.println("UDP服务器启动,监听端口9999");
// 2. 创建接收缓冲区
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
// 3. 循环接收数据
while (true) {
// 接收数据(阻塞)
socket.receive(packet);
// 解析数据
String message = new String(packet.getData(), 0, packet.getLength());
InetAddress clientAddress = packet.getAddress();
int clientPort = packet.getPort();
System.out.println("收到来自 " + clientAddress + ":" + clientPort + " 的消息:" + message);
// 4. 发送响应
String response = "服务器收到:" + message;
byte[] responseData = response.getBytes();
DatagramPacket responsePacket = new DatagramPacket(
responseData,
responseData.length,
clientAddress,
clientPort
);
socket.send(responsePacket);
}
}
}
UDP客户端
import java.net.*;
public class UDPClient {
public static void main(String[] args) throws Exception {
// 1. 创建DatagramSocket(系统自动分配端口)
DatagramSocket socket = new DatagramSocket();
// 2. 准备要发送的数据
String message = "Hello UDP Server!";
byte[] data = message.getBytes();
// 3. 创建数据包(指定目标地址和端口)
InetAddress serverAddress = InetAddress.getByName("localhost");
DatagramPacket packet = new DatagramPacket(
data,
data.length,
serverAddress,
9999
);
// 4. 发送数据
socket.send(packet);
System.out.println("发送消息:" + message);
// 5. 接收响应
byte[] buffer = new byte[1024];
DatagramPacket responsePacket = new DatagramPacket(buffer, buffer.length);
socket.receive(responsePacket);
String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
System.out.println("收到响应:" + response);
// 6. 关闭Socket
socket.close();
}
}
运行:
# 启动服务器
java UDPServer
# 启动客户端
java UDPClient
# 输出(服务器)
UDP服务器启动,监听端口9999
收到来自 /127.0.0.1:54321 的消息:Hello UDP Server!
# 输出(客户端)
发送消息:Hello UDP Server!
收到响应:服务器收到:Hello UDP Server!
UDP的常见陷阱
陷阱1:数据包大小限制
问题:UDP数据包太大会被分片,增加丢包概率
// ❌ 错误:发送64KB数据
byte[] largeData = new byte[65536];
DatagramPacket packet = new DatagramPacket(largeData, largeData.length, address, port);
socket.send(packet);
// 问题:
// - IP层MTU通常为1500字节
// - UDP数据包会被分片成多个IP数据包
// - 任何一个分片丢失,整个UDP包丢失
// ✅ 正确:限制数据包大小
byte[] data = new byte[1400]; // 小于MTU - IP头部 - UDP头部
DatagramPacket packet = new DatagramPacket(data, data.length, address, port);
socket.send(packet);
最佳实践:
- 数据包大小 ≤ 1400字节(安全值)
- 或者查询MTU:
netstat -i
陷阱2:没有处理丢包
// ❌ 错误:假设数据一定到达
socket.send(packet);
// 继续下一步...
// ✅ 正确:应用层实现重传
socket.setSoTimeout(5000); // 设置5秒超时
try {
socket.send(packet);
socket.receive(responsePacket); // 等待响应
} catch (SocketTimeoutException e) {
// 超时,重传
socket.send(packet);
}
陷阱3:没有处理乱序
// ❌ 错误:假设数据按顺序到达
socket.receive(packet1);
socket.receive(packet2);
socket.receive(packet3);
// 假设收到的是1、2、3,但实际可能是2、1、3
// ✅ 正确:应用层实现排序
class UDPPacket {
int sequenceNumber;
byte[] data;
}
TreeMap<Integer, byte[]> receiveBuffer = new TreeMap<>();
socket.receive(packet);
UDPPacket udpPacket = deserialize(packet.getData());
receiveBuffer.put(udpPacket.sequenceNumber, udpPacket.data);
UDP的可靠性扩展
虽然UDP本身不可靠,但可以在应用层实现可靠性:
1. QUIC协议(HTTP/3)
QUIC = UDP + 可靠性 + 安全性
QUIC在UDP之上实现:
- ✅ 连接建立(0-RTT)
- ✅ 可靠传输(序列号、ACK、重传)
- ✅ 流量控制
- ✅ 拥塞控制
- ✅ 多路复用
- ✅ 加密(内置TLS 1.3)
优势:
- 比TCP更快(0-RTT连接建立)
- 没有队头阻塞(多路复用)
- 更好的移动网络支持(连接迁移)
2. KCP协议
KCP = UDP + TCP的可靠性 - TCP的延迟
KCP特点:
- ✅ 可靠传输(ARQ算法)
- ✅ 快速重传(比TCP更激进)
- ✅ 更低的延迟(牺牲带宽换取低延迟)
应用:游戏、实时通信
3. WebRTC
WebRTC = UDP + 实时媒体传输
WebRTC特点:
- ✅ 基于UDP(低延迟)
- ✅ SRTP加密(安全性)
- ✅ 丢包恢复(FEC前向纠错)
- ✅ 自适应码率(根据网络状况调整)
应用:视频会议、直播
总结
核心要点
UDP极简设计:
- 头部仅8字节(TCP至少20字节)
- 无连接、无确认、无重传、无流量控制、无拥塞控制
UDP的不可靠性:
- 不保证数据到达
- 不保证顺序
- 不保证不重复
- 不保证不损坏(虽然有校验和,但可选)
UDP的优势场景:
- 实时性要求高(视频、游戏)
- 广播和多播(设备发现、IPTV)
- 简单请求-响应(DNS、SNMP)
- 不需要可靠性(传感器数据)
性能对比:
- 延迟降低88%
- 吞吐量提升41%
- 开销降低94%
- CPU占用降低80%
与下一篇的关联
本文讲解了UDP协议的极简设计和优势场景。下一篇是传输层协议篇的最后一篇,我们将总结TCP与UDP的选择策略,理解:
- 什么时候用TCP?什么时候用UDP?
- 微服务场景的协议选择
- RPC框架的协议选择(Dubbo、gRPC)
- 如何在UDP之上实现可靠性
思考题
- 为什么DNS使用UDP而不是TCP?
- 如果要用UDP实现可靠传输,需要在应用层实现哪些机制?
- QUIC协议基于UDP,为什么比TCP更快?
- 什么场景下绝对不能使用UDP?
下一篇预告:《TCP与UDP的选择策略:微服务场景的协议决策》