WebSocket in Node.js: server, client, heartbeat en reconnect
WebSocket in Node.js: ws-server, ingebouwde client, heartbeat | We.Inc
Bouw een WebSocket-server in Node.js met het ws-pakket, verbind met de ingebouwde WebSocket-client van Node, spoor dode sockets op met ping en pong en maak opnieuw verbinding met backoff.
Node.js heeft geen ingebouwde WebSocket-server, dus de meeste projecten installeren het ws-pakket en maken een WebSocketServer. Voor de clientkant levert Node een browsercompatibele globale WebSocket, toegevoegd in v21.0.0 en v20.10.0, zonder vlag sinds v22.0.0 en stabiel sinds v22.4.0. Voeg een ping-en-pong-heartbeat en reconnect met backoff toe voordat u live gaat.
Welk onderdeel wat doet
Taak
Gebruik
Waar het vandaan komt
Verbindingen accepteren
new WebSocketServer({ port })
ws-pakket (npm install ws)
Verbinden vanuit Node
new WebSocket(url)
Globaal in Node 22 en later
Verbinden vanuit een browser
new WebSocket(url)
Ingebouwd in elke moderne browser
Dode sockets vinden
ws.ping() plus een pong-listener
ws-pakket
Herstarts overleven
Opnieuw verbinden met exponentiële backoff
Uw eigen clientcode
Deze gids bouwt één kleine chatachtige server en de clients die ermee praten, in deze volgorde: een server die terugkaatst en broadcast, een Node-script dat zonder enige dependency verbindt, een heartbeat die stilletjes gestorven verbindingen opruimt, en een client die terugkomt nadat de server herstart. De calculator hieronder laat zien hoe uw wachttijden tussen pogingen oplopen voordat u ze in code zet.
Voer npm install ws uit in een project waarvan package.json "type": "module" bevat, zodat de importsyntax in de voorbeelden werkt.
Start het serverbestand met node server.js. Het luistert op poort 8080 en logt elk bericht.
Voer het clientbestand uit in een tweede terminal met Node 22 of nieuwer, of open de browserconsole en plak dezelfde code.
Stop de server met Ctrl+C en start hem opnieuw om te zien hoe de client wacht, opnieuw probeert en herstelt.
Koppel altijd een error-listener aan elke socket aan de serverkant. ws stuurt error-events uit, en een onafgehandeld event kan het proces laten crashen.
Controleer req.headers.origin in de connection-handler en sluit sockets van origins die u niet bedient; browsers sturen die mee, ook vanaf pagina's van aanvallers.
Reverse proxies verbreken inactieve sockets. nginx sluit standaard een geproxyde WebSocket als de backend 60 seconden niets stuurt, dus houd uw ping-interval daaronder.
Voeg willekeurige jitter toe aan de wachttijden voor reconnect. Zonder jitter proberen alle clients na een herstart op hetzelfde moment opnieuw en treffen ze de server tegelijk.
Broadcastlussen moeten clients overslaan waarvan readyState niet OPEN is, want versturen naar een socket die sluit heeft geen zin.
Veelgestelde vragen
Heeft Node.js WebSocket ingebouwd?
Alleen de client. De globale WebSocket-klasse verscheen in v21.0.0 en v20.10.0 achter de vlag --experimental-websocket, verloor de vlag in v22.0.0 en was vanaf v22.4.0 niet langer experimenteel. Er is nog steeds geen ingebouwde server, en daarom blijft ws de gebruikelijke keuze.
Moet ik ws of Socket.IO gebruiken?
ws spreekt het gewone WebSocket-protocol, dus elke standaardclient, inclusief de browser en de globale variant van Node, kan verbinden. Socket.IO voegt er een eigen protocol bovenop, met rooms en automatisch opnieuw verbinden, maar heeft aan de andere kant een eigen clientlibrary nodig.
Waarom sterven verbindingen zonder close-event?
Als een netwerkpad verdwijnt, bijvoorbeeld als een laptop wordt dichtgeklapt, wordt er nooit een close-frame gestuurd en houdt de server een half-open socket vast. De heartbeat vindt deze: een socket die de laatste ping niet beantwoordde, wordt in de volgende ronde beëindigd.
Kan een browserclient pings sturen?
Nee. De browser-API heeft alleen send() en close(). Browsers beantwoorden pings van de server wel zelf met pongs, en daarom werkt de heartbeat aan de serverkant ook met browserclients.
Hoe deel ik een poort met een HTTP-server?
Maak de HTTP-server zelf, bijvoorbeeld met Express of node:http, en geef hem mee als new WebSocketServer({ server }). Upgrade-verzoeken gaan naar ws en normale verzoeken naar uw app.
Hoe een verbinding begint
Een WebSocket begint als een HTTP GET met Upgrade: websocket. De ws-server controleert die, antwoordt met 101 Switching Protocols en vuurt zijn connection-event af met de nieuwe socket en het oorspronkelijke verzoek. Alles wat u uit de handshake wilt halen, zoals een token in de querystring of de Origin-header, leest u uit dat verzoek.
Heartbeats in gewone taal
Het protocol zegt dat een endpoint dat een ping ontvangt, moet antwoorden met een pong met dezelfde data. De server markeert elke socket als levend als er een pong binnenkomt, wist die markering daarna op een timer en pingt opnieuw. Elke socket die bij de volgende tik nog ongemarkeerd is, heeft nooit geantwoord, dus de server beëindigt hem.
De README van ws gebruikt hiervoor een timer van 30 seconden. Kies een waarde die ruim onder de idle-timeout ligt van elke proxy die vóór u staat.
Opnieuw verbinden zonder de server te bestoken
Een goede client wacht na de eerste verbreking even, daarna na elke mislukking langer, tot een plafond, en zet de teller terug zodra een verbinding opent. Door de wachttijden willekeurig te spreiden komt een menigte clients niet in hetzelfde ritme terug.
Stap 1: een server met ws
server.js: echo en broadcast
Elk bericht wordt teruggestuurd naar elke open client. De error-listener voorkomt dat één slechte socket het proces laat crashen.
Stap 2: een client zonder dependencies
client.js: de globale WebSocket van Node
Draait ongewijzigd op Node 22 of later en in een browserconsole, omdat beide dezelfde WebSocket-klasse bieden.
Stap 3: dode verbindingen opruimen met ping en pong
Onze versie van het patroon uit de README van ws: markeer als levend bij een pong, beëindig alles wat de laatste ronde miste.
Stap 4: een client die opnieuw verbindt
reconnect.js: exponentiële backoff met jitter
De wachttijd verdubbelt na elke mislukte poging tot een plafond, krijgt een willekeurige spreiding en wordt teruggezet zodra een verbinding opent.
Achter nginx
nginx geeft de upgrade alleen door als u de headers Upgrade en Connection zelf doorstuurt. Vóór nginx 1.29.7 hebt u ook proxy_http_version 1.1 nodig.
nginx-location voor de socket
proxy_read_timeout verhoogt de idle-limiet van 60 seconden; de heartbeat hierboven houdt het verkeer sowieso op gang.
Calculator voor reconnect-wachttijd
Stel een eerste wachttijd, een vermenigvuldiger en een plafond in om te zien hoe lang een client voor elke nieuwe poging wacht, en wat de kortste en langste wachttijd is met jitter.
Eerste wachttijd (ms)
Vermenigvuldiger
Plafond (ms)
Aantal pogingen tonen
Jitter toevoegen (wacht tussen de helft en de volle wachttijd)