udp 是无连接协议,`listenudp` 创建的连接无法直接使用 `write` 发送响应,必须通过 `readfromudp` 获取客户端地址,再用 `writetoudp` 显式指定目标地址才能实现双向通信。
在 Go 中使用 net.ListenUDP 启动 UDP 服务端时,得到的是一个 *net.UDPConn,它不维护连接状态,因此调用 conn.Write([]byte{...}) 实际上等价于向一个未指定目标地址的“空连接”写入数据——该操作会静默失败(或返回 nil 错误但被忽略),Wireshark 无法捕获任何发出的报文,正是这个原因。
正确做法是:使用 ReadFromUDP 接收数据的同时获取发送方的网络地址(*net.UDPAddr),再通过 WriteToUDP 将响应明确发送回该地址。以下是修正后的完整服务端(Node 1)代码:
func main() {
addr := &net.UDPAddr{Port: 7000, IP: net.ParseIP("127.0.0.1")}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
panic(err)
}
defer conn.Close()
fmt.Println("UDP server listening on :7000")
for {
buf := make([]byte, 1024) // 建议增大缓冲区,避免截断
n, clientAddr, err := conn.ReadFromUDP(buf)
if err != nil {
log.Printf("Read error: %v", err)
continue
}
msg := strings.TrimSpace(string(buf[:n]))
fmt.Printf("Received from %v: %q\n", clientAddr, msg)
reply := []byte("sending back")
_, err = conn.WriteToUDP(reply, clientAddr)
if err != nil {
log.Printf("WriteToUDP error to %v: %v", clientAddr, err)
}
}
}客户端(Node 2)可保持原样(使用 net.Dial 亦可,因其内部会绑定临时端口并记录对端地址),但建议显式处理错误并增加超时控制:
func main() {
conn, err := net.Dial("udp", "127.0.0.1:7000")
if err != nil {
panic(err)
}
defer conn.Close()
_, _ = conn.Write([]byte("first send"))
buf := make([]byte, 1024)
conn.SetReadDeadline(time.Now().Add(3 * time.Second))
n, err := conn.Read(buf)
if err != nil {
log.Fatal("Read timeout or error:", err)
}
fmt.Println("Reply:", string(buf[:n]))
}⚠️ 注意事项:
可靠性(如重传、序列号);通过上述改造,即可在无连接的 UDP 基础上,稳定实现请求-响应式的双向通信。