Markus Hutnik




Go Websockets

Intro

This post shows how to set up a web sockets project in Go. I will be using golang.org/x/net. This package is part of the extended standard library, so it is maintained by the Go team. I followed this same template for one of my recent projects - Yacht Dice.

The rest of this article is a step-by-step explanation this template. To skip ahead to the end result, take a look at the repo.

To see my project that makes use of this template, see yacht-dice-service.




Basic WebSockets

To start, get the websocket package.

$ go get golang.org/x/net/websocket
$ go mod tidy

Create files main.go and server.go. In server.go, add the following skeleton code. This contains the Server struct and a few methods. handleWebSocket will handle incoming web socket connections and add them to our list of known connections. readLoop will constantly check for new messages sent by any clients. broadcast will send messages to clients.

initial server

In main.go, I just have the main method which sets up the web server.

initial main

Now we can start implementing the methods in server.go. First, I'll set up handleWebSocket since this is sort of the "entry point" into a web socket connection. In this first pass, all I'm going to do is print out where the connection is coming from, add it to the conns map and kick off the read loop for that connection.

initial handleWebSocket method

The read loop is going to constantly check for messages from the client and decide what to do with it. I set the buffer size to 1024, but this value can be changed depending on expected message size. Then it goes into a loop where it tries to read a message from the connection. If it encounters an EOF (i.e. the client disconnected), the connection gets removed from the conns map. Any other errors and it will just continue. If it was able to read a message, it's going to broadcast it back to the clients for now.

initial readLoop method

Finally, the broadcast method will send a message to every client (each entry in the conns map). A goroutine is used here because we don't seen to wait for it to finish sending the message before moving on to the next client.

initial broadcast method

At this point, we should have a functioning server which clients can connect to and send/receive messages. The easiest way to open a couple browser windows and connect from the console.

Run the server

$ go run .

In each browser window, open the console and create a WebSocket connection.

let ws = new WebSocket('ws://localhost:8000/ws');

Then set the onnmessage function to print out anything received from the server.

ws.onmessage=(event) => {console.log(event.data)}

Now messages can be sent with ws.send('message'). When a message is sent from any client, all clients will receive the message and print it out. In the image below, notice the client on the left send 'hello' and both clients received the message and displayed it.

browser testing


Adding functionality

This is a decent starter template so far, but one more thing I'm going to add to this template is the concept of rooms. This is a common thing for WebSockets since usually clients will want to interact with a handful of people in a virtual room, rather than everyone who's currently using the site.

Adding the concept of rooms only requires some small modifications to the code that has been written so far. The first change is to update the main method so that the route has a "/" on the end. I chose to implement this so that the room clients are attempting to join is part of the URL (e.g. /ws/room_id). Adding the slash allows the handler to match URLs with something after the "/ws".

updated main

The Server connections kept track of by the server need to be wrapped in another map where each key is a room ID.

updated server

The handleWebSocket method will now parse the URL to get the room ID. After getting it from the URL, this is where custom logic can be added to check if the room is full, keep track of additional info about the connection, etc. It will also pass the room ID to readLoop.

updated handlWebSocket

The main change to readLoop is that now it will check if a room is empty and delete it from the map if it is. Also note that we don't have to broadcast a message here. This is where we might want to parse the message and decide what to do.

updated readLoop

Finally, broadcast is updated so that instead of sending a message to all clients, it accepts a room ID and will only send a message to the clients in that room.

updated broadcast

For the final test, WebSocket connections can be created in two browser windows the same as before, but include a room ID in the URL. When both clients are in the same room, they both get the message. The client on the left leaves room1 and joins room2. When they do that and send another message, the client on the right does not see it.

browser testing