最常见原因是服务端绑定地址写死为127.0.0.1,导致仅监听本地回环接口,而客户端从其他网卡(如192.168.1.100)发包时内核直接丢弃;应改用":8080"或&net.udpaddr{port:8080}监听所有接口,并检查防火墙及接收缓冲区大小。

UDP服务端监听时为什么收不到包
最常见原因是绑定地址写死了 127.0.0.1,但客户端从其他网卡(比如 192.168.1.100)发包,导致内核直接丢弃。UDP不走连接握手,没“accept”环节,收不到就是地址不匹配。
- 用
":8080"或&net.UDPAddr{Port: 8080}替代"127.0.0.1:8080",让服务端监听所有接口 - 检查防火墙:
sudo ufw status(Ubuntu)或sudo pfctl -sr(macOS),确认 UDP 端口未被拦截 - 接收 buffer 至少开到
65536字节:buf := make([]byte, 65536);小于 1472 可能截断,小于 64KB 在某些系统上会静默丢包 - 别用
net.ListenPacket("udp", ...)—— 它返回net.PacketConn,缺少ReadFromUDP方法,语义不清,99% 场景该用*net.UDPConn
UDP客户端发完就退出,对方却收不到
Go 的 net.DialUDP 创建的是“伪连接” socket,Write 调用返回成功只代表数据进了内核发送缓冲区,程序立刻退出会导致缓冲区来不及刷出。本地测试常表现为“发了但收不到”,线上更隐蔽。
- 发完加
time.Sleep(1 * time.Millisecond)是最快验证手段(仅限开发环境) - 生产环境应设写超时:
conn.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)),再调conn.Write(...),检查 err 是否为nil或net.ErrWriteTimeout - 如果只是单次发包(如心跳探测),改用
net.ListenUDP("udp", &net.UDPAddr{Port: 0})+conn.WriteToUDP(data, dst),避免维护连接状态
ReadFromUDP 返回长度比预期小,但没报错
这不是 Go 的 bug,是 UDP 协议层行为:超过 MTU(通常 IPv4 为 1500 字节)的数据包会被分片,而任意一片丢失,整个包就收不到;部分系统默认禁用 IP 分片,导致超长包直接被丢弃。你看到的“短数据”,大概率是中间某个设备(如路由器)做了截断或路径 MTU 发现失败。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 发送前强制限制:
if len(data) > 1472 { /* 拆包 or return error */ } - 接收端永远按
buf[:n]解析,不要假设n == len(buf) - 别依赖
conn.LocalAddr().(*net.UDPAddr).Port判断端口复用是否成功——SO_REUSEADDR在 Linux 和 macOS 行为不一致,macOS 更严格
并发处理多个 UDP 客户端请求要注意什么
UDP 本身无连接,但业务上常需“识别客户端+响应”。一个 *net.UDPConn 可同时处理成百上千个源地址,关键在读取模型,而非连接数。
- 每个
ReadFromUDP调用都会阻塞,所以必须用 goroutine 封装循环读取:go func() { for { conn.ReadFromUDP(...) } }() - 别在同一个 goroutine 里混用
SetReadDeadline和长耗时逻辑,超时重设时机错位会导致漏包 - 收到包后立即起新 goroutine 处理:
go handlePacket(buf[:n], clientAddr),防止慢处理阻塞后续ReadFromUDP -
conn.Close()必须调用,否则文件描述符泄漏;用defer conn.Close()最稳妥
缓冲区大小、地址绑定范围、写后立即退出、MTU 边界——这些点单独看都简单,但组合起来最容易在线上突然失效。尤其是跨平台部署时,macOS 和 Linux 对 SO_REUSEADDR 和分片的处理差异,会让本地测通的代码在服务器上静默丢包。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










