udp客户端应使用writetoudp绕过dialudp的connect检查,错误延迟至发送时暴露;服务端需设读超时、合理缓冲区并避免buffer复用;地址复用需listenconfig+control设置so_reuseaddr;udp无内置可靠性,高可靠场景建议换tcp或quic。

UDP客户端如何正确发送数据并避免“connection refused”
Go 的 net.DialUDP 会尝试建立连接语义(即绑定远端地址),但 UDP 本身无连接;如果目标端口没程序监听,Linux 内核会返回 ICMP “port unreachable”,Go 将其转为 connect: connection refused 错误。这不是代码写错了,而是服务端根本没起来。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 先用
nc -u localhost 8080或telnet -u localhost 8080确认服务端已监听 - 客户端别依赖
DialUDP成功就代表能发——它只校验本地 socket 创建和远端地址合法性,不探测对方是否在线 - 真正发送时用
WriteToUDP配合net.UDPAddr,绕过 connect 步骤,错误延迟到第一次 send 时才暴露 - 若需静默丢包(如监控打点),可忽略
WriteToUDP返回的 error;否则必须检查,否则发不出去还浑然不觉
UDP服务端如何避免读取阻塞或丢包
ReadFromUDP 是阻塞调用,且每次只读一个 UDP 数据报(datagram)。常见误区是把它当 TCP 流来读,或在 for 循环里不加超时控制,导致整个 goroutine 卡死。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 务必给 listener 设置读超时:
conn.SetReadDeadline(time.Now().Add(5 * time.Second)),否则一次卡住就永远卡住 - UDP 包大小受 MTU 限制(通常 IPv4 是 65507 字节),但应用层 buffer 建议设为 65536,避免截断;
make([]byte, 65536)是安全起点 - 不要复用同一块 buffer 跨 goroutine 读——
ReadFromUDP是覆盖写入,若并发处理需拷贝有效字节(data[:n]) - 高并发场景下,单个 UDP listener + goroutine 模型容易成为瓶颈;可考虑用
runtime.GOMAXPROCS配合多个 worker 从 channel 消费数据,但注意 channel 容量要足够,否则丢包
如何处理 UDP 的地址复用(SO_REUSEADDR)和端口冲突
Go 默认创建 UDP socket 时不启用 SO_REUSEADDR,所以如果前一个进程没干净退出(比如 panic 后 socket 还在 TIME_WAIT 类似状态),新服务启动会报 bind: address already in use。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 使用
net.ListenUDP时无法直接设 socket option;必须用底层net.ListenConfig+Control回调:
lc := net.ListenConfig{
Control: func(network, address string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) {
syscall.SetsockoptInt(​fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
})
},
}
ln, err := lc.ListenPacket(context.Background(), "udp", ":8080")
SO_EXCLUSIVEADDRUSE 才能真正复用,但 Go 标准库不支持,得用 golang.org/x/sys/windows 手动调用sudo sysctl -w net.ipv4.ip_local_port_range="1024 65535" 并缩短 net.ipv4.tcp_fin_timeout,但仅限调试UDP通信中如何可靠传递或检测丢包
UDP 本身不保证送达、不排序、不重传。所谓“可靠 UDP”必须自己实现,比如加序号、ACK、超时重发——但这已经不是标准 UDP 编程,而是应用层协议设计。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 简单场景(如日志上报、心跳):加时间戳 + 自增序列号字段,服务端记录最近收到的 seq,发现跳变就告警,但不重传
- 需要确认机制时,避免在同一个 conn 上同步等待 ACK——这会让吞吐暴跌;应把请求 ID 存 map,用单独 goroutine 定时扫描超时项
- 别用
time.Sleep实现重试,用time.AfterFunc或 timer channel,防止 goroutine 泄漏 - 如果业务对可靠性要求高,直接换 TCP 或 QUIC;硬在 UDP 上堆逻辑,最后往往比用 TCP 更难维护
UDP 的“轻量”是假象,它的容错边界非常窄:没有连接状态帮你兜底,没有流控帮你减速,连地址复用都要手动拧螺丝。写完第一版后,一定要用 ss -uln 看端口、用 tcpdump -i lo udp port 8080 抓包、用 kill -9 模拟服务端崩溃——否则上线后丢包无声无息,排查成本远高于初期多写几行健壮逻辑。











