Introduction : au-delà du simple WebSocket en Go
Les exemples de base de WebSocket en Go montrent généralement une simple connexion écho. Dans les projets de production réels — des terminaux boursiers aux jeux multijoueurs et systèmes d'alerte — il faut bien plus : gestion des salles, arrêt progressif, mise à l'échelle et gestion correcte des erreurs. Dans cet article, nous allons analyser un modèle avancé de construction d'applications temps réel en Go utilisant la bibliothèque gorilla/websocket.
Nous allons créer une architecture basée sur Hub (répartiteur central) et Client (connexion individuelle), ajouter la prise en charge des salles, un mécanisme de tolérance aux pannes et montrer comment arrêter correctement le serveur.
Architecture Hub et Client : base du serveur temps réel
Pourquoi un Hub ?
Chaque client WebSocket est une goroutine distincte. Les gérer directement depuis le gestionnaire HTTP est dangereux : cela entraîne des conditions de concurrence et des fuites mémoire. Le modèle Hub résout ce problème en agissant comme un canal unique pour l'enregistrement, la suppression et la diffusion des messages.
Implémentation du Hub
package main
import ( "log" "sync")
type Client struct { hub *Hub send chan []byte room string}
type Hub struct { clients map[*Client]bool broadcast chan Message register chan *Client unregister chan *Client mu sync.RWMutex}
type Message struct { Room string Data []byte Sender *Client}
func NewHub() *Hub { return &Hub{ clients: make(map[*Client]bool), broadcast: make(chan Message, 256), register: make(chan *Client), unregister: make(chan *Client), }}
func (h *Hub) Run() { for { select { case client := <-h.register: h.mu.Lock() h.clients[client] = true h.mu.Unlock() log.Printf("Client connecté. Total : %d", len(h.clients))
case client := <-h.unregister: h.mu.Lock() if _, ok := h.clients[client]; ok { delete(h.clients, client) close(client.send) } h.mu.Unlock()
case message := <-h.broadcast: h.mu.RLock() for client := range h.clients { // Envoyer uniquement dans la bonne salle if client.room == message.Room || message.Room == "" { select { case client.send <- message.Data: default: // Si le canal est saturé — déconnecter le client h.mu.RUnlock() h.unregister <- client h.mu.RLock() } } } h.mu.RUnlock() } }}Points clés :
- Canaux — register, unregister, broadcast — synchronisent en toute sécurité les goroutines.
- RWMutex — permet de lire simultanément la liste des clients, mais bloque l'écriture.
- Routage par salle — le message est envoyé uniquement aux clients de la même salle.
Gestion des salles et des abonnements
Les salles (Rooms) sont des groupes logiques de clients. Par exemple, dans un chat, chaque conversation a sa propre salle. Dans notre Hub, la salle est définie par le champ room du Client. Voyons comment un client s'abonne à une salle.
Gestionnaire HTTP avec mise à niveau WebSocket
import ( "net/http" "github.com/gorilla/websocket")
var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, CheckOrigin: func(r *http.Request) bool { return true // Pour la production, remplacer par une vérification de l'origine },}
func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err !=