Real-Time Awesomeness: Go and WebSockets

Dhairya VermaDhairya Verma
3 min read

Golang is really great when it comes to network programming. Maintaining web sockets, and scaling them, comes very easy with Go.

In this article, we will be creating a WebSocket server to receive messages from clients. After that, we will also explore broadcasting messages to all clients.

We will be using the following packages for creating WebSockets:

So, let's begin.

Importing libraries

package main

import (
    "fmt"
    "io"
    "net/http"

    "golang.org/x/net/websocket"
)

Creating Interface for Server

The server needs to persist the address of all clients. In case any connection information is lost, the server won't be able to connect to that client. Here we are using a map to persist all connections.

type Server struct {
    coons map[*websocket.Conn]bool
}

func NewServer() *Server {

    return &Server{
        coons: make(map[*websocket.Conn]bool),
    }
}

Handler for Connection requests

Here we are doing two jobs. When we get the connection request we will persist that connection and start reading from that connection in a loop.

func (s *Server) handleWS(ws *websocket.Conn) {
    fmt.Print("New Incoming Connection", ws.RemoteAddr())

    s.coons[ws] = true
    s.readLoop(ws)
}

func (s *Server) readLoop(ws *websocket.Conn) {

    buff := make([]byte, 1024) /// creating a buffer here to read from client

    for {
        n, err := ws.Read(buff) //// blocking call to read fron client

        if err != nil {
            if err == io.EOF { // is client has sent a signal to close the connection
                break
            }

            fmt.Println("Read error")
            continue
        }

        msg := buff[:n]
        fmt.Println(string(msg))
        ws.Write([]byte("thank you for the message"))
    }
}
๐Ÿ’ก
when we are reading from the connection, we can also add read timeout. If nothing is received from a client in X seconds we can either close the connection or take any other action.

Initiating Server

We are starting the server on port 3000. For every request matching path /ws, the request will be routed to the handler we just created.

func main() {
    server := NewServer()
    http.Handle("/ws", websocket.Handler(server.handleWS))
    http.ListenAndServe(":3000", nil)
}
๐Ÿ’ก
We can also add another endpoint here, like "/stockPrices" which can be mapped to another handler that broadcasts stock prices to clients.

Broadcast message

Broadcasting message to clients is fairly easy. Just get all the connections that we have persisted and send data to these clients.

func (s *Server) handleWS(ws *websocket.Conn) {
    fmt.Print("New Incoming Connection", ws.RemoteAddr())

    s.broadCastMessage() // whenever a new client has joined broadcast message to all the other clients
    s.coons[ws] = true
    s.readLoop(ws)

}

func (s *Server) broadCastMessage() {

    broadCastMessage := []byte("A new peer has joined the group")
    for key, _ := range s.coons {

        connection := key
        connection.Write(broadCastMessage)
    }
}

Testing

This can be easily tested via any socket client. I prefer to test it using Javascript. Just go to the Chrome console and create a web socket to connect to our server.

let socket = new WebSocket("ws://localhost:3000/ws"); // connecting to socket

/// adding listener on any message received
socket.onMessage = (event) => console.log(`Received data: ${event.data}`)
socket.send("Hi I am here"); /// sending data to server

Happy Coding ๐Ÿค 

References
4
Subscribe to my newsletter

Read articles from Dhairya Verma directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Dhairya Verma
Dhairya Verma

Hey there ๐Ÿ‘‹ I'm Dhairya, a backend developer. With a solid background spanning 5 years, my achievement includes scaling an app to 3M users.