引言

在前面的文章中,我们深入学习了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 Port16位(2字节)源端口号(可选,不需要时可以设为0)
Destination Port16位(2字节)目标端口号(必须)
Length16位(2字节)UDP报文段总长度(头部8字节 + 数据长度)
Checksum16位(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前向纠错)
- ✅ 自适应码率(根据网络状况调整)

应用:视频会议、直播

总结

核心要点

  1. UDP极简设计

    • 头部仅8字节(TCP至少20字节)
    • 无连接、无确认、无重传、无流量控制、无拥塞控制
  2. UDP的不可靠性

    • 不保证数据到达
    • 不保证顺序
    • 不保证不重复
    • 不保证不损坏(虽然有校验和,但可选)
  3. UDP的优势场景

    • 实时性要求高(视频、游戏)
    • 广播和多播(设备发现、IPTV)
    • 简单请求-响应(DNS、SNMP)
    • 不需要可靠性(传感器数据)
  4. 性能对比

    • 延迟降低88%
    • 吞吐量提升41%
    • 开销降低94%
    • CPU占用降低80%

与下一篇的关联

本文讲解了UDP协议的极简设计和优势场景。下一篇是传输层协议篇的最后一篇,我们将总结TCP与UDP的选择策略,理解:

  • 什么时候用TCP?什么时候用UDP?
  • 微服务场景的协议选择
  • RPC框架的协议选择(Dubbo、gRPC)
  • 如何在UDP之上实现可靠性

思考题

  1. 为什么DNS使用UDP而不是TCP?
  2. 如果要用UDP实现可靠传输,需要在应用层实现哪些机制?
  3. QUIC协议基于UDP,为什么比TCP更快?
  4. 什么场景下绝对不能使用UDP?

下一篇预告:《TCP与UDP的选择策略:微服务场景的协议决策》