WebSocket in Node.js: server, client, heartbeat e riconnessione
WebSocket in Node.js: server ws, client integrato, heartbeat | We.Inc
Crea un server WebSocket in Node.js con il pacchetto ws, connettiti con il client WebSocket integrato in Node, rileva i socket morti con ping e pong e riconnettiti con backoff.
Node.js non ha un server WebSocket integrato, quindi la maggior parte dei progetti installa il pacchetto ws e crea un WebSocketServer. Per il lato client, Node include un WebSocket globale compatibile con il browser, aggiunto in v21.0.0 e v20.10.0, senza flag da v22.0.0 e dichiarato stabile in v22.4.0. Aggiungi un heartbeat con ping e pong e la riconnessione con backoff prima di andare in produzione.
Quale parte fa cosa
Compito
Usa
Da dove arriva
Accettare connessioni
new WebSocketServer({ port })
Pacchetto ws (npm install ws)
Connettersi da Node
new WebSocket(url)
Globale in Node 22 e successivi
Connettersi da un browser
new WebSocket(url)
Integrato in ogni browser moderno
Trovare i socket morti
ws.ping() più un listener pong
Pacchetto ws
Sopravvivere ai riavvii
Riconnessione con backoff esponenziale
Il tuo codice client
Questa guida costruisce un piccolo server in stile chat e i client che ci parlano, in quest'ordine: un server che fa echo e broadcast, uno script Node che si connette senza alcuna dipendenza, un heartbeat che scarta le connessioni morte in silenzio e un client che ritorna dopo il riavvio del server. Il calcolatore qui sotto mostra come crescono i ritardi tra i tentativi prima che tu li metta nel codice.
Esegui npm install ws in un progetto il cui package.json ha "type": "module", così la sintassi import degli esempi funziona.
Avvia il file del server con node server.js. Ascolta sulla porta 8080 e registra ogni messaggio.
Esegui il file del client in un secondo terminale con Node 22 o successivo, oppure apri la console del browser e incolla lo stesso codice.
Ferma il server con Ctrl+C e riavvialo per vedere il client che aspetta, ritenta e si ricollega.
Collega sempre un listener error a ogni socket lato server. ws emette eventi error, e uno non gestito può far crashare il processo.
Controlla req.headers.origin nell'handler di connessione e chiudi i socket provenienti da origini che non servi; i browser lo inviano, anche dalle pagine degli attaccanti.
I reverse proxy chiudono i socket inattivi. nginx chiude un WebSocket in proxy se il backend non invia nulla per 60 secondi, di default, quindi tieni l'intervallo di ping sotto quel valore.
Aggiungi un jitter casuale ai ritardi di riconnessione. Senza, dopo un riavvio ogni client ritenta nello stesso istante e colpisce il server tutto insieme.
I cicli di broadcast devono saltare i client il cui readyState non è OPEN, perché inviare a un socket in chiusura non serve a nulla.
Domande frequenti
Node.js ha WebSocket integrato?
Solo il client. La classe globale WebSocket è comparsa in v21.0.0 e v20.10.0 dietro il flag --experimental-websocket, ha perso il flag in v22.0.0 e non è più sperimentale da v22.4.0. Non esiste ancora un server integrato, ed è per questo che ws resta la scelta abituale.
Devo usare ws o Socket.IO?
ws parla il protocollo WebSocket puro, quindi qualsiasi client standard può connettersi, compresi il browser e il globale di Node. Socket.IO aggiunge sopra un suo protocollo, con stanze e riconnessione automatica, ma richiede la sua libreria client dall'altra parte.
Perché le connessioni muoiono senza un evento close?
Se un percorso di rete sparisce, ad esempio quando si chiude il coperchio di un portatile, nessun frame di chiusura viene inviato e il server tiene un socket semiaperto. L'heartbeat li trova: un socket che non ha risposto all'ultimo ping viene terminato al giro successivo.
Un client browser può inviare ping?
No. L'API del browser ha solo send() e close(). I browser però rispondono da soli ai ping del server con dei pong, ed è per questo che l'heartbeat lato server funziona anche con i client browser.
Come condivido una porta con un server HTTP?
Crea tu il server HTTP, ad esempio con Express o node:http, poi passalo come new WebSocketServer({ server }). Le richieste di upgrade vanno a ws e quelle normali alla tua app.
Come inizia una connessione
Un WebSocket nasce come una HTTP GET con Upgrade: websocket. Il server ws la verifica, risponde 101 Switching Protocols ed emette il suo evento connection con il nuovo socket e la richiesta originale. Tutto ciò che ti serve dall'handshake, come un token nella query string o l'header Origin, si legge da quella richiesta.
Gli heartbeat spiegati semplice
Il protocollo dice che un endpoint che riceve un ping deve rispondere con un pong che porta gli stessi dati. Il server segna ogni socket come vivo quando arriva un pong, poi a intervalli cancella il segno e manda di nuovo un ping. Qualsiasi socket ancora non segnato al tick successivo non ha mai risposto, quindi il server lo termina.
Il README di ws usa un timer di 30 secondi per questo. Scegli un valore ben al di sotto del timeout di inattività di qualsiasi proxy davanti a te.
Riconnettersi senza martellare il server
Un buon client aspetta un po' dopo la prima caduta, poi più a lungo dopo ogni fallimento, fino a un tetto, e azzera il contatore quando una connessione si apre. Distribuire le attese in modo casuale evita che una folla di client torni tutta in sincrono.
Passo 1: un server con ws
server.js: echo e broadcast
Ogni messaggio viene rimandato a tutti i client aperti. Il listener error evita che un singolo socket difettoso faccia crashare il processo.
Passo 2: un client senza dipendenze
client.js: il WebSocket globale di Node
Funziona senza modifiche su Node 22 o successivo e nella console di un browser, perché entrambi espongono la stessa classe WebSocket.
Passo 3: eliminare le connessioni morte con ping e pong
La nostra versione dello schema del README di ws: segna come vivo al pong, termina tutto ciò che ha saltato l'ultimo giro.
Passo 4: un client che si riconnette
reconnect.js: backoff esponenziale con jitter
Il ritardo raddoppia dopo ogni tentativo fallito fino a un tetto, riceve una variazione casuale e si azzera quando una connessione si apre.
Dietro nginx
nginx lascia passare l'upgrade solo se inoltri tu gli header Upgrade e Connection. Prima di nginx 1.29.7 serve anche proxy_http_version 1.1.
Location nginx per il socket
proxy_read_timeout alza il limite di inattività di 60 secondi; l'heartbeat qui sopra mantiene comunque il traffico attivo.
Calcolatore del ritardo di riconnessione
Imposta un primo ritardo, un moltiplicatore e un tetto per vedere quanto aspetta un client prima di ogni tentativo, e l'attesa più breve e più lunga una volta aggiunto il jitter.
Primo ritardo (ms)
Moltiplicatore
Tetto (ms)
Tentativi da mostrare
Aggiungi jitter (attesa tra metà e l'intero ritardo)