Problem: You want to create a UDP server to receive data from a UDP client.
Solution: Use the ListenPacket function in the net package to listen for incoming packets. Then use the ReadFrom method of the PacketConn interface to read data from the connection. You can also use the WriteTo method to write data to the connection.
UDP is a connectionless protocol. This means there is no connection between the client and the server. The client sends data to the server and the server sends data back to the client. Neither the client nor the server knows if the server or client received the data or not.
A UDP server in Go is similar to a TCP server. The main difference is that you use the net.ListenPacket function instead of the net.Listen function:
func main() { conn, err := net.ListenPacket("udp", ":9001") if err != nil { log.Fatal(err) } defer conn.Close() buf := make([]byte, 1024) for { _, addr, err := conn.ReadFrom(buf) if err != nil { log.Fatal(err) } log.Printf("Received %s from %s", string(buf), addr) conn.WriteTo([]byte("Hello from UDP server"), addr) } }
The net.ListenPacket function returns a net.PacketConn interface, which is a packet-oriented network connection. Unlike the net.Conn interface, net.PacketConn is not a Reader or Writer because it doesn’t have Read or Write methods. Instead, it has ReadFrom and WriteTo methods to read and write data from and to the connection.
You can test the server. Start this on one terminal:
$ go run main.go
Use the nc command to send data to it. In another terminal, run the following command as the client:
$ echo "Hello from UDP client" | nc -u localhost 9001
You should see “Received Hello from UDP client” printed out on the server side, and “Hello from UDP server” printed out on the client side.
As in the TCP server, your UDP server is listening on both IPv4 and IPv6. To show this you’ll use the -6 flag to force nc to use IPv6:
$ echo "Hello from UDP client" | nc -u -6 ip6-localhost 9001
You should see the same output as before.
Besides using net.ListenPacket you can also use the net.ListenUDP function to create a UDP server:
func main() { addr, err := net.ResolveUDPAddr("udp", ":9001") if err != nil { log.Fatal(err) } conn, err := net.ListenUDP("udp", addr) if err != nil { log.Fatal(err) } defer conn.Close() buf := make([]byte, 1024) for { _, addr, err := conn.ReadFromUDP(buf) if err != nil { log.Fatal(err) } log.Printf("Received %s from %s", string(buf), addr) conn.WriteToUDP([]byte("Hello from UDP server"), addr) } }
Unlike the net.ListenPacket , though, the net.ListenUDP function takes a net.UDPAddr as an argument. First, use the net.ResolveUDPAddr function to resolve and create the address. Then use the net.ListenUDP function to create the connection. Instead of net.PacketConn you get a net.UDPConn interface. The net.UDPConn struct implements both the net.PacketConn and net.Conn interfaces, meaning it has both ReadFrom and WriteTo methods and Read and Write methods. In addition, it has ReadFromUDP and WriteToUDP methods to read and write data from and to the connection.
The net.ListenPacket function is more generic and can be used to create a UDP server, but it is also used to create other types of servers such as IP and Unix domain sockets. The net.ListenUDP function is more specific and can only be used to create a UDP server.
At this point, you might be confused about why net.UDPConn implements both the net.PacketConn and net.Conn interfaces. After all, net.UDPConn is a packet-oriented connection whereas net.Conn is a stream-oriented connection, which is also implemented by the net.TCPConn struct. How can a connection be both packet-oriented and stream-oriented at the same time?
This comes from the original Berkeley, sockets API that has become the standard in Unix-like systems where sockets are stream-oriented. Both UDP and TCP are implemented on top of sockets so they both implement the net.Conn interface.