通八洲科技

UDP 通信双向收发的正确实现方式

日期:2025-12-26 00:00 / 作者:心靈之曲

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 基础上,稳定实现请求-响应式的双向通信。