应优先使用 net.ListenPacket 而非 net.ListenUDP:它更现代、通用,自动适配 IPv4/IPv6 双栈,返回统一的 net.PacketConn 接口,避免手动构造 net.UDPAddr 和类型断言;仅在需端口复用或限定地址族时选用后者。

UDP收发用 net.ListenPacket 而不是 net.ListenUDP
直接上结论:除非你要做端口复用或绑定特定地址族(比如仅 IPv6),否则 net.ListenPacket 是更现代、更通用的选择。它返回的 net.PacketConn 接口统一处理 UDP/TCP(虽 TCP 不常用),且避免了 net.UDPAddr 手动构造和类型断言的冗余。
常见错误是看到文档里有 net.ListenUDP 就默认用它,结果在需要接收非本地地址(如容器外发包)、或想兼容 IPv4/IPv6 双栈时卡住——net.ListenUDP 返回的是具体类型 *net.UDPConn,而 net.ListenPacket 自动适配底层协议族。
-
net.ListenPacket("udp", ":8080")会自动监听所有可用网络接口的 IPv4 和 IPv6(若系统支持) - 如果只要 IPv4,显式写
"udp4";只要 IPv6 写"udp6",避免模糊匹配带来的端口占用冲突 - 绑定失败常见原因是端口被占用,或没有权限绑定
接收数据必须用 ReadFrom,不能用 Read
net.PacketConn 的核心是面向“包”的:每次调用 ReadFrom 拿到一整份 UDP 数据 + 对端地址,这是语义必需的。UDP 无连接,不维护会话上下文,Read 会报 "operation not supported" 错误——因为没地方存对端地址。
典型错误现象:程序卡在 conn.Read(buf),或者 panic 提示 invalid memory address(因没初始化 buf 或长度为 0)。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 务必用
n, addr, err := conn.ReadFrom(buf),其中addr是net.Addr接口,通常可直接转成*net.UDPAddr用于回包 -
buf长度建议 ≥ 1500(以太网 MTU),避免截断;太小会导致丢包且无提示 - UDP 不保证送达,也不保证顺序,应用层需自行处理重传或校验逻辑
发送数据用 WriteTo,注意地址类型要匹配
发包必须带目标地址,用 WriteTo。常见坑是把 net.UDPAddr 和字符串地址混用,或忽略地址解析失败。
比如你从 ReadFrom 收到一个 net.Addr,直接传给 WriteTo 没问题;但如果你手动构造,得确保类型一致:
- 正确:
conn.WriteTo([]byte("hi"), &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 9000}) - 错误:
conn.WriteTo([]byte("hi"), "127.0.0.1:9000")—— 这会 panic,因为WriteTo第二个参数必须是net.Addr - 如果目标地址来自用户输入,先用
net.ResolveUDPAddr("udp", "host:port")解析,别自己拼字符串
并发读写要自己加锁,net.PacketConn 不是线程安全的
Go 的 net.Conn 系列接口不承诺并发安全,net.PacketConn 同样如此。多个 goroutine 同时调用 ReadFrom 或 WriteTo 可能导致数据错乱、panic 或读到别人的包。
典型表现:服务运行几分钟后突然收不到包,或日志里出现重复的 read: connection refused(其实是并发写冲突触发了底层 fd 错误)。
- 最简单做法:一个 goroutine 专职
ReadFrom,收到包后发到 channel;另一个或多个 goroutine 从 channel 拿数据并处理、回包 - 回包时仍用同一个
conn,但只允许一个 goroutine 调用WriteTo,或用sync.Mutex包裹 - 不要试图用
conn.SetDeadline来“控制并发”——它只影响单次 I/O,不解决竞争问题
UDP 的轻量是假象,真正难的是边界:包大小、地址解析、并发模型、错误静默。这些地方一旦漏掉检查,问题往往延迟暴露,而且很难复现。

















