Trabalho avançado com WebSocket em JavaScript: do protótipo à produção
\nWebSocket é um protocolo que fornece comunicação full-duplex entre cliente e servidor através de uma única conexão TCP persistente. Em artigos anteriores, abordamos os fundamentos: estabelecimento de conexão, envio de mensagens e chats simples. No entanto, para criar aplicações real-time verdadeiramente robustas e escaláveis (jogos online, plataformas de negociação, sistemas de edição colaborativa), é necessário dominar técnicas avançadas. Neste material, exploraremos a arquitetura de um servidor WebSocket de nível de produção em Node.js, incluindo escalabilidade, gerenciamento de estado, autorização e tratamento de erros.
\n\n1. Arquitetura avançada do servidor e gerenciamento de salas
\nUm servidor WebSocket simples (baseado na biblioteca ws) gerencia todos os clientes em um único objeto global. Em projetos reais, é necessário isolamento lógico — salas (rooms) e canais.
Vejamos um exemplo de arquitetura usando a classe RoomManager:
const WebSocket = require('ws');\n\nclass RoomManager {\n constructor() {\n this.rooms = new Map(); // roomName -> Set<WebSocket>\n }\n\n join(ws, roomName) {\n if (!this.rooms.has(roomName)) {\n this.rooms.set(roomName, new Set());\n }\n this.rooms.get(roomName).add(ws);\n ws.room = roomName; // Salva a sala no socket\n }\n\n leave(ws) {\n if (ws.room && this.rooms.has(ws.room)) {\n this.rooms.get(ws.room).delete(ws);\n if (this.rooms.get(ws.room).size === 0) {\n this.rooms.delete(ws.room);\n }\n }\n }\n\n broadcast(roomName, message, excludeWs = null) {\n if (!this.rooms.has(roomName)) return;\n this.rooms.get(roomName).forEach(client => {\n if (client !== excludeWs && client.readyState === WebSocket.OPEN) {\n client.send(JSON.stringify(message));\n }\n });\n }\n}\n\n// Uso\nconst wss = new WebSocket.Server({ port: 8080 });\nconst roomManager = new RoomManager();\n\nwss.on('connection', (ws) => {\n ws.on('message', (data) => {\n const msg = JSON.parse(data);\n switch (msg.type) {\n case 'join':\n roomManager.join(ws, msg.room);\n break;\n case 'message':\n roomManager.broadcast(ws.room, { user: msg.user, text: msg.text }, ws);\n break;\n }\n });\n\n ws.on('close', () => roomManager.leave(ws));\n});\nEste código permite criar chats isolados ou sessões de jogo. Observe o tratamento do fechamento da conexão — isso é crítico para evitar vazamentos de memória.
\n\n2. Escalabilidade: clusterização e Redis Pub/Sub
\nUm único processo Node.js não pode utilizar eficientemente mais de um núcleo de CPU. Para escalar, usamos o módulo embutido cluster. No entanto, surge um problema: as conexões WebSocket estão vinculadas a um worker específico. Como enviar uma mensagem para um usuário conectado a outro worker?
A solução é um broker de mensagens externo, por exemplo, Redis Pub/Sub.
\nconst cluster = require('cluster');\nconst http = require('http');\nconst { Server } = require('ws');\nconst redis = require('redis');\n\nconst REDIS_CHANNEL = 'ws:messages';\n\nif (cluster.isMaster) {\n // Inicia workers de acordo com o número de núcleos\n const numCPUs = require('os').cpus().length;\n for (let i = 0; i < numCPUs; i++) {\n cluster.fork();\n }\n\n cluster.on('exit', (worker) => {\n console.log(`Worker ${worker.process.pid} morreu`);\n cluster.fork(); // Reinicia\n });\n} else {\n // Worker\n const server = http.createServer();\n const wss = new Server({ server });\n\n // Conecta ao Redis\n const pub = redis.createClient();\n const sub