在Go中处理读/写udp连接
我需要创建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)的方法 。 这段代码可能会起作用,但我不太喜欢它。 有没有更优雅的方法来解决这个问题?
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>
==================
根据来自竞争检测器的跟踪,检测到的竞争看起来是由于在随后的写入中重新使用由读取调用返回的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
为什么不启动两个程序,一个用于写入,另一个用于读取并且是全双工? 即:
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]}
}
}()
}
链接地址: http://www.djcxy.com/p/77745.html