WebSocket in JavaScript: fortgeschrittene Echtzeitanwendungen mit Node.js

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Fortgeschrittene Arbeit mit WebSocket in JavaScript: vom Prototyp zur Produktion

\n

WebSocket ist ein Protokoll, das eine Vollduplex-Kommunikation zwischen Client und Server über eine einzige permanente TCP-Verbindung ermöglicht. In früheren Artikeln haben wir die Grundlagen behandelt: Verbindungsaufbau, Nachrichtensenden und einfache Chats. Für die Erstellung wirklich zuverlässiger und skalierbarer Echtzeitanwendungen (Online-Spiele, Handelsplattformen, Systeme für kollaboratives Bearbeiten) ist es jedoch notwendig, fortgeschrittene Techniken zu beherrschen. In diesem Material betrachten wir die Architektur eines production-grade WebSocket-Servers auf Node.js, einschließlich Skalierung, Zustandsverwaltung, Autorisierung und Fehlerbehandlung.

\n\n

1. Fortgeschrittene Serverarchitektur und Raumverwaltung

\n

Ein einfacher WebSocket-Server (basierend auf der Bibliothek ws) behandelt alle Clients in einem globalen Objekt. In realen Projekten ist eine logische Isolierung erforderlich – Räume (rooms) und Kanäle.

\n

Betrachten wir ein Beispiel einer Architektur unter Verwendung der Klasse RoomManager:

\n
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; // Speichert den Raum auf dem 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// Verwendung\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});
\n

Dieser Code ermöglicht die Erstellung isolierter Chats oder Spielsitzungen. Beachten Sie die Behandlung der Verbindungsschließung – dies ist entscheidend, um Speicherlecks zu vermeiden.

\n\n

2. Skalierung: Clusterbildung und Redis Pub/Sub

\n

Ein einzelner Node.js-Prozess kann nicht effizient mehr als einen CPU-Kern nutzen. Zur Skalierung verwenden wir das integrierte Modul cluster. Es tritt jedoch ein Problem auf: WebSocket-Verbindungen sind an einen bestimmten Worker gebunden. Wie sendet man eine Nachricht an einen Benutzer, der mit einem anderen Worker verbunden ist?

\n

Die Lösung ist ein externer Nachrichtenbroker, zum Beispiel Redis Pub/Sub.

\n
const 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    // Startet Worker entsprechend der Anzahl der Kerne\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} died`);\n        cluster.fork(); // Neustart\n    });\n} else {\n    // Worker\n    const server = http.createServer();\n    const wss = new Server({ server });\n\n    // Verbindung zu Redis herstellen\n    const pub = redis.createClient();\n    const sub                

Blogs

Buchempfehlungen