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.
