WebSocket en Go: creación de aplicaciones en tiempo real de nivel avanzado

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

WebSocket en Go: técnicas avanzadas para aplicaciones en tiempo real

\n\n

El desarrollo de aplicaciones en tiempo real en Go es uno de los puntos fuertes del lenguaje. El soporte integrado para concurrencia a través de gorutinas y canales hace de Go una opción ideal para servidores WebSocket. Sin embargo, cuando se trata de sistemas en producción con miles de conexiones simultáneas, el simple ejemplo "Hola, WebSocket" deja de funcionar. En este artículo, analizaremos técnicas avanzadas: desde la optimización del pool de conexiones hasta la escalabilidad a través de brokers de mensajes.

\n\n

1. Arquitectura del pool de conexiones: gestión de miles de clientes

\n\n

En el ejemplo básico, creamos una gorutina separada para cada conexión. Esto es aceptable para 100-200 clientes, pero con 10 000+ conexiones, el consumo de memoria y la sobrecarga del cambio de contexto se vuelven críticos. El enfoque avanzado es utilizar un pool de conexiones con un número fijo de workers y canales.

\n\n
// Pool — pool de conexiones con límite de gorutinas\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

¿Por qué funciona esto? Los canales actúan como búferes, evitando bloqueos. Un único select gestiona todos los eventos, y las gorutinas para escritura se crean solo cuando es necesario. Esto reduce la carga máxima en el GC (recolector de basura).

\n\n

2. Apagado graceful y gestión del estado de las conexiones

\n\n

Al reiniciar el servidor, es importante cerrar correctamente todas las conexiones WebSocket, enviando a los clientes un mensaje de finalización. Ignorar este paso provoca conexiones "colgantes" y fugas de memoria.

\n\n
// Server — servidor WebSocket con apagado graceful\ntype Server struct {\n    pool    *Pool\n    httpSrv *http.Server\n}\n\nfunc (s *Server) Shutdown(ctx context.Context) error {\n    // 1. Cerramos el servidor HTTP (no se aceptan nuevas conexiones)\n    if err := s.httpSrv.Shutdown(ctx); err != nil {\n        return err\n    }\n\n    // 2. Enviamos a todos los clientes un mensaje de finalización\n    shutdownMsg := []byte(`{"type":"shutdown","message":"el servidor se está cerrando"}`)\n    s.pool.broadcast <- shutdownMsg\n\n    // 3. Cerramos todas las conexiones en el pool\n    for conn := range s.pool.connections {\n        conn.WriteMessage(websocket.CloseMessage, \n            websocket.FormatCloseMessage(websocket.CloseNormalClosure, "cierre del servidor"))\n        conn.Close()\n    }\n\n    return nil\n}\n\n// Uso en 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    // Manejo de señales\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