How to send messages over the internet using UDP and Go
Go has to be one of the best programming languages to do anything related to networking and communication. Based on a earlier project where me and my friend Adrian controlled elevators over the local network, I will go through a simple program to send messages over the internet using UDP and Go.
Go is by far my favorite language (Maybe after Rust?). I feel sometimes that Go gets unreasonably much hate for not having the full array of features that for example C++ has. I do think though, that most of the people who complain about missing generics and stuff like that, haven’t actually programmed that much in Go. For me, not having generics forced me to think differently and to write simpler code that, in the end, is easier to understand. So, let’s take a look at the awesomeness of Go!
First we will make a new file called UDPmodule.go
and add some stuff to at the top of the file.
package UDPmodule
import (
"bytes"
"encoding/gob"
"fmt"
"net"
)
const broadcast_addr = "255.255.255.255"
type Packet struct {
ID, Response string,
Content []byte
}
At the top is the package name which we will call UDPmodule
. The bytes
library gives us some nice tools for handling the bytes of data that we will send and receive.
Then we have the encoding library, here we use gob, Golangs own encoding library. Here we could also have used the JSON library for encoding.
Finally, we have the net library which handles the UDP communication.
For this article, we will send all messages to the LAN broadcast IP here stored as broadcast_addr
.
You can of course send to any other IP too, if you know the IP you want to send to.
The broadcast IP is a “send-to-everyone” address, that every computer on the local network can access.
We then have the data structure we want to use for sending data.
We will take use of channels to send and receive messages to the UDP module.
To do this, we need to create an Init
function that can handle the initialization of our program.
func Init(readPort string, writePort string) (<-chan Packet, chan<- Packet) {
receive := make(chan Packet, 10)
send := make(chan Packet, 10)
go listen(receive, readPort)
go broadcast(send, writePort)
return receive, send
}
Our init function takes in two strings with the port that we want to send packets from and the port where we want to receive packets.
After that, we set the return parameters which here are our channels receive
and send
.
By writing <-chan
we ensure that the function that the channel is returned to, can only be read from the receive channel.
Similarly, chan<-
does the opposite so that only you can only send and not read from the channel.
The receive
and send
channels are created with a buffer of 10.
The main purpose of channels is to use them with a Golang feature called goroutines.
Goroutines are similar to threads, but much more light weight and more strongly connected to the programming language rather than the OS, which is often the case for other programming languages.
For our program we will need a listen and a send function that will be run as goroutines shown in the code as go listen
and go broadcast
.
The two functions will run indefinitely and in parallel with each other and any program that implements our UDP module.
func listen(receive chan Packet, port string) {
localAddress, _ := net.ResolveUDPAddr("udp", port)
connection, err := net.ListenUDP("udp", localAddress)
defer connection.Close()
First we will set up our UDP connection so that we can listen to incoming messages.
This is pretty straight forward, we just need to create an address object that we can connect to.
In this case, this will end up being our local address with the given port that we are listening to.
The address object is created by calling with net.ResolveUDPAddr
.
We start listening by calling net.ListenUDP()
. The defer
ensures that the connection is closed when we leave the listen
function.
var message Packet
for {
inputBytes := make([]byte, 4096)
length, _, _ := connection.ReadFromUDP(inputBytes)
buffer := bytes.NewBuffer(inputBytes[:length])
decoder := gob.NewDecoder(buffer)
decoder.Decode(&message)
//Filters out all messages not relevant for the system
if message.ID == ID {
receive <- message
}
}
}
The listen
function will be in an infinite for-loop that continuously will wait for messages and then pass them over to the receive channel.
By calling connection.ReadFromUDP(inputBytes)
, data from the network read into the bytes-array.
To decode the incoming message, we need to have the data in a type that implements the io.Reader
interface.
This can be done by creating an object of type Buffer
from the bytes
library and then copying the byte array over to the buffer object.
The buffer is passed to our new decoder by calling gob.NewDecoder(buffer)
. With the decoder we decode the bytes we receive from our connection and store the data in our message
object.
Finally we check that the message ID matches our ID, and if so, we pass it to the receive
channel.
func broadcast(send chan Packet, port string) {
destinationAddress, _ := net.ResolveUDPAddr("udp", broadcast_addr+port)
connection, err := net.DialUDP("udp", "localhost", broadcastAddress)
defer connection.Close()
Our broadcast
function is pretty similar to the listen
function.
We create our address object with net.ResolveUDPAddr
using the broadcast address and the port we want to send from.
Then net.DialUDP
is called and our connection is set up. "localhost"
is ourselves, that is; our own IP-address (could also use 127.0.0.1 which is the local IP-address of the computer you are on, same thing. Almost…).
var buffer bytes.Buffer
encoder := gob.NewEncoder(&buffer)
for {
message := <-send
encoder.Encode(message)
connection.Write(buffer.Bytes())
buffer.Reset()
}
}
The boardcast
function also has a infinite loop what will wait for incoming messages on the send
channel.
New messages gets encoded to bytes with encoder.Encode(message)
and then sent with connection.Write(buffer.Bytes())
.
After the message is sent, the buffer is reset and the program loops back to the beginning.
That’s it! Sending messages over the internet doesn’t need to be very complicated, at least when programming in Go. I remember it was very hard to figure it out the first time, especially because we at the time hadn’t even learned how IP-addresses works. Hopefully this is of some help to anyone working with UDP networking in Go (Ehm like students taking TTK4145 - Sanntidsprogrammering - Heislab at NTNU).
Check out the repository for the full code.