1. 简介
UDP(User Datagram Protocol)是一个简单的无连接协议,不保证可靠性、顺序性和错误检测功能。在Go语言中,可以使用net包中的相关函数实现UDP通信。
2. 实现UDP通信
2.1 创建UDP服务器
下面的代码演示了如何创建一个UDP服务器,并监听一个指定的端口:
import (
"fmt"
"net"
)
func main() {
// 指定监听的地址和端口
udpAddress, _ := net.ResolveUDPAddr("udp", "127.0.0.1:8888")
// 创建连接
udpConn, _ := net.ListenUDP("udp", udpAddress)
defer udpConn.Close()
fmt.Println("UDP server is listening...")
buf := make([]byte, 1024)
for {
// 接收数据
n, remoteAddr, err := udpConn.ReadFromUDP(buf)
if err != nil {
fmt.Println(err.Error())
}
fmt.Println("Receive data from", remoteAddr.String(), string(buf[0:n]))
}
}
在代码中,我们首先调用net.ResolveUDPAddr函数解析UDP地址,并使用ListenUDP函数创建一个UDP连接。随后我们使用一个无限循环不断地从该连接中读取数据。
代码中的ReadFromUDP函数用于接收来自远端的数据。当有数据到来时,该函数会将数据读入buf缓存中,并返回数据字节数。其中remoteAddr表示数据来源的地址。
2.2 创建UDP客户端
下面的代码演示了如何创建一个UDP客户端,并向指定的服务器发送数据:
import (
"fmt"
"net"
)
func main() {
// 指定服务器地址和端口
udpAddress, _ := net.ResolveUDPAddr("udp", "127.0.0.1:8888")
// 建立连接
udpConn, _ := net.DialUDP("udp", nil, udpAddress)
defer udpConn.Close()
fmt.Println("UDP client is running...")
data := []byte("Hello, UDP server!")
// 发送数据
_, err := udpConn.Write(data)
if err != nil {
fmt.Println(err.Error())
}
}
在代码中,我们首先调用ResolveUDPAddr函数解析UDP地址,并使用DialUDP函数建立一个UDP连接。随后,我们使用Write函数向该连接写入数据。
2.3 使用非阻塞UDP模式
对于UDP连接,也可以使用非阻塞模式来处理收发数据。下面的代码演示了如何使用非阻塞模式:
import (
"fmt"
"net"
"time"
)
func main() {
// 指定服务器地址和端口
udpAddress, _ := net.ResolveUDPAddr("udp", "127.0.0.1:8888")
// 建立连接
udpConn, _ := net.DialUDP("udp", nil, udpAddress)
defer udpConn.Close()
udpConn.SetReadDeadline(time.Now().Add(time.Millisecond * 500))
data := []byte("Hello, UDP server!")
for {
// 发送数据
_, err := udpConn.Write(data)
if err != nil {
fmt.Println(err.Error())
break
}
fmt.Println("Send data:", string(data))
// 接收数据
buf := make([]byte, 1024)
n, _, err := udpConn.ReadFromUDP(buf)
if err != nil {
fmt.Println(err.Error())
continue
}
fmt.Println("Receive data:", string(buf[0:n]))
time.Sleep(time.Second * 1)
}
}
在代码中,我们使用DialUDP函数创建一个UDP连接,并使用SetReadDeadline函数设置接收数据的超时时间。在循环中,我们使用Write函数发送数据,并使用ReadFromUDP函数接收来自服务器的数据。对于ReadFromUDP函数,如果没有数据则会返回一个超时错误。
2.4 使用多个协程处理UDP连接
在进行UDP通信时,我们可以使用多个协程处理不同的请求。下面的代码演示了如何使用多个协程处理UDP连接:
import (
"fmt"
"net"
)
func main() {
udpAddress, _ := net.ResolveUDPAddr("udp", "127.0.0.1:8888")
udpConn, _ := net.ListenUDP("udp", udpAddress)
defer udpConn.Close()
fmt.Println("UDP server is listening...")
for i := 0; i < 2; i++ {
go func() {
buf := make([]byte, 1024)
for {
n, remoteAddr, err := udpConn.ReadFromUDP(buf)
if err != nil {
fmt.Println(err.Error())
}
fmt.Println("Receive data from", remoteAddr.String(), string(buf[0:n]))
}
}()
}
// 使用select函数在多个协程之间切换
select {}
}
在代码中,我们使用ListenUDP函数创建一个UDP连接,并使用多个协程处理不同的请求。每个协程使用ReadFromUDP函数接收数据,并在接收到数据时输出数据和远程地址。
2.5 使用IP数据包操作
Go语言中提供了对IP数据包的直接访问,可以使用net包中的IPConn结构体和RawConn结构体实现IP数据包操作。
下面的代码演示了如何使用IPConn结构体和RawConn结构体进行IP数据包操作:
import (
"fmt"
"net"
"time"
)
func main() {
addr, _ := net.ResolveIPAddr("ip", "127.0.0.1")
conn, _ := net.ListenIP("ip4:icmp", addr)
defer conn.Close()
fmt.Println("ICMP server is listening...")
buf := make([]byte, 1024)
for {
// 接收数据
n, addr, err := conn.ReadFrom(buf)
if err != nil {
fmt.Println(err.Error())
continue
}
fmt.Println("Receive data from", addr.String(), string(buf[0:n]))
// 发送数据
packet := &icmp.Message{
Type: ipv4.ICMPTypeEchoReply,
Code: 0,
Body: &icmp.Echo{
ID: 1,
Seq: 1,
Data: make([]byte, 56),
},
}
b, _ := packet.Marshal(nil)
conn.WriteTo(b, addr)
fmt.Println("Send data to", addr.String())
}
}
在代码中,我们使用net.ListenIP函数创建一个IPConn类型的连接,使用ReadFrom函数接收数据,使用WriteTo函数发送数据。对于发送数据,我们使用icmp.Message结构体和其相关函数进行封装和处理。
3. 总结
本文介绍了如何使用Go语言中的网络编程函数实现UDP通信,并讲解了如何使用非阻塞UDP模式、使用多个协程处理UDP连接、以及使用IP数据包操作。