通迅模型
(A-->B-->A):A通过UDP发送数据给B(A可以是指定目的地,也可以是广播发送消息给B),B收到消息后根据来源地址和端口向A回发消息,就这么简单的一个通迅过程。
关于golang udp方面的讲解可以参考下https://colobu.com/2016/10/19/Go-UDP-Programming/这篇文件,讲的挺详细。什么时候udp socket是connected状态,什么时候是unconnected状态,什么时候用read/write,什么时候用readFromUDP/writeToUDP都有说明,写的挺好。
来段code demo吧
服务端:
func main() {
// 创建监听
sock, err := net.ListenUDP("udp4", &net.UDPAddr{
IP: net.IPv4(0, 0, 0, 0),
Port: 9000,
})
if err != nil {
fmt.Println("listen udp failure!", err)
return
}
defer sock.Close()
for {
// 读取数据
data := make([]byte, 4096)
readNum, rAddr, err := sock.ReadFromUDP(data)
if err != nil {
fmt.Println("read data failure!", err)
continue
}
fmt.Println("read byte number:",readNum, "remote addr:", rAddr)
fmt.Println("received: ", string(data[:readNum]))
// 发送数据
sendBts := []byte("hello client!")
_, err = sock.WriteToUDP(sendBts, rAddr)
if err != nil {
fmt.Println("send msg back failure!", err)
return
} else {
fmt.Println("send msg mack ok.", string(sendBts), "to:", rAddr)
}
}
}
客户端
func main() {
sock, err := net.DialUDP("udp", nil, &net.UDPAddr{
IP: net.IPv4(255, 255, 255, 255), // 广播地址
Port: 9000,
})
// 创建监听
//soct, err := net.ListenUDP("udp4", &net.UDPAddr{
// IP: net.IPv4(0, 0, 0, 0),
// Port: 9001,
//})
if err != nil {
fmt.Println("connect udp failure!", err)
return
}
defer sock.Close()
// 发送数据
sendBts := []byte("hello server!")
_, err = sock.Write(sendBts)
if err != nil {
fmt.Println("send msg failure!", err)
return
}
// 接收数据
data := make([]byte, 4096)
for {
readNum, rAddr, err := sock.ReadFromUDP(data)
if err != nil {
fmt.Println("read udp failure!", err)
return
}
fmt.Println("read byte number:", readNum, "from:", rAddr)
fmt.Println("received: ", string(data[:readNum]))
}
}
问题呈现
运行下上面的demo:
服务端
read byte number: 13 remote addr: 10.200.2.50:54404
received: hello server!
send msg mack ok. hello client! to: 10.200.2.50:54404
客户端
// nothing
发现客户端啥都没输出,可见咱们遇到问题了。服务端明明发送数据了,而且也指定了目的ip和端口号了!为什么客户端没收到呢?客户端dial的时候,发广播消息不会帧听本地端口??或者说广播消息的时候,侦听的不是同一个网卡?带着问题再来改下客户端的代码。
// 启用客户端注释掉的那段代码
func main() {
// 创建监听
sock, err := net.ListenUDP("udp4", &net.UDPAddr{
IP: net.IPv4(0, 0, 0, 0),
Port: 9001, // 再同一台主机上测试,要跟服务端绑定不同的端口;如果是不同的主机,可以用相同的端口
})
if err != nil {
fmt.Println("connect udp failure!", err)
return
}
defer sock.Close()
// 发送数据
sendBts := []byte("hello server!")
_, err = sock.WriteToUDP(sendBts, &net.UDPAddr{
IP: net.IPv4(255, 255, 255, 255),
Port: 9000,
})
if err != nil {
fmt.Println("send msg failure!", err)
return
}
// 接收数据
data := make([]byte, 4096)
for {
readNum, rAddr, err := sock.ReadFromUDP(data)
if err != nil {
fmt.Println("read udp failure!", err)
return
}
fmt.Println("read byte number:", readNum, "from:", rAddr)
fmt.Println("received: ", string(data[:readNum]))
}
}
再次运行下
服务端
read byte number: 13 remote addr: 10.200.2.50:54404
received: hello server!
send msg mack ok. hello client! to: 10.200.2.50:54404
客户端
received: hello client!
看结果还真印证了没有侦听就不能收消息的猜测。为什么dial的时候没有侦听呢?记得c代码会隐式的绑定(参考https://stackoverflow.com/questions/54443823/udp-socket-sendto-implicit-bind,https://www.cnblogs.com/skyfsm/p/6287787.html),语言之间会有不同的封装吗?咱们就一同来看看golang的源码。
看到dialUDP和listenUDP这两个方法,也就红框里的参数不一样,其他可以说都一样。再继续往下看internetSocket -> socket方法
注意到红框里都这段代码,它是给udp侦听/绑定用的,但是有个前提:要存在local addr(laddr)而且要不存在remote addr(raddr)。
那再改下客户端DialUDP的代码
sock, err := net.DialUDP("udp", &net.UDPAddr{
IP: net.IPv4(0,0,0,0),
Port: 9001,
}, nil)
再次运行客户端的代码。
connect udp failure! dial udp 0.0.0.0:9001: missing address
得到了一个缺少地址的一个错误,那说明DialUDP是不支持隐式绑定的。
总结
在使用udp编程的时候呢,如果是单纯的发送数据,可以使用DialUDP来获得socket句柄,然后调用write方法发送数据。如果想要收消息不管是客户端还是服务端,都得用ListenUDP,侦听/绑定后才可以接收消息。
有疑问加站长微信联系(非本文作者)