Socket.IO 4.8
Überblick
Diese Skill enthält die Socket.IO 4.8 Regeln, die leicht falsch gemacht werden: eine flüchtige socket.id als Benutzerkennung, doppelt registrierte Listener, Acknowledgements deren Callback-Form sich mit timeout() ändert, CORS, das WebSocket nicht schützt, vergessene Sticky Sessions hinter einem Load Balancer und v2- oder v3-APIs, die nicht mehr funktionieren. Jede Behauptung enthält die URL der Seite, aus der sie stammt. Die Referenzdateien enthalten die Details pro Bereich.
Abgedeckte Version
Socket.IO 4.8 (Server socket.io, Client socket.io-client), Docs v4. JS-Clients 3.x und 4.x kommunizieren mit Servern 3.x und 4.x. https://socket.io/docs/v4/client-installation/#version-compatibility
Kurzanleitung
- An den selbst erstellten HTTP-Server anhängen und darauf
listenaufrufen, niemals auf der Expressapp:app.listen()erstellt einen weiteren HTTP-Server, an dem Socket.IO nicht hängt. https://socket.io/docs/v4/server-initialization/#with-express
import express from "express";
import { createServer } from "node:http";
import { Server } from "socket.io";
const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, { cors: { origin: ["https://example.com"] } });
io.on("connection", (socket) => {
socket.on("hello", (arg, callback) => callback("got it"));
});
httpServer.listen(3000);
https://socket.io/docs/v4/server-initialization/#with-express
- Client:
io()auf dem gleichen Ursprung,io("https://server-domain.com")Cross-Origin (CORS muss auf dem Server aktiviert sein). Import mitimport { io } from "socket.io-client". https://socket.io/docs/v4/client-initialization/#from-a-different-domain
import { io } from "socket.io-client";
const socket = io("https://server-domain.com", { auth: { token: "abc" } });
const answer = await socket.timeout(5000).emitWithAck("hello", "world");
https://socket.io/docs/v4/client-api/#socketemitwithackeventname-args
- Typdefinitionen werden mit
socket.io-clientmitgeliefert. Installieren Sie nicht@types/socket.io-client, es kollidiert mit den gebündelten Typdefinitionen. https://socket.io/docs/v4/client-installation/#from-npm
Regeln und Fallstricke
Verbindung und Identität
- Socket.IO ist keine WebSocket-Implementierung: Ein reiner WebSocket-Client kann keinen Socket.IO-Server erreichen, und der Socket.IO-Client kann keinen reinen WebSocket-Server erreichen. https://socket.io/docs/v4/#what-socketio-is-not
- Sofern keine Verbindungszustandswiederherstellung aktiviert ist, ändert sich
socket.idbei jeder Wiederverbindung und jedem Seitenrefresh, unterscheidet sich pro Tab und hat keine Nachrichtenwarteschlange. Identifizieren Sie Benutzer mit Ihrer eigenen Sitzungs-ID, die in einem Cookie oder inauthgesendet wird, und erreichen Sie jeden Tab eines Benutzers über einen Raum, der nach der Benutzer-ID benannt ist. https://socket.io/docs/v4/server-socket-instance/#socketid
io.on("connection", async (socket) => {
const userId = await computeUserIdFromHeaders(socket.handshake.headers);
socket.join(userId);
io.to(userId).emit("hi");
});
https://socket.io/docs/v4/rooms/#sample-use-cases
- Authentifizieren Sie in einer Verbindungs-Middleware mit
socket.handshake.auth. Rufen Sienext()in jedem Codepfad auf, andernfalls bleibt die Verbindung hängen, bis sie nach einem Timeout geschlossen wird.next(err)erreicht den Client alsconnect_error, miterr.datafür Details. https://socket.io/docs/v4/middlewares/
io.use((socket, next) => {
if (isValid(socket.handshake.auth.token)) return next();
const err = new Error("not authorized");
err.data = { content: "Please retry later" };
next(err);
});
https://socket.io/docs/v4/middlewares/#handling-middleware-error
- Eine von einer Middleware abgelehnte Verbindung wird nicht wiederholt. Bei
connect_errorbedeutetsocket.active === false, dass die Verbindung vom Server abgelehnt wurde und der Client selbstsocket.connect()aufrufen muss, um sich erneut zu verbinden. https://socket.io/docs/v4/client-socket-instance/#connect_error - Registrieren Sie Client-Listener auf oberster Ebene, niemals innerhalb des
connect-Handlers:connectwird bei jeder Wiederverbindung erneut ausgelöst und fügt jedes Mal einen doppelten Listener hinzu. https://socket.io/docs/v4/client-socket-instance/#connect - Registrieren Sie auf dem Server
socket.on(...)synchron imconnection-Handler, vor jedemawait, sonst erreichen frühe Pakete keinen Handler. https://socket.io/docs/v4/troubleshooting-connection-issues/#delayed-event-handler-registration - Express-Middlewares gehören zu
io.engine.use(...), nicht zuio.use(...). https://socket.io/docs/v4/middlewares/#compatibility-with-express-middleware
Ereignisse und Bestätigungen
- Übergeben Sie Objekte so, wie sie sind. Verwenden Sie niemals
JSON.stringify()für eine Nutzlast.Datekommt als Zeichenkette an,MapundSetmüssen manuell konvertiert werden. https://socket.io/docs/v4/emitting-events/#basic-emit - Der Ack-Callback ist das letzte Argument. Ohne
timeout()erhält er(response), mittimeout(ms)erhält er(err, response). https://socket.io/docs/v4/emitting-events/#with-timeout - Bevorzugen Sie
await socket.timeout(ms).emitWithAck(...)in einemtry-Block: Es wird bei Timeout abgelehnt. Die empfangende Seite ruft ihren Callback mit 1 Argument auf. https://socket.io/docs/v4/client-api/#socketemitwithackeventname-args - Ein Broadcast mit Bestätigung erfordert
timeout()und gibt 1 Antwort pro Client zurück. https://socket.io/docs/v4/server-api/#serveremiteventname-args
const responses = await io.timeout(10000).emitWithAck("some-event");
https://socket.io/docs/v4/server-api/#serveremitwithackeventname-args
- Listener haben keine eingebaute Fehlerbehandlung und keine Validierung. Fangen Sie Fehler in jedem Handler ab, validieren Sie Nutzlasten mit einer Schema-Bibliothek und prüfen Sie
typeof callback === "function", bevor Sie es verwenden. https://socket.io/docs/v4/listening-to-events/#validation - Verwenden Sie niemals
connect,connect_error,disconnect,disconnecting,newListeneroderremoveListenerals Anwendungsereignis: Das Senden eines reservierten Namens wiesocket.emit("disconnecting")wirft einen Fehler. https://socket.io/docs/v4/emit-cheatsheet/
Broadcasting und Räume
socket.to(raum)undsocket.broadcastschließen den Sender aus,io.to(raum)nicht.socket.to(socket.id).emit()sendet nichts: Verwenden Sieio.to(socket.id).emit(). https://socket.io/docs/v4/server-api/#sockettoroom- Verkettete
to()-Aufrufe sind eine Vereinigung, jeder Socket empfängt das Ereignis einmal. https://socket.io/docs/v4/rooms/#joining-and-leaving - Jeder Broadcast-Modifikator gibt einen neuen unveränderlichen Operator zurück: Bewahren Sie ihn in einer Variablen auf, anstatt
io.to()und dannio.emit()aufzurufen. https://socket.io/docs/v4/migrating-from-3-x-to-4-0/#ioto-is-now-immutable - Raumnamen werden nicht umgewandelt:
socket.join(42)stimmt nicht mitio.to("42")überein. https://socket.io/docs/v4/migrating-from-2-x-to-3-0/#room-names-are-not-coerced-to-string-anymore - Räume werden bei Trennung automatisch verlassen. Lesen Sie sie in
disconnecting, nicht indisconnect, wosocket.roomsleer ist. https://socket.io/docs/v4/rooms/#disconnection - Lesen Sie den Adapter mit
io.of("/").adapter: Die Dokumentation warnt, dassio.adapter()dafür nicht funktioniert, es ist der Setter. https://socket.io/docs/v4/adapter/#api
Zustellung
- Die Standardgarantie ist höchstens einmal und der Server speichert keine Nachrichten: Ein getrennter Client verpasst Broadcasts für immer. Spielen Sie sie aus einer Datenbank mit einem in
authgesendeten Offset erneut ab oder aktivieren Sie die Verbindungszustandswiederherstellung. https://socket.io/docs/v4/delivery-guarantees#at-most-once - Der Client puffert Emits, während er offline ist, und sendet sie bei Wiederverbindung. Überprüfen Sie
socket.connectedoder verwenden Siesocket.volatile.emit(...)für Daten, die verworfen werden können. https://socket.io/docs/v4/client-offline-behavior/#buffered-events - Client zu Server mindestens einmal:
io({ retries: 3, ackTimeout: 10000 }). Für genau einmal speichern Sie eine clientseitig generierte ID unter einer eindeutigen Einschränkung. Bestätigen Sie bei Erfolg und bei einem Duplikat, aber bestätigen Sie bei keinem anderen Fehler, sodass der Client ein Timeout erhält und es erneut versucht. https://socket.io/docs/v4/tutorial/step-8 - Die Verbindungszustandswiederherstellung ist optional, erfordert eine begrenzte
maxDisconnectionDurationund kann fehlschlagen: Verzweigen Sie mitsocket.recovered. Der Redis (Pub/Sub)-Adapter unterstützt es nicht, der Redis Streams-Adapter schon. https://socket.io/docs/v4/connection-state-recovery
const io = new Server(httpServer, {
connectionStateRecovery: { maxDisconnectionDuration: 2 * 60 * 1000, skipMiddlewares: false },
});
https://socket.io/docs/v4/connection-state-recovery#usage
CORS und Bereitstellung
- CORS ist standardmäßig deaktiviert. Die Option ist
cors.origin, niemalsorigins.origin: "*"mitcredentials: trueschlägt im Browser fehl: Listen Sie explizite Ursprünge auf. https://socket.io/docs/v4/server-options/#cors - CORS deckt nur Browser über HTTP Long-Polling ab, nicht WebSocket. Beschränken Sie, wer sich mit
allowRequestverbindet. https://socket.io/docs/v4/handling-cors/ - Ein Proxy muss die
Upgrade- undConnection-Header weiterleiten, und sein Lese-Timeout musspingInterval + pingTimeout(standardmäßig 45 Sekunden) überschreiten, sonst sehen Clientstransport close. https://socket.io/docs/v4/reverse-proxy/#nginx - Bei mehreren Servern und aktiviertem HTTP Long-Polling sind Sticky Sessions weiterhin erforderlich, unabhängig vom Adapter (die Alternativen sind Deaktivieren von Long-Polling oder clientseitiger Lastausgleich). https://socket.io/docs/v4/using-multiple-nodes/ und https://socket.io/docs/v4/adapter/
- Ohne Sticky Sessions erhalten Clients HTTP 400-Antworten mit
{"code":1,"message":"Session ID unknown"}. https://socket.io/docs/v4/troubleshooting-connection-issues/#you-didnt-enable-sticky-sessions-in-a-multi-server-setup path(Standard/socket.io/) muss auf beiden Seiten übereinstimmen.io("/my-path/")wählt einen Namespace aus, keinen Pfad. https://socket.io/docs/v4/troubleshooting-connection-issues/#the-request-path-does-not-match-on-both-sides- Schließen Sie mit
io.close(), was auch den HTTP-Server schließt. https://socket.io/docs/v4/server-api/#serverclosecallback
Referenzdateien
| Datei | Lesen für |
|---|---|
| references/server-setup.md | Installation, Initialisierung mit Express, Koa, Fastify, Hono, HTTPS, HTTP/2, Bun, uWebSockets.js, Serveroptionen und Standardwerte, Bündelung, Handler-Layout |
| references/middleware-and-auth.md | Verbindungs-, Socket- und Engine-Middlewares, Anmeldeinformationen, Benutzeridentität, Header-Hooks, allowRequest, Client-Zertifikate |
| references/cors-and-proxies.md | CORS-Optionen und -Fehler, Cookies, nginx, Apache, Caddy, http-proxy, Cloud-Load-Balancer, Portfreigabe mit ws |
| references/server-api.md | Server- und Socket-Instanzen, Namespaces auf io, Hilfsmethoden, serverSideEmit, Trennungsgründe, Engine-Ereignisse und Fehlercodes, Schließen |
| references/client.md | Bundles, Initialisierung, Socket-Ereignisse, connect_error und disconnect, Manager und Multiplexing, alle Client-Optionen, Bundler |
| references/events-rooms-namespaces.md | Serialisierung, Bestätigungen, Catch-All-Listener, Validierung, Fehlerbehandlung, Broadcasting, Räume, dynamische Namespaces |
| references/delivery-and-recovery.md | Liefergarantien, Wiederholungen, genau einmal, Offset-Wiedergabe, Verbindungszustandswiederherstellung |
| references/multiple-nodes.md | Sticky Sessions, Proxy-Konfigurationen für mehrere Knoten, Alternativen, Node.js Cluster, PM2 |
| references/adapters.md | Redis, Sharded Redis, Redis Streams, Postgres, MongoDB, Cluster, AWS SQS, Azure, GCP-Adapter, Feature-Matrix, Emitter |
| references/performance-and-operations.md | Native Add-ons, wsEngine, Speicher, benutzerdefinierte Parser, OS-Grenzen, Lasttests, Admin-UI, Debug-Logs |
| references/typescript.md | Ereignis-Schnittstellen auf Server, Client und Namespaces, SocketData, typisierte Tests |
| references/testing-and-troubleshooting.md | Server-Integrationstests, Client-Tests mit einem Fake-Socket, Verbindungsfehler, Trennungen, feststeckendes Long-Polling |
| references/migrations-and-protocol.md | Änderungen von v2 zu v3 und v3 zu v4, rollierendes Upgrade mit allowEIO3, Engine.IO- und Socket.IO-Drahtprotokolle |
Alte Muster
| Alte Verwendung | Ersatz | Quelle |
|---|---|---|
origins oder handlePreflightRequest-Option | cors: { origin: [...] } | https://socket.io/docs/v4/migrating-from-2-x-to-3-0/#cors-handling |
io.set("authorization", fn) | io.use((socket, next) => ...) | https://socket.io/docs/v4/migrating-from-2-x-to-3-0/#ioset-is-removed |
Token in der Socket-query-Option | auth-Option, gelesen als socket.handshake.auth | https://socket.io/docs/v4/migrating-from-2-x-to-3-0/ |
Client-error-Ereignis für eine abgelehnte Verbindung | connect_error, mit err.message und err.data | https://socket.io/docs/v4/migrating-from-2-x-to-3-0/ |
socket.on("reconnect_attempt") auf dem Socket | socket.io.on("reconnect_attempt") auf dem Manager | https://socket.io/docs/v4/client-socket-instance/#events |
Client-pong-Ereignis für Latenz | socket.volatile.emit("ping", cb) in einem Intervall | https://socket.io/docs/v4/migrating-from-2-x-to-3-0/#no-more-pong-event-for-retrieving-latency |
Namespace.connected-Objekt | Namespace.sockets, ein Map | https://socket.io/docs/v4/migrating-from-2-x-to-3-0/ |
Object.keys(socket.rooms) | socket.rooms ist ein Set | https://socket.io/docs/v4/migrating-from-2-x-to-3-0/ |
socket.join(room, cb) | socket.join(room), erwartet mit einem asynchronen Adapter | https://socket.io/docs/v4/migrating-from-2-x-to-3-0/ |
Namespace.clients(cb) (v2), dann allSockets() (v3, wird in der nächsten Hauptversion entfernt) | fetchSockets() | https://socket.io/docs/v4/server-api/#namespaceallsockets |
socket.binary(false) | Ein benutzerdefinierter parser | https://socket.io/docs/v4/migrating-from-2-x-to-3-0/ |
socket.emit("a").emit("b") | Getrennte Aufrufe, emit() gibt true zurück | https://socket.io/docs/v4/migrating-from-2-x-to-3-0/ |
Standardimport von socket.io oder socket.io-client | import { Server } from "socket.io" und import { io } from "socket.io-client" | https://socket.io/docs/v4/server-installation/#from-npm und https://socket.io/docs/v4/client-installation/#from-npm |
socket.packet({ ... }) (Engine.IO) | socket.send([...]) (Socket.IO) | https://socket.io/docs/v4/migrating-from-3-x-to-4-0/ |