在Go中处理读/写udp连接

时间:2014-02-23 12:36:53

标签: go udp

我需要创建UDP连接,通过它我可以同时写入和读取数据包。 (使用不同的goroutines和GOMAXPROCS(n),其中n> 1)首次尝试是这样的:

func new_conn(port, chan_buf int) (conn *net.UDPConn, inbound chan Packet, err error) {
    inbound = make(chan Packet, chan_buf)

    conn, err = net.ListenUDP("udp4", &net.UDPAddr{Port: port})
    if err != nil {return}

    go func () {
        for {
            b := make([]byte, UDP_PACKET_SIZE)
            n, addr, err := conn.ReadFromUDP(b)
            if err != nil {
                log.Printf("Error: UDP read error: %v", err)
                continue
            }
            inbound <- Packet{addr, b[:n]}
        }
    }
}

因此,要读取数据包,我使用 packet:=&lt; - inbound 并写入 conn.WriteTo(data_bytes,remote_addr)。但是race detector会在连接上发出同时读/写警告。所以我将代码重写为:

func new_conn(port, chan_buf int) (inbound, outbound chan Packet, err error) {
    inbound = make(chan Packet, chan_buf)
    outbound = make(chan Packet, chan_buf)

    conn, err = net.ListenUDP("udp4", &net.UDPAddr{Port: port})
    if err != nil {return}

    go func () {
        for {
            select {
            case packet := <- outbound:
                _, err := conn.WriteToUDP(packet.data, packet.addr)
                if err != nil {
                    log.Printf("Error: UDP write error: %v", err)
                    continue
                }
            default:
                b := make([]byte, UDP_PACKET_SIZE)
                n, addr, err := conn.ReadFromUDP(b)
                if err != nil {
                    log.Printf("Error: UDP read error: %v", err)
                    continue
                }
                inbound <- Packet{addr, b[:n]}
            }
        }
    }
}

此代码不再触发竞争条件,但如果没有入站数据包,则存在阻止goroutine的风险。我看到的唯一解决方案是在调用ReadFromUDP之前调用类似 SetReadDeadline(time.Now()+ 10 * time.Millisecond)的内容。这段代码可能会有用,但我不太喜欢它。是否有更优雅的方法来解决这个问题?

UPD:警告讯息:

==================
WARNING: DATA RACE
Read by goroutine 553:
  net.ipToSockaddr()
      /usr/local/go/src/pkg/net/ipsock_posix.go:150 +0x18a
  net.(*UDPAddr).sockaddr()
      /usr/local/go/src/pkg/net/udpsock_posix.go:45 +0xd9
  net.(*UDPConn).WriteToUDP()
      /usr/local/go/src/pkg/net/udpsock_posix.go:123 +0x4df
  net.(*UDPConn).WriteTo()
      /usr/local/go/src/pkg/net/udpsock_posix.go:139 +0x2f6
  <traceback which points on conn.WriteTo call>

Previous write by goroutine 556:
  syscall.anyToSockaddr()
      /usr/local/go/src/pkg/syscall/syscall_linux.go:383 +0x336
  syscall.Recvfrom()
      /usr/local/go/src/pkg/syscall/syscall_unix.go:223 +0x15c
  net.(*netFD).ReadFrom()
      /usr/local/go/src/pkg/net/fd_unix.go:227 +0x33c
  net.(*UDPConn).ReadFromUDP()
      /usr/local/go/src/pkg/net/udpsock_posix.go:67 +0x164
  <traceback which points on conn.ReadFromUDP call>

Goroutine 553 (running) created at:
  <traceback>

Goroutine 556 (running) created at:
  <traceback>
==================

2 个答案:

答案 0 :(得分:3)

根据来自竞争检测器的跟踪,检测到的竞争似乎是由于在后续写入中重用了读取调用返回的UDPAddr。特别是,IP字段引用的数据。

目前尚不清楚这是否真的存在问题,因为syscall.ReadFrom正在为每次调用分配一个新的地址结构,并且不会长期保留该结构。您可以尝试在将地址发送到出站goroutine之前复制该地址。例如:

newAddr := new(net.UDPAddr)
*newAddr = *addr
newAddr.IP = make(net.IP, len(addr.IP))
copy(newAddr.IP, add.IP)

但是,如果不了解您的计划,很难说出为什么这会被标记为竞赛。也许这足以让你指出正确的方向。根据您发布的内容,我无法使用此测试程序重现比赛:http://play.golang.org/p/suDG6hCYYP

答案 1 :(得分:1)

为什么不开始两个goroutines,一个用于写入,一个用于阅读并且是全双工的?即:

func new_conn(port, chan_buf int) (inbound, outbound chan Packet, err error) {
    inbound = make(chan Packet, chan_buf)
    outbound = make(chan Packet, chan_buf)

    conn, err = net.ListenUDP("udp4", &net.UDPAddr{Port: port})
    if err != nil {
        return
    }

    go func() {
        for packet := range outbound {
            _, err := conn.WriteToUDP(packet.data, packet.addr)
            if err != nil {
                log.Printf("Error: UDP write error: %v", err)
                continue
            }
        }
    }()

    go func() {

        b := make([]byte, UDP_PACKET_SIZE)
        for {

            n, addr, err := conn.ReadFromUDP(b)
            if err != nil {
                log.Printf("Error: UDP read error: %v", err)
                continue
            }
            b2 := make([]byte, UDP_PACKET_SIZE)
            copy(b2, b)
            inbound <- Packet{addr, b2[:n]}
        }
    }()

}