这篇笔记我们继续学习如何使用Go语言实现UDP广播和组播。
UDP广播允许将UDP数据包发送到子网内的所有设备。广播使用的IP地址比较特殊,我们需要将发送UDP数据包的目标IP设置为255.255.255.255
,除此之外Go语言代码其实和我们之前编写的UDP客户端和服务端是类似的。我们向这个地址发送UDP数据包后所有监听指定UDP端口的网络设备都会收到UDP包。
下面例子代码中,UDP发送端向接收端发送一条信息,随后结束程序。广播UDP包接收端代码如下。
package main
import (
"log/slog"
"net"
)
func main() {
// UDP监听8000端口
addr, err := net.ResolveUDPAddr("udp", ":8000")
if err != nil {
slog.Error(err.Error())
return
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
slog.Error(err.Error())
return
}
defer func(conn *net.UDPConn) {
err := conn.Close()
if err != nil {
slog.Error(err.Error())
return
}
}(conn)
for {
// 打印收到的广播消息
buffer := make([]byte, 512)
n, addr, err := conn.ReadFromUDP(buffer)
if err != nil {
slog.Error(err.Error())
continue
}
data := string(buffer[:n])
slog.Info("[" + addr.String() + "] " + data)
}
}
广播UDP包发送端代码如下。
package main
import (
"log/slog"
"net"
)
func main() {
// 使用广播地址准备UDP套接字
addr, err := net.ResolveUDPAddr("udp", "255.255.255.255:8000")
if err != nil {
slog.Error(err.Error())
return
}
conn, err := net.DialUDP("udp", nil, addr)
if err != nil {
slog.Error(err.Error())
return
}
defer func(conn *net.UDPConn) {
err := conn.Close()
if err != nil {
slog.Error(err.Error())
return
}
}(conn)
// 发送Ping字符串
_, err = conn.Write([]byte("Ping"))
if err != nil {
slog.Error(err.Error())
return
}
}
这里我们要注意,UDP广播包是单向发送的,我们只能发送UDP包而接收端不能直接回复,不过接收端收到包后可以得到发送端的IP地址,因此接收端可以使用单播的方式向原发送端主动发送消息来回复。实际开发中,广播UDP包最常见的用途就是获取内网中程序的上线列表,我们通过一个程序节点发送UDP广播包,其它程序节点通过单播回复自己的IP地址和上线状态。
UDP组播(也叫多播)是指将UDP包发送到特定群组的设备。组播也不难理解,接收端需要绑定一个组播地址,这类似于加入一个“聊天群”,发送端向组播地址发送UDP包时,所有加入群的设备都会收到这条“群消息”。组播需要路由器的支持,组播地址的范围是224.0.0.0
至239.255.255.255
。
下面代码中,我们改写前面UDP广播的例子代码,实现UDP组播。接收端代码如下。
package main
import (
"log/slog"
"net"
)
func main() {
//UDP绑定组播8000端口
addr, err := net.ResolveUDPAddr("udp", "224.0.2.0:8000")
if err != nil {
slog.Error(err.Error())
return
}
conn, err := net.ListenMulticastUDP("udp", nil, addr)
if err != nil {
slog.Error(err.Error())
return
}
defer func(conn *net.UDPConn) {
err := conn.Close()
if err != nil {
slog.Error(err.Error())
return
}
}(conn)
for {
// 打印收到的组播消息
buffer := make([]byte, 512)
n, addr, err := conn.ReadFromUDP(buffer)
if err != nil {
slog.Error(err.Error())
continue
}
data := string(buffer[:n])
slog.Info("[" + addr.String() + "] " + data)
}
}
注意代码中net.ListenMulticastUDP()
函数用于监听组播地址,它有3个参数,第1个参数指定UDP协议;第2个参数是网络接口,我们可以用类似net.InterfaceByName("eth0")
的写法指定网络接口,如果传nil
则表示使用系统默认的组播网络接口;第3个参数是组播地址,一般来说224.0.2.0~238.255.255.255
是用户可用的组播地址,其他组播地址可能被保留作为其他用途,因此路由器未必支持使用其它的地址进行UDP组播。
UDP组播发送端代码如下。
package main
import (
"log/slog"
"net"
)
func main() {
// 使用组播地址准备UDP套接字
addr, err := net.ResolveUDPAddr("udp", "224.0.2.0:8000")
if err != nil {
slog.Error(err.Error())
return
}
conn, err := net.DialUDP("udp", nil, addr)
if err != nil {
slog.Error(err.Error())
return
}
defer func(conn *net.UDPConn) {
err := conn.Close()
if err != nil {
slog.Error(err.Error())
return
}
}(conn)
// 发送Ping字符串
_, err = conn.Write([]byte("Ping"))
if err != nil {
slog.Error(err.Error())
return
}
}
发送端代码没有什么特别的,我们向组播地址发送UDP包,发送后会观察到加入组播的所有接收端都受到了UDP包。如果没有收到UDP包也不用惊慌,可能是路由器配置不支持组播,我们知道有UDP组播这样一个重要的机制即可。