跳到主要内容

UDP 是怎么工作的

UDP 协议概述

UDP (User Datagram Protocol) 是传输层的一种无连接、不可靠的数据报协议。与 TCP 不同,UDP 不提供连接建立、数据确认、重传等机制,而是以最小的开销提供数据传输服务。

UDP 报文结构

UDP 报文头部非常简单,只有 8 字节:

字段说明:

  • 源端口号: 发送方的端口号
  • 目标端口号: 接收方的端口号
  • UDP 长度: UDP 头部 + 数据的总长度
  • UDP 校验和: 用于错误检测

UDP 的核心特性

无连接特性

UDP 不需要在数据传输前建立连接,应用程序可以直接发送数据:

// UDP 客户端示例
func udpClient() {
// 直接发送,无需建立连接
conn, err := net.Dial("udp", "localhost:8080")
if err != nil {
log.Fatal(err)
}
defer conn.Close()

// 立即发送数据
conn.Write([]byte("Hello UDP"))
}

不可靠传输

UDP 不保证数据包的到达、顺序或完整性:

// UDP 服务端示例 - 可能丢失数据包
func udpServer() {
addr, _ := net.ResolveUDPAddr("udp", ":8080")
conn, _ := net.ListenUDP("udp", addr)
defer conn.Close()

buffer := make([]byte, 1024)
for {
n, clientAddr, err := conn.ReadFromUDP(buffer)
if err != nil {
continue // 忽略错误,继续接收
}

fmt.Printf("从 %s 接收到: %s\n", clientAddr, string(buffer[:n]))
// 注意:数据包可能丢失、重复或乱序
}
}

面向数据报

UDP 保持数据报的边界,每次发送的数据作为一个独立的数据报:

// 数据报边界示例
func datagramBoundary() {
conn, _ := net.Dial("udp", "localhost:8080")

// 发送三个独立的数据报
conn.Write([]byte("消息1"))
conn.Write([]byte("消息2"))
conn.Write([]byte("消息3"))

// 接收端会收到三个独立的数据报
// 而不是连接成一个字节流
}

UDP 的应用场景

实时通信应用

DNS 查询: 快速的域名解析

// DNS 查询示例
func dnsQuery() {
conn, _ := net.Dial("udp", "8.8.8.8:53")

// 构造 DNS 查询包
query := buildDNSQuery("example.com")
conn.Write(query)

// 读取响应
response := make([]byte, 512)
conn.Read(response)

// DNS 使用 UDP 因为:
// 1. 查询快速,不需要连接开销
// 2. 如果失败,客户端可以重试
// 3. 响应通常很小,适合单个数据报
}

在线游戏: 实时性比可靠性更重要

流媒体应用

音视频流: 连续的数据流,偶尔丢失可以容忍

// 简化的音频流发送
func audioStream() {
conn, _ := net.Dial("udp", "receiver:8080")

for {
audioFrame := captureAudioFrame() // 捕获音频帧

// 添加序列号用于检测丢包
packet := createAudioPacket(audioFrame, sequenceNumber)
conn.Write(packet)

sequenceNumber++
time.Sleep(20 * time.Millisecond) // 50fps 音频

// 如果某帧丢失,继续发送下一帧
// 音频播放可以容忍偶尔的丢失
}
}

广播和组播

UDP 支持一对多的通信模式:

// UDP 广播示例
func udpBroadcast() {
conn, _ := net.Dial("udp", "255.255.255.255:8080")

// 向网络中所有设备广播消息
message := "服务发现广播"
conn.Write([]byte(message))

// 典型应用:
// - DHCP 服务发现
// - 局域网游戏发现
// - 设备自动发现
}

UDP vs TCP 对比

特性UDPTCP
连接性无连接面向连接
可靠性不可靠可靠传输
速度相对较慢
开销低 (8字节头部)高 (20字节头部)
数据边界保持不保持
流量控制
拥塞控制

UDP 的性能特点

1. 低延迟

  • 无连接建立开销
  • 无确认等待时间
  • 头部开销小

2. 高吞吐量

// UDP 性能测试示例
func udpPerformanceTest() {
conn, _ := net.Dial("udp", "localhost:8080")

start := time.Now()
packetCount := 100000

for i := 0; i < packetCount; i++ {
conn.Write([]byte("test packet"))
}

duration := time.Since(start)
fmt.Printf("发送 %d 个包用时: %v\n", packetCount, duration)
fmt.Printf("吞吐量: %.2f 包/秒\n", float64(packetCount)/duration.Seconds())
}

3. 内存使用

  • 无需维护连接状态
  • 无发送/接收缓冲区
  • 适合处理大量并发客户端

UDP 编程最佳实践

应用层可靠性

对于需要可靠性的应用,在应用层实现:

// 应用层确认机制
type ReliableUDP struct {
conn *net.UDPConn
sequenceID uint32
ackTimeout time.Duration
}

func (r *ReliableUDP) SendWithAck(data []byte) error {
packet := createPacketWithID(data, r.sequenceID)

for retries := 0; retries < 3; retries++ {
r.conn.Write(packet)

// 等待确认
if r.waitForAck(r.sequenceID, r.ackTimeout) {
r.sequenceID++
return nil
}
}

return errors.New("发送失败,超过重试次数")
}

数据包大小控制

避免IP分片:

const MAX_UDP_PAYLOAD = 1472 // 以太网MTU(1500) - IP头(20) - UDP头(8)

func sendLargeData(data []byte) {
conn, _ := net.Dial("udp", "localhost:8080")

// 分片发送大数据
for offset := 0; offset < len(data); offset += MAX_UDP_PAYLOAD {
end := offset + MAX_UDP_PAYLOAD
if end > len(data) {
end = len(data)
}

chunk := data[offset:end]
packet := createChunkPacket(chunk, offset, len(data))
conn.Write(packet)
}
}

错误处理

func robustUDPServer() {
addr, _ := net.ResolveUDPAddr("udp", ":8080")
conn, _ := net.ListenUDP("udp", addr)
defer conn.Close()

buffer := make([]byte, 1024)
for {
conn.SetReadDeadline(time.Now().Add(5 * time.Second))

n, clientAddr, err := conn.ReadFromUDP(buffer)
if err != nil {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
continue // 超时继续
}
log.Printf("读取错误: %v", err)
continue
}

// 验证数据包完整性
if !validatePacket(buffer[:n]) {
continue
}

go handlePacket(buffer[:n], clientAddr, conn)
}
}

总结

UDP 是一个简单、高效的传输协议,适用于以下场景:

  1. 实时性要求高: 游戏、音视频流
  2. 可以容忍数据丢失: 传感器数据、状态更新
  3. 简单的请求-响应: DNS、SNMP
  4. 一对多通信: 广播、组播应用

选择 UDP 还是 TCP 取决于应用的具体需求。UDP 牺牲了可靠性换取了性能,因此在使用时需要在应用层考虑数据完整性和重传机制。

通过理解 UDP 的工作原理和特性,我们可以在合适的场景下充分发挥其优势,构建高效的网络应用。