DEV Community

Jones Charles
Jones Charles

Posted on

13

Implementing WebSocket Communication and Heartbeat Mechanism with GoFrame: A Hands-on Guide

In modern web development, real-time communication has become increasingly crucial. WebSocket stands out as the go-to technology for implementing bidirectional communication between clients and servers. This guide will walk you through implementing WebSocket communication and a robust heartbeat mechanism using GoFrame.

What You'll Learn

  • Setting up a WebSocket server with GoFrame
  • Implementing client-side WebSocket communication
  • Handling concurrent WebSocket connections
  • Building a reliable heartbeat mechanism
  • Best practices for production-ready WebSocket applications

Prerequisites

  • Basic knowledge of Go programming
  • GoFrame framework installed
  • Understanding of WebSocket protocol basics

Setting Up the WebSocket Server

Let's start by creating a basic WebSocket server:

package main

import (
    "github.com/gogf/gf/v2/frame/g"
    "github.com/gogf/gf/v2/net/ghttp"
    "github.com/gogf/gf/v2/os/gctx"
)

func main() {
    ctx := gctx.New()
    s := g.Server()
    s.BindHandler("/ws", func(r *ghttp.Request) {
       ws, err := r.WebSocket()
       if err != nil {
          g.Log().Error(ctx, err)
          return
       }
       defer ws.Close()

       for {
          msgType, msg, err := ws.ReadMessage()
          if err != nil {
             return
          }
          if err = ws.WriteMessage(msgType, msg); err != nil {
             return
          }
       }
    })
    s.SetPort(8399)
    s.Run()
}
Enter fullscreen mode Exit fullscreen mode

This creates a simple echo server that listens on port 8399 and echoes back any messages it receives.

Client-Side Implementation

Here's a basic HTML/JavaScript client implementation:

<!DOCTYPE html>
<html>
<head>
    <title>WebSocket Client</title>
</head>
<body>
    <script>
        const socket = new WebSocket('ws://localhost:8399/ws');

        socket.onopen = function(e) {
            console.log('Connection established');
            socket.send('Hello, server!');
        };

        socket.onmessage = function(event) {
            console.log('Message received:', event.data);
        };

        socket.onclose = function(event) {
            console.log('Connection closed');
        };
    </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Handling Concurrent Connections

In a production environment, you'll need to handle multiple connections efficiently. Here's how to implement a connection pool:

import "github.com/gogf/gf/v2/os/gmlock"

var (
    connPool = make(map[string]*ghttp.WebSocket)
    mu       = gmlock.New()
)

func addConn(id string, ws *ghttp.WebSocket) {
    mu.Lock()
    connPool[id] = ws
    mu.Unlock()
}

func removeConn(id string) {
    mu.Lock()
    delete(connPool, id)
    mu.Unlock()
}

func broadcastMessage(ctx context.Context, id string, message []byte) {
    mu.RLock(id)
    defer mu.RUnlock(id)

    for _, ws := range connPool {
       go func(ws *ghttp.WebSocket) {
          if err := ws.WriteMessage(websocket.TextMessage, message); err != nil {
             g.Log().Error(ctx, err)
          }
       }(ws)
    }
}
Enter fullscreen mode Exit fullscreen mode

Implementing the Heartbeat Mechanism

Here's a production-ready heartbeat implementation:

func main() {
    ctx := gctx.New()
    s := g.Server()
    s.BindHandler("/ws", func(r *ghttp.Request) {
       ws, err := r.WebSocket()
       if err != nil {
          g.Log().Error(ctx, err)
          return
       }

       // Start heartbeat goroutine
       go heartbeat(ctx, ws)

       for {
          msgType, msg, err := ws.ReadMessage()
          if err != nil {
             break
          }
          if msgType == ghttp.WsMsgPing {
             if err = ws.WriteMessage(ghttp.WsMsgPong, []byte{}); err != nil {
                break
             }
          } else {
             g.Log().Info(ctx, string(msg))
          }
       }

       ws.Close()
    })
    s.SetPort(8399)
    s.Run()
}

func heartbeat(ctx context.Context, ws *ghttp.WebSocket) {
    ticker := time.NewTicker(10 * time.Second)
    defer ticker.Stop()

    for {
       select {
       case <-ticker.C:
          if err := ws.WriteMessage(ghttp.WsMsgPing, []byte("heartbeat")); err != nil {
             g.Log().Error(ctx, err)
             return
          }
       }
    }
}
Enter fullscreen mode Exit fullscreen mode

Client-Side Heartbeat Handling

const socket = new WebSocket('ws://localhost:8399/ws');

// Handle connection events
socket.onopen = function(event) {
    console.log('WebSocket connected');
};

// Handle incoming messages
socket.onmessage = function(event) {
    if (event.data instanceof Blob) {
        const reader = new FileReader();
        reader.onload = function() {
            console.log('Received binary message:', this.result);
        };
        reader.readAsText(event.data);
    } else {
        console.log('Received message:', event.data);
    }
};

// Handle heartbeat
socket.addEventListener('ping', function(event) {
    console.log('Received ping, sending pong');
    socket.pong();
});
Enter fullscreen mode Exit fullscreen mode

Best Practices and Tips

  1. Error Handling: Always implement proper error handling for connection failures and timeouts.
  2. Connection Cleanup: Ensure resources are properly cleaned up when connections close.
  3. Heartbeat Intervals: Choose appropriate heartbeat intervals based on your application needs (10-30 seconds is common).
  4. Message Size: Consider implementing message size limits to prevent memory issues.
  5. Reconnection Logic: Implement automatic reconnection on the client side.

Common Pitfalls to Avoid

  • Not implementing proper connection cleanup
  • Ignoring heartbeat timeouts
  • Not handling reconnection scenarios
  • Missing error handling for network issues
  • Blocking operations in the main connection loop

Conclusion

With GoFrame's WebSocket support, you can easily implement robust real-time communication in your applications. The combination of proper connection handling, heartbeat mechanisms, and concurrent connection management ensures a reliable and scalable WebSocket implementation.

Remember to:

  • Test your implementation under different network conditions
  • Monitor connection health in production
  • Implement proper error handling and recovery mechanisms
  • Consider scaling strategies for large numbers of connections

Resources

Now you have a solid foundation for implementing WebSocket communication in your GoFrame applications. Happy coding! 🚀

Image of AssemblyAI

Automatic Speech Recognition with AssemblyAI

Experience near-human accuracy, low-latency performance, and advanced Speech AI capabilities with AssemblyAI's Speech-to-Text API. Sign up today and get $50 in API credit. No credit card required.

Try the API

Top comments (0)

nextjs tutorial video

📺 Youtube Tutorial Series

So you built a Next.js app, but you need a clear view of the entire operation flow to be able to identify performance bottlenecks before you launch. But how do you get started? Get the essentials on tracing for Next.js from @nikolovlazar in this video series 👀

Watch the Youtube series

👋 Kindness is contagious

Explore a sea of insights with this enlightening post, highly esteemed within the nurturing DEV Community. Coders of all stripes are invited to participate and contribute to our shared knowledge.

Expressing gratitude with a simple "thank you" can make a big impact. Leave your thanks in the comments!

On DEV, exchanging ideas smooths our way and strengthens our community bonds. Found this useful? A quick note of thanks to the author can mean a lot.

Okay