Introducción: más allá del WebSocket básico
La comprensión básica de WebSocket permite crear un chat simple o notificaciones. Sin embargo, para construir aplicaciones en tiempo real de nivel de producción — juegos multijugador, terminales financieros, sistemas de edición colaborativa — es necesario dominar técnicas avanzadas. En este artículo iremos más allá del "Hello, World" y analizaremos cómo crear aplicaciones WebSocket robustas, escalables y seguras en Node.js.
Abordaremos cuatro áreas clave: escalado con Redis, gestión de estados y salas, seguridad y autorización, y agrupación en clústeres para funcionar en múltiples procesadores. Todos los ejemplos de código están escritos en JavaScript utilizando la biblioteca ws y Node.js.
1. Escalado con Pub/Sub (Redis)
Una de las principales limitaciones de un servidor WebSocket es que no puede "comunicarse" con otras instancias. Si tienes dos servidores ejecutándose detrás de un balanceador de carga, un cliente conectado al servidor A no recibirá un mensaje enviado a través del servidor B. La solución es utilizar el patrón Pub/Sub (publicador-suscriptor) basado en Redis.
Redis actúa como un broker central: cada servidor se suscribe a un canal y publica mensajes en él. De esta manera, un mensaje enviado por cualquier servidor se distribuye instantáneamente a todos los demás.
const WebSocket = require('ws');const redis = require('redis');
const wss = new WebSocket.Server({ port: 8080 });const publisher = redis.createClient();const subscriber = redis.createClient();
// Nos suscribimos al canal 'broadcast'subscriber.subscribe('broadcast');subscriber.on('message', (channel, message) => { // Enviamos el mensaje a todos los clientes conectados wss.clients.forEach(client => { if (client.readyState === WebSocket.OPEN) { client.send(message); } });});
wss.on('connection', (ws) => { ws.on('message', (data) => { // Publicamos el mensaje en Redis publisher.publish('broadcast', data.toString()); });});Este enfoque permite escalar horizontalmente tu aplicación casi infinitamente. Al agregar un nuevo servidor, simplemente crea su propia suscripción a Redis.
2. Gestión de salas y estados
En la mayoría de las aplicaciones en tiempo real es necesario agrupar a los clientes en "salas" (salas de chat, lobbies de juegos) y gestionar su estado. El wss.clients básico no proporciona esta funcionalidad. Implementemos un sistema de salas simple pero efectivo.
Crearemos un objeto rooms, donde la clave es el nombre de la sala y el valor es un Set de clientes WebSocket. Agregaremos métodos para entrar, salir y transmitir dentro de la sala.
const rooms = new Map();
function joinRoom(ws, roomName) { if (!rooms.has(roomName)) { rooms.set(roomName, new Set()); } rooms.get(roomName).add(ws); // Guardamos la sala en el objeto ws para acceso rápido ws.room = roomName;}
function leaveRoom(ws) { if (ws.room && rooms.has(ws.room)) { rooms.get(ws.room).delete(ws); if (rooms.get(ws.room).size === 0) { rooms.delete(ws.room); } delete ws.room; }}
function broadcastToRoom(roomName, message, exclude = null) { if (!rooms.has(roomName)) return; rooms.get(roomName).forEach(client => { if (client !== exclude && client.readyState === WebSocket.OPEN) { client.send(message); } });}
const wss = new WebSocket.Server({ port: 8080 });wss.on('connection', (ws) => { ws.on('message', (data) => { const msg = JSON.parse(data); switch (msg.type) { case 'join': joinRoom(ws, msg.room); break; case 'message': broadcastToRoom(ws.room, msg.text, ws); break; } }); ws.on('close', () => leaveRoom(ws));});Para gestionar el estado (por ejemplo, posición del jugador, cursor) se pueden almacenar datos en un Map