英文:
Handling read/write udp connection in Go
问题
我需要创建一个UDP连接,通过该连接我可以同时写入和读取数据包(使用不同的goroutine和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 := <- inbound来读取数据包,使用**conn.WriteTo(data_bytes, remote_addr)**来写入数据包。但是竞争检测器会在连接的同时读写时发出警告。因此,我将代码重写为以下形式:
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)**的方法。这段代码可能会起作用,但我不太喜欢它。是否有更优雅的方法来解决这个问题?
**更新:**警告信息如下:
==================
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>
==================
英文:
I need to create UDP connection through which I could write and read packets simultaneously. (using different goroutines and with GOMAXPROCS(n) where n>1) First attempt was something like this:
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]}
}
}
}
So to read packet I used packet := <- inbound and to write conn.WriteTo(data_bytes, remote_addr). But race detector issues warnings on simultaneous read/write on connection. So I rewrite code to something like this:
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]}
}
}
}
}
This code will no more trigger race condition, but have risk of blocking goroutine if there is no inbound packets. Only solution which I see is to call something like SetReadDeadline(time.Now()+10*time.Millisecond) before calling ReadFromUDP. This code will probably work, but I don't like it so much. Is there more elegant ways to solve this problem?
UPD: Warning message:
==================
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>
==================
答案1
得分: 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
英文:
According to the trace from the race detector, the detected race appears to be due to the reuse of a UDPAddr
returned by a read call in a subsequent write. In particular, the data its IP
field references.
It's not clear that this is really a problem though, since syscall.ReadFrom
is allocating a new address structure on every call and doesn't hold on to that structure long term. You could try copying the address prior to sending it to your outbound goroutine. For example:
newAddr := new(net.UDPAddr)
*newAddr = *addr
newAddr.IP = make(net.IP, len(addr.IP))
copy(newAddr.IP, add.IP)
But without knowing more about your program, it is difficult to tell why this is being flagged as a race. Perhaps it is enough to point you in the right direction though. I wasn't able to reproduce the race using this test program based on what you've posted: http://play.golang.org/p/suDG6hCYYP
答案2
得分: 2
为什么不同时启动两个goroutine,一个用于写入,一个用于读取,以实现全双工通信?即:
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]}
}
}()
}
英文:
Why not start two goroutines, one for writing and one for reading and be full duplex? i.e:
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]}
}
}()
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论