2014-02-23 30 views
7

我需要創建UDP連接,通過它我可以同時寫入和讀取數據包。 (使用不同夠程並用GOMAXPROCS(n),其中N> 1)首先嚐試是這樣的:在Go中處理讀/寫udp連接

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]} 
     } 
    } 
} 

所以讀取包我用分組:= < - 入站和寫入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]} 
      } 
     } 
    } 
} 

此代碼將不再觸發競爭狀態,但有如果沒有入站數據包阻塞夠程的風險。我所看到的解決方案是在調用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> 
================== 
+0

確切的錯誤是什麼? – Arjan

+0

這不是一個錯誤,而是警告。如果我用--race標誌運行第一個例子,我會從比賽檢測中得到警告。 (我會嘗試重新創建這個警告並將它粘貼到下一個註釋中) – user3234005

+0

它可能會抱怨你在兩個goroutine(它的指針)中訪問同一個變量,但是實現Conn的任何東西都可以處理多個goroutines來訪問它:「Multiple goroutines可能會同時在康恩上調用方法。「 – Arjan

回答

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

+0

似乎UDPAddr確實存在問題。在調用WriteTo之前複製它似乎解決了種族警告的問題。但它很奇怪,因爲程序必須像服務器一樣工作,即從入站數據包中重用地址,因此讀取總是在寫入後進行。或者可以讓比賽探測器以非常接近的讀寫方式發生誤報?那麼,無論如何,這是進一步改進的一個很好的起點。感謝您的幫助。 – user3234005

+0

我也有點驚訝,因爲我在答案結尾處鏈接的測試程序不會觸發比賽檢測器。也許看看你的代碼如何與簡單的例子不同,看看是否可以解釋這個問題。 –

1

爲什麼不啓動兩個夠程,一個用於寫,一個用於讀取並全雙工?即:

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]} 
     } 
    }() 

} 
+0

這種方法基本上等於我的文章中的第一個。所以它會引發競爭條件。 – user3234005

+0

你實際上不需要爲這兩者都有相同的'conn',它是UDP,而不是TCP。 –

+0

但是,如果我嘗試打開同一端口上的第二個連接,我會收到錯誤「bind:address already in use」。從一個端口讀取並寫入另一個端口不是一個解決方案,因爲我需要從同一個端口進行寫入和讀取。 – user3234005