DEV Community

Jones Charles
Jones Charles

Posted on

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! 🚀

Top comments (0)