DEV Community

Cover image for Using WebSockets in Go for Real-Time Communication
Neel Patel
Neel Patel

Posted on

Using WebSockets in Go for Real-Time Communication

Building apps that require real-time updates—like chat applications, live notifications, or collaborative tools—requires a communication method faster and more interactive than traditional HTTP. That’s where WebSockets come in! Today, we’ll explore how to use WebSockets in Go, so you can add real-time functionality to your applications.

In this post, we’ll cover:

  • What WebSockets are and how they work.
  • Setting up a WebSocket server in Go.
  • Creating a simple WebSocket client.
  • Handling events and maintaining connections.

Let’s get real-time! 🕒


What Are WebSockets? 🤔

WebSockets provide a full-duplex communication channel over a single, long-lived connection. Unlike traditional HTTP requests, which follow a request-response cycle, WebSockets allow the server and client to send and receive messages independently, making it ideal for real-time applications.

Key Features:

  • Full-Duplex Communication: Both the client and server can send messages independently, without waiting for a response.
  • Low Latency: WebSockets keep a persistent connection open, reducing the delay between messages.
  • Event-Driven: WebSockets handle data as events, enabling you to react instantly to incoming messages.

Setting Up a WebSocket Server in Go

To create a WebSocket server, we’ll use the Gorilla WebSocket package, which makes it easy to work with WebSockets in Go.

Step 1: Install Gorilla WebSocket

Install the package using go get:

go get -u github.com/gorilla/websocket
Enter fullscreen mode Exit fullscreen mode

Step 2: Create the WebSocket Server

Now, let’s set up a simple WebSocket server that listens for incoming connections and echoes back any messages it receives:

package main

import (
    "fmt"
    "net/http"
    "github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool { return true }, // Allow all connections
}

func handleConnections(w http.ResponseWriter, r *http.Request) {
    // Upgrade initial GET request to a WebSocket
    ws, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer ws.Close()

    for {
        // Read message from browser
        _, msg, err := ws.ReadMessage()
        if err != nil {
            fmt.Println("read error:", err)
            break
        }
        fmt.Printf("Received: %s\n", msg)

        // Write message back to browser
        if err := ws.WriteMessage(websocket.TextMessage, msg); err != nil {
            fmt.Println("write error:", err)
            break
        }
    }
}

func main() {
    http.HandleFunc("/ws", handleConnections)

    fmt.Println("WebSocket server started on :8080")
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        fmt.Println("ListenAndServe:", err)
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example:

  • Upgrading: We upgrade an HTTP connection to a WebSocket connection using Upgrader.
  • Read/Write Loop: We enter a loop where we read messages from the client and echo them back.

Note: CheckOrigin in Upgrader allows all connections. In a production environment, make sure to validate the origin to avoid Cross-Site WebSocket Hijacking.


Setting Up a Simple WebSocket Client

To test your WebSocket server, you can use an HTML client with JavaScript, which connects to the WebSocket server and sends/receives messages.

HTML Client Example:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket Client</title>
</head>
<body>
    <h2>WebSocket Client</h2>
    <input type="text" id="messageInput" placeholder="Enter message" />
    <button onclick="sendMessage()">Send</button>
    <pre id="messages"></pre>

    <script>
        let socket = new WebSocket("ws://localhost:8080/ws");

        socket.onopen = function(event) {
            document.getElementById("messages").textContent += "Connected to WebSocket server\n";
        };

        socket.onmessage = function(event) {
            document.getElementById("messages").textContent += "Received: " + event.data + "\n";
        };

        socket.onclose = function(event) {
            document.getElementById("messages").textContent += "Disconnected from WebSocket server\n";
        };

        function sendMessage() {
            let message = document.getElementById("messageInput").value;
            socket.send(message);
            document.getElementById("messageInput").value = "";
        }
    </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

This HTML client:

  • Connects to the WebSocket server at ws://localhost:8080/ws.
  • Sends messages typed into the input box.
  • Displays incoming messages in a <pre> block, allowing you to see the server’s responses in real-time.

Handling Events in WebSockets

With WebSockets, you can go beyond simple message echoing. Here are a few common events and how to handle them.

Broadcasting Messages to All Clients

To broadcast messages to all connected clients, store each connection in a slice and loop through it to send messages.

var clients = make(map[*websocket.Conn]bool) // Track active clients

func handleConnections(w http.ResponseWriter, r *http.Request) {
    ws, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer ws.Close()

    clients[ws] = true

    for {
        _, msg, err := ws.ReadMessage()
        if err != nil {
            fmt.Println("read error:", err)
            delete(clients, ws)
            break
        }

        // Broadcast message to all clients
        for client := range clients {
            if err := client.WriteMessage(websocket.TextMessage, msg); err != nil {
                fmt.Println("broadcast error:", err)
                client.Close()
                delete(clients, client)
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, whenever a message is received, it’s broadcast to all connected clients. This is a common approach for chat applications or real-time updates.

Handling Disconnections Gracefully

To handle client disconnections, use defer ws.Close() and remove the client from the clients map if the read operation fails.

defer func() {
    delete(clients, ws)
    ws.Close()
}()
Enter fullscreen mode Exit fullscreen mode

Ping/Pong for Connection Health

WebSockets support ping/pong frames to keep the connection alive and check if the client is still connected.

ws.SetPongHandler(func(appData string) error {
    fmt.Println("pong received")
    return nil
})
Enter fullscreen mode Exit fullscreen mode

Best Practices for WebSockets

  1. Limit Connections: Protect your server from being overwhelmed by limiting the number of active connections.
  2. Close Inactive Connections: Use timeouts or ping/pong frames to close inactive connections.
  3. Use TLS (wss): For security, always use wss (WebSocket Secure) in production to encrypt the connection.
  4. Handle Errors Gracefully: Always check for errors when reading from or writing to WebSockets and log them appropriately.

Final Thoughts

WebSockets make it possible to build interactive, real-time applications that go beyond traditional request/response cycles. By setting up a WebSocket server in Go, you’re now equipped to handle real-time data with ease. Whether you’re building a chat app, live dashboard, or collaborative tool, WebSockets give you the power to create seamless, interactive experiences.

Try it out: Set up a WebSocket server and start experimenting! Once you get the hang of it, you’ll see why WebSockets are essential for real-time applications.


What’s Your Favorite Use Case for WebSockets? Let me know in the comments, or share any tips you have for building real-time applications!

Top comments (0)