UDP发送端必须设置写超时(如SetWriteDeadline),ReadFromUDP需校验返回长度并处理截断,多实例共用端口需手动启用SO_REUSEADDR,且UDP无连接状态,地址须每次显式传递。

UDP发送端必须设置超时,否则WriteToUDP可能永久阻塞
Go 的 *net.UDPConn 默认是阻塞模式,WriteToUDP 在网络异常(如目标主机不可达、路由中断)时不会立即失败,而是等待系统底层 ICMP 错误返回——这个过程可能长达数分钟。实际部署中极易导致 goroutine 积压。
- 务必在创建连接后调用
SetWriteDeadline或SetWriteTimeout - 推荐用
SetWriteDeadline(time.Now().Add(2 * time.Second)),而非固定超时值 - 错误检查不能只看
err != nil,要区分net.ErrWriteTimeout和真实网络错误
conn, _ := net.ListenUDP("udp", &net.UDPAddr{Port: 0})
defer conn.Close()
conn.SetWriteDeadline(time.Now().Add(2 * time.Second))
_, err := conn.WriteToUDP([]byte("ping"), &net.UDPAddr{IP: net.ParseIP("10.0.0.1"), Port: 8080})
if err != nil {
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
log.Println("write timeout")
} else {
log.Printf("write failed: %v", err)
}
}
ReadFromUDP返回的n长度必须校验,UDP数据报可能被截断
UDP 是无连接、不可靠协议,内核接收缓冲区大小有限(通常 212992 字节 Linux 默认),当单个 UDP 包超过 MTU(一般 1500)且开启 IP 分片时,任意一片丢失都会导致整包丢弃;但更常见的是应用层读取缓冲区太小,ReadFromUDP 只写入前 len(buf) 字节并截断剩余内容,而不会报错。
- 接收缓冲区建议至少
65536字节(IPv4 最大 UDP 数据报理论值) - 必须检查返回的
n是否等于预期消息长度,或按协议自定义分隔符解析 - 不要假设
ReadFromUDP一次读完一个完整业务消息——UDP 本身不保证“消息边界”语义
buf := make([]byte, 65536)
for {
n, addr, err := conn.ReadFromUDP(buf)
if err != nil {
log.Printf("read error: %v", err)
continue
}
if n == 0 {
continue // 忽略空包
}
// 实际业务逻辑:解析 buf[:n]
handleMessage(buf[:n], addr)
}多个UDP服务共用同一端口需用SO_REUSEADDR,但Go默认不启用
Linux/macOS 下,若已有进程绑定 :8080,后续 ListenUDP 默认会报 address already in use。虽然 TCP 场景常用 SO_REUSEADDR 解决端口复用,但 Go 标准库的 net.ListenUDP 不暴露 socket 选项控制,必须通过 net.ListenConfig + Control 函数手动设置。
- 仅当明确需要多实例监听同一端口(如负载分散、热升级)时才启用
- Windows 不支持
SO_REUSEADDR用于 UDP 多播/单播混用场景,行为不一致 - 启用后仍需确保各实例处理逻辑互斥,避免重复消费
lc := net.ListenConfig{
Control: func(fd uintptr) {
syscall.SetsockoptInt32(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
},
}
l, err := lc.ListenPacket(context.Background(), "udp", ":8080")
if err != nil {
log.Fatal(err)
}
conn, _ := l.(*net.UDPConn)UDP没有连接状态,WriteToUDP和ReadFromUDP必须配对使用地址
与 TCP 不同,UDP 连接对象 *net.UDPConn 本身不维护远端地址。即使你用 net.DialUDP 创建了“已连接”的 conn,其内部也只是缓存了对端地址,WriteToUDP 仍允许传入不同地址(此时会忽略 conn 自带地址),而 ReadFromUDP 总是返回实际发包方地址。这意味着无法靠 conn 自身判断“谁发来的”或“该回给谁”,每次收发都必须显式处理 *net.UDPAddr。
- 不要依赖
DialUDP创建的 conn 做“单向通信”假设——它只是语法糖 - 服务器场景下,必须保存每次
ReadFromUDP返回的addr,再用它调用WriteToUDP回复 - 客户端若需并发请求多个地址,应复用同一
UDPConn,避免频繁创建销毁 fd
最容易被忽略的一点:UDP 没有“连接关闭”概念,Close() 只是释放本地 fd,对端完全无感知。因此心跳、重连、会话超时等逻辑全部要应用层自己实现。










