WebSocket en Go : création d'applications temps réel de niveau avancé

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

WebSocket en Go : techniques avancées pour les applications temps réel

\n\n

Le développement d'applications temps réel en Go est l'un des points forts du langage. Le support intégré de la concurrence via les goroutines et les canaux fait de Go un choix idéal pour les serveurs WebSocket. Cependant, lorsqu'il s'agit de systèmes de production avec des milliers de connexions simultanées, le simple exemple "Hello, WebSocket" ne suffit plus. Dans cet article, nous aborderons des techniques avancées : de l'optimisation du pool de connexions au passage à l'échelle via des courtiers de messages.

\n\n

1. Architecture du pool de connexions : gestion de milliers de clients

\n\n

Dans l'exemple de base, nous créons une goroutine distincte pour chaque connexion. C'est acceptable pour 100 à 200 clients, mais au-delà de 10 000 connexions, la consommation mémoire et les frais généraux de changement de contexte deviennent critiques. L'approche avancée consiste à utiliser un pool de connexions avec un nombre fixe de workers et de canaux.

\n\n
// Pool — pool de connexions avec limitation de goroutines\ntype Pool struct {\n    connections map[*websocket.Conn]bool\n    broadcast   chan []byte\n    register    chan *websocket.Conn\n    unregister  chan *websocket.Conn\n}\n\nfunc NewPool(maxGoroutines int) *Pool {\n    return &Pool{\n        connections: make(map[*websocket.Conn]bool),\n        broadcast:   make(chan []byte, 256),\n        register:    make(chan *websocket.Conn),\n        unregister:  make(chan *websocket.Conn),\n    }\n}\n\nfunc (pool *Pool) Start() {\n    for {\n        select {\n        case conn := <-pool.register:\n            pool.connections[conn] = true\n        case conn := <-pool.unregister:\n            if _, ok := pool.connections[conn]; ok {\n                delete(pool.connections, conn)\n                conn.Close()\n            }\n        case message := <-pool.broadcast:\n            for conn := range pool.connections {\n                go func(c *websocket.Conn) {\n                    if err := c.WriteMessage(websocket.TextMessage, message); err != nil {\n                        pool.unregister <- c\n                    }\n                }(conn)\n            }\n        }\n    }\n}
\n\n

Pourquoi cela fonctionne-t-il ? Les canaux agissent comme des tampons, empêchant le blocage. Un seul select gère tous les événements, et les goroutines d'écriture ne sont créées qu'en cas de besoin. Cela réduit la charge maximale sur le GC (ramasse-miettes).

\n\n

2. Arrêt gracieux et gestion de l'état des connexions

\n\n

Lors du redémarrage du serveur, il est important de fermer correctement toutes les connexions WebSocket en envoyant un message de fin aux clients. Ignorer cette étape entraîne des connexions "orphelines" et des fuites mémoire.

\n\n
// Server — serveur WebSocket avec arrêt gracieux\ntype Server struct {\n    pool    *Pool\n    httpSrv *http.Server\n}\n\nfunc (s *Server) Shutdown(ctx context.Context) error {\n    // 1. Fermer le serveur HTTP (aucune nouvelle connexion acceptée)\n    if err := s.httpSrv.Shutdown(ctx); err != nil {\n        return err\n    }\n\n    // 2. Envoyer un message de fin à tous les clients\n    shutdownMsg := []byte(`{"type":"shutdown","message":"le serveur va s'arrêter"}`)\n    s.pool.broadcast <- shutdownMsg\n\n    // 3. Fermer toutes les connexions dans le pool\n    for conn := range s.pool.connections {\n        conn.WriteMessage(websocket.CloseMessage, \n            websocket.FormatCloseMessage(websocket.CloseNormalClosure, "arrêt du serveur"))\n        conn.Close()\n    }\n\n    return nil\n}\n\n// Utilisation dans main\nfunc main() {\n    pool := NewPool(1000)\n    go pool.Start()\n\n    srv := &Server{pool: pool}\n    srv.httpSrv = &http.Server{Addr: ":8080"}\n\n    // Gestion des signaux\n    go func() {\n        sigint := make(chan os.Signal, 1)\n        signal.Notify(sigint, os.Interrupt, syscall.SIGTERM)\n        <-sigint\n        ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n        defer cancel()\n        srv.Shutdown(ctx)\n    }()\n\n    log.Fatal(srv.httpSrv.ListenAndServ                

Blogs

Book Recommendations