Dive deeper into Go web socket and concurrency programming
Chatting app can be done in various way (theoretically). Like using simple long-polling request that provide half-duplex (one-way communication) or using protocol that provide full-duplex (2 way) communication like TCP Socket. But, website cannot directly connect to TCP, fortunately, there is a wrapper to TCP called Websocket.
You may read more explanation of Websocket that spread through internet, and this time, I’ll explain more about how to implement it in Go
Before we go right into programming, that’s one library that we need:
Gorilla WebSocket is a Go implementation of the WebSocket protocol. The Gorilla WebSocket package provides a complete…
gorilla/websocket is A fast, well-tested and widely used WebSocket implementation for Go. (quoted from the repo itself)
To do that, right in the project root folder, install the library by typing this command:
go get github.com/gorilla/websocket
The project structure
You like it simple, right?
So, this is the structure:
Yes, just one file. No need to follow SOLID Principle, following best practice, etc. All we need is a minimal working code. And in big picture, the step to make it is as follow:
- Simple HTTP Server as the receiver of Websocket request
- Websocket connection handler. We call it the Hub. It is a goroutine that save and remove the data of connected client and handle the message broadcast event
- Websocket client manager. It handle the Read and Write of Websocket of each client. Ideally, we create one goroutine for each. And 2 separate go routine for the reader and writer. (Total 3 goroutine for 1 client)
I will follow the example provided in this folder of repo
Now, let’s go into the code:
Main function and HTTP Endpoint
At this point, we have create a simple HTTP Server that can receive request to http://127.0.0.1:8080/ws
Now, let’s handle the process when user hit the endpoint by using Web socket protocol. Firstly, we create the Hub.
We have created the Hub. Technically we have provided the container and handler of events like client connection, disconnection and message broadcast that triggered from Websocket. Now we create the Client Manager. Add this below the code, or in the same package (main).
*don’t forget to import github.com/gorilla/websocket when writing this part of code
Let’s see, from line 1–4 there, Upgrader used as the parameter of memory buffer constraint for each web socket packet.
struct Client represent client connection, it holds reference of Hub and Websocket connection, so it can communicate with other client having the same Hub and Websocket connection reference.
readPump wait for any incoming message send from client. Once it receive a message, it will pass the data to channel send (send <- message) and next, it will be handled by the Hub.
writePump will write (show) the message to the client from incoming data from broadcast (<- send) to client output stream
serveWs is the gateway for connected HTTP request of client to Websocket world. It will create new instance of websocket connection, create new client object, emitting the register event in hub so the client is registered in the Clients map of Hub. And finally start the reader and writer function
Hub it self identify Client by it’s memory address. So that is why, in the Client map inside Hub struct, the key is the memory address of the client. We don’t have to create a custom ID for every client as it is by nature already unique. (Memory address looks like: 0x2311cc)
Now. let’s go back to the main function. We finish the code by initializing new Hub and adding websocket handler.
And the complete code:
Test the Websocket Connection
Run the code
go run main.go
I use wscat https://www.npmjs.com/package/wscat to connect to Websocket via Terminal. You may use your favorite tools
First, I open a tab in I use wscat by using the following command
wscat -c http://127.0.0.1:8080/ws
and I open a new tab and use the same command again
wscat -c http://127.0.0.1:8080/ws
So it looks like this:
Now, try to send some message. Look for the real time message received from other terminal
*white text is the message we write, and blue text is the output.
And that’s all
There are plenty of room to be improve. Since I get the example from the repository itself. I also drop some “fancy” function and make it as minimal as I can. I drop the Ping/pong mechanism, queue messages and some message encodings. But, after we understand how it works, I think it can be your challenge to implement those by your self and add more advance features like custom ID, adding room features like Socket IO or anything that fit it to your needs.
Ok. Everything is set up, now let’s dance