Einführung: Jenseits des grundlegenden WebSocket
Ein grundlegendes Verständnis von WebSocket ermöglicht die Erstellung eines einfachen Chats oder von Benachrichtigungen. Für die Entwicklung von production-grade Real-Time-Anwendungen – Multiplayer-Spiele, Finanzterminals, Systeme für kollaboratives Bearbeiten – müssen jedoch fortgeschrittene Techniken beherrscht werden. In diesem Artikel gehen wir über "Hello, World" hinaus und analysieren, wie man zuverlässige, skalierbare und sichere WebSocket-Anwendungen mit Node.js erstellt.
Wir betrachten vier Schlüsselbereiche: Skalierung mit Redis, Zustands- und Raumverwaltung, Sicherheit und Autorisierung sowie Clustering für den Betrieb auf mehreren Prozessoren. Alle Codebeispiele sind in JavaScript unter Verwendung der Bibliothek ws und Node.js geschrieben.
1. Skalierung mit Pub/Sub (Redis)
Eine der Hauptbeschränkungen eines WebSocket-Servers ist, dass er nicht mit anderen Instanzen "kommunizieren" kann. Wenn Sie zwei Server hinter einem Load Balancer betreiben, erhält ein Client, der mit Server A verbunden ist, keine Nachricht, die über Server B gesendet wurde. Die Lösung ist die Verwendung des Pub/Sub-Musters (Publisher-Subscriber) auf Basis von Redis.
Redis fungiert als zentraler Broker: Jeder Server abonniert einen Kanal und veröffentlicht Nachrichten darin. Auf diese Weise wird eine von einem beliebigen Server gesendete Nachricht sofort an alle anderen verteilt.
const WebSocket = require('ws');const redis = require('redis');
const wss = new WebSocket.Server({ port: 8080 });const publisher = redis.createClient();const subscriber = redis.createClient();
// Abonnieren des Kanals 'broadcast'subscriber.subscribe('broadcast');subscriber.on('message', (channel, message) => { // Senden der Nachricht an alle verbundenen Clients wss.clients.forEach(client => { if (client.readyState === WebSocket.OPEN) { client.send(message); } });});
wss.on('connection', (ws) => { ws.on('message', (data) => { // Veröffentlichen der Nachricht in Redis publisher.publish('broadcast', data.toString()); });});Dieser Ansatz ermöglicht es, Ihre Anwendung praktisch unbegrenzt horizontal zu skalieren. Beim Hinzufügen eines neuen Servers erstellt dieser einfach sein eigenes Abonnement bei Redis.
2. Verwaltung von Räumen und Zuständen
In den meisten Real-Time-Anwendungen ist es notwendig, Clients in "Räumen" zu gruppieren (Chat-Räume, Spiel-Lobbys) und deren Zustand zu verwalten. Das grundlegende wss.clients bietet diese Funktionalität nicht. Wir implementieren ein einfaches, aber effektives Raumsystem.
Wir erstellen ein Objekt rooms, bei dem der Schlüssel der Raumname und der Wert ein Set von WebSocket-Clients ist. Wir fügen Methoden zum Beitreten, Verlassen und für Rundsendungen innerhalb des Raumes hinzu.
const rooms = new Map();
function joinRoom(ws, roomName) { if (!rooms.has(roomName)) { rooms.set(roomName, new Set()); } rooms.get(roomName).add(ws); // Speichern des Raums am ws-Objekt für schnellen Zugriff 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));});Zur Zustandsverwaltung (z. B. Spielerposition, Cursor) können Daten in einer Map gespeichert werden.