Create a Golang websocket server with front end example

Websocket protocol provides bidirectional communication over TCP. Let’s look at an example code.

1. Create a folder go_ws_server and enter the folder with “cd go_ws_server
2. Run command “go mod init go_ws_server
3. We need a few libraries as well.

go get github.com/gorilla/websocket
go get github.com/satori/go.uuid

4. Create a file main.go with below contents.

package main

import (
	"fmt"
	"math/rand"
	"net/http"
	"time"

	"github.com/gorilla/websocket"
	uuid "github.com/satori/go.uuid"
)

func readRandomNumber() {
	Random_number := rand.Intn(200)
	for Random_number > 0 {
		manager.broadcast <- []byte(fmt.Sprintf("%d", Random_number))
		time.Sleep(2 * time.Second)
		Random_number = rand.Intn(200)
		fmt.Println(fmt.Sprintf("%d", Random_number))

	}

	fmt.Println("readRandomNumber Loop done")
}

//Client Manager
type ClientManager struct {
	//This stores list of clients in a map. True is online and False means not connected
	clients map[*Client]bool
	//Use this to broadcast message to all clients
	broadcast chan []byte
	//Newly created client is registered  with this
	register chan *Client
	//Call unregister if client is disconnected
	unregister chan *Client
}

//Client
type Client struct {
	id     string
	socket *websocket.Conn
	send   chan []byte
}

//Create a client manager
var manager = ClientManager{
	broadcast:  make(chan []byte),
	register:   make(chan *Client),
	unregister: make(chan *Client),
	clients:    make(map[*Client]bool),
}

func (manager *ClientManager) start() {
	for {
		select {
		//If there is a new connection access, pass the connection to conn through the channel
		case conn := <-manager.register:
			//Set the client connection to true
			manager.clients[conn] = true
			fmt.Println("Client Connected")
			fmt.Println("Client count: " + fmt.Sprintf("%d", len(manager.clients)))

		case conn := <-manager.unregister:
			//Determine the state of the connection, if it is true, turn off Send and delete the value of connecting client
			if _, ok := manager.clients[conn]; ok {
				close(conn.send)
				delete(manager.clients, conn)

				fmt.Println("Client disconnected")
			}
		case message := <-manager.broadcast:
			//Traversing the client that has been connected, send the message to them
			for conn := range manager.clients {
				fmt.Println("Sending to :" + conn.id)
				select {
				case conn.send <- message:
				default:
					close(conn.send)
					delete(manager.clients, conn)
				}
			}
		}

	}
}

//Define the send method
func (manager *ClientManager) send(message []byte, ignore *Client) {

	for conn := range manager.clients {
		//Send messages to active connections
		if conn != ignore {
			conn.send <- message
		}
	}
}

//Define the read method
func (c *Client) read() {
	defer func() {
		manager.unregister <- c
		_ = c.socket.Close()
	}()

	for {
		//Read message
		_, message, err := c.socket.ReadMessage()
		//If there is an error message, cancel this connection and then close it

		if err != nil {
			manager.unregister <- c
			_ = c.socket.Close()
			break
		}

		fmt.Println("Message from client ", string(message))
		go readRandomNumber()

	}
}

func (c *Client) write() {
	defer func() {
		_ = c.socket.Close()
	}()

	for {
		select {
		//Read the message from send
		case message, ok := <-c.send:
			//If there is no message
			if !ok {
				_ = c.socket.WriteMessage(websocket.CloseMessage, []byte{})
				return
			}
			//Write it if there is news and send it back to the web
			_ = c.socket.WriteMessage(websocket.TextMessage, message)
		}
	}
}

func main() {
	fmt.Println("Starting application...")
	//Open a goroutine execution start program
	go manager.start()

	//Register the default route to /ws, and use the wsHandler method
	http.HandleFunc("/ws", GenerateRandomNumber)

	//Note that this must be 0.0.0.0 to deploy in the server to use
	err := http.ListenAndServe("0.0.0.0:8443", nil)
	fmt.Println("Error ListenAndServe :" + err.Error())
}

var upgrader = websocket.Upgrader{

	ReadBufferSize:  0,
	WriteBufferSize: 0,
	//Solving cross-domain problems
	CheckOrigin: func(r *http.Request) bool {
		//validate any cross domain call here
		return true
	},
}

func GenerateRandomNumber(res http.ResponseWriter, req *http.Request) {
	//Upgrade the HTTP protocol to the websocket protocol
	conn, err := upgrader.Upgrade(res, req, nil)
	if err != nil {
		fmt.Println("Error GenerateRandomNumber Upgrade")
		http.NotFound(res, req)
		return
	}

	//Every connection will open a new client, client.id generates through UUID to ensure that each time it is different
	client := &Client{id: uuid.Must(uuid.NewV4(), nil).String(), socket: conn, send: make(chan []byte)}
	manager.register <- client

	go client.read()
	go client.write()

}

func init() {
	go readRandomNumber()
}

5. To connect to the server and read the random number let’s create a simple html file with below contents. We will use the javascript websocket client to connect to the server and receive the response.

ws.html

<!DOCTYPE html>
<html lang="en">
<head>
     <meta charset="UTF-8">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>Universal Server</title>
</head>
<body>
<table border=1>
<tr>
<th>Random Number</th>
</tr>
<tr id="random_number">
</tr>
</table>
     <script>
         var ws = new WebSocket("ws://localhost:8443/ws");
         //Triggered when the connection is opened
         ws.onopen = function(evt) {
             console.log("Connection open...");
             ws.send("Hello WebSockets!");
         };
         //Triggered when a message is received
         ws.onmessage = function(evt) {
            console.log("Received Message: " + evt.data);
            console.log(evt.data)
            str = ''
            str += '<td>'+evt.data+'</td>';
            

            document.getElementById("random_number").innerHTML = str
         };
         //Triggered when the connection is closed
         ws.onclose = function(evt) {
             console.log("Connection closed.");
         };

         </script>
</body>
</html>

We can run the go file on the command line using “go run main.go” on you local machine and the application will be started. You can use a port of your choice as per your machine just make sure you call the same port number on both files.
Then we have to open the ws.html file on the browser to see that the websocket connection is established and the random numbers are being generated and viewed on the screen.

We can open multiple tabs as well and see that each tab is considered as a separate client connection and all the tabs will receive the broadcast. Below is a short video gif of the same.


Leave a Reply