Créez un serveur WebSocket en Node.js avec le paquet ws, connectez-vous avec le client WebSocket intégré de Node, détectez les sockets morts avec ping et pong, et reconnectez-vous avec un backoff.
Node.js n'a pas de serveur WebSocket intégré, donc la plupart des projets installent le paquet ws et créent un WebSocketServer. Côté client, Node fournit un WebSocket global compatible avec celui des navigateurs, ajouté dans v21.0.0 et v20.10.0, sans flag depuis v22.0.0 et marqué stable dans v22.4.0. Ajoutez un heartbeat ping et pong ainsi qu'une reconnexion avec backoff avant la mise en production.
Quel élément fait quoi
Tâche
Utiliser
Provenance
Accepter les connexions
new WebSocketServer({ port })
Paquet ws (npm install ws)
Se connecter depuis Node
new WebSocket(url)
Global dans Node 22 et suivants
Se connecter depuis un navigateur
new WebSocket(url)
Intégré à tous les navigateurs modernes
Trouver les sockets morts
ws.ping() plus un listener pong
Paquet ws
Survivre aux redémarrages
Reconnexion avec backoff exponentiel
Votre propre code client
Ce guide construit un petit serveur de type chat et les clients qui lui parlent, dans l'ordre : un serveur qui renvoie et diffuse les messages, un script Node qui se connecte sans aucune dépendance, un heartbeat qui élimine les connexions mortes sans bruit, et un client qui revient après un redémarrage du serveur. Le calculateur ci-dessous montre comment vos délais de nouvelle tentative augmentent avant de les mettre dans le code.
Lancez npm install ws dans un projet dont le package.json contient "type": "module", pour que la syntaxe import des exemples fonctionne.
Démarrez le fichier serveur avec node server.js. Il écoute sur le port 8080 et journalise chaque message.
Lancez le fichier client dans un second terminal avec Node 22 ou plus récent, ou ouvrez la console du navigateur et collez le même code.
Arrêtez le serveur avec Ctrl+C et relancez-le pour voir le client attendre, réessayer et se rétablir.
Attachez toujours un listener error à chaque socket côté serveur. ws émet des événements error, et un événement non géré peut faire planter le processus.
Vérifiez req.headers.origin dans le gestionnaire de connexion et fermez les sockets provenant d'origines que vous ne servez pas ; les navigateurs l'envoient, y compris depuis les pages d'attaquants.
Les reverse proxies coupent les sockets inactifs. nginx ferme par défaut un WebSocket proxifié si le backend n'envoie rien pendant 60 secondes, gardez donc votre intervalle de ping en dessous.
Ajoutez un jitter aléatoire aux délais de reconnexion. Sans lui, tous les clients réessaient au même instant après un redémarrage et frappent le serveur ensemble.
Les boucles de diffusion doivent ignorer les clients dont le readyState n'est pas OPEN, car envoyer à un socket en cours de fermeture ne sert à rien.
Questions fréquentes
Node.js intègre-t-il WebSocket ?
Seulement le client. La classe globale WebSocket est apparue dans v21.0.0 et v20.10.0 derrière le flag --experimental-websocket, a perdu ce flag dans v22.0.0 et n'est plus expérimentale depuis v22.4.0. Il n'y a toujours pas de serveur intégré, c'est pourquoi ws reste le choix habituel.
Faut-il utiliser ws ou Socket.IO ?
ws parle le protocole WebSocket standard, donc n'importe quel client standard, y compris le navigateur et le global de Node, peut se connecter. Socket.IO ajoute son propre protocole par-dessus, avec des rooms et la reconnexion automatique, mais nécessite sa propre bibliothèque client de l'autre côté.
Pourquoi des connexions meurent-elles sans événement close ?
Si un chemin réseau disparaît, par exemple quand on ferme l'écran d'un portable, aucune trame de fermeture n'est envoyée et le serveur garde un socket à moitié ouvert. Le heartbeat les repère : un socket qui n'a pas répondu au dernier ping est terminé au tour suivant.
Un client navigateur peut-il envoyer des pings ?
Non. L'API du navigateur n'a que send() et close(). Les navigateurs répondent d'eux-mêmes aux pings du serveur par des pongs, c'est pourquoi le heartbeat côté serveur fonctionne aussi avec les clients navigateur.
Comment partager un port avec un serveur HTTP ?
Créez vous-même le serveur HTTP, par exemple avec Express ou node:http, puis passez-le avec new WebSocketServer({ server }). Les requêtes Upgrade vont à ws et les requêtes normales à votre application.
Comment démarre une connexion
Un WebSocket commence sa vie comme un HTTP GET avec Upgrade: websocket. Le serveur ws le vérifie, répond 101 Switching Protocols et déclenche son événement connection avec le nouveau socket et la requête d'origine. Tout ce que vous voulez tirer de la négociation, comme un jeton dans la query string ou l'en-tête Origin, se lit dans cette requête.
Le heartbeat en termes simples
Le protocole indique qu'un point de terminaison qui reçoit un ping doit répondre par un pong portant les mêmes données. Le serveur marque chaque socket comme vivant quand un pong arrive, puis, à intervalle régulier, efface la marque et envoie un nouveau ping. Tout socket encore non marqué au tick suivant n'a jamais répondu, donc le serveur le termine.
Le README de ws utilise un intervalle de 30 secondes pour cela. Choisissez une valeur nettement inférieure au délai d'inactivité de tout proxy placé devant vous.
Se reconnecter sans marteler le serveur
Un bon client attend un peu après la première coupure, puis plus longtemps après chaque échec, jusqu'à un plafond, et remet le compteur à zéro dès qu'une connexion s'ouvre. Répartir les attentes au hasard évite qu'une foule de clients revienne en même temps.
Étape 1 : un serveur avec ws
server.js : écho et diffusion
Chaque message est renvoyé à tous les clients ouverts. Le listener error empêche un socket défaillant de faire planter le processus.
Étape 2 : un client sans dépendance
client.js : le WebSocket global de Node
Fonctionne sans modification sur Node 22 ou plus récent et dans une console de navigateur, car les deux exposent la même classe WebSocket.
Étape 3 : éliminer les connexions mortes avec ping et pong
Notre version du modèle du README de ws : marquer comme vivant au pong, terminer tout ce qui a manqué le dernier tour.
Étape 4 : un client qui se reconnecte
reconnect.js : backoff exponentiel avec jitter
Le délai double après chaque tentative échouée jusqu'à un plafond, reçoit une dispersion aléatoire, et revient à zéro dès qu'une connexion s'ouvre.
Derrière nginx
nginx ne transmet l'upgrade que si vous transférez vous-même les en-têtes Upgrade et Connection. Avant nginx 1.29.7, il faut aussi proxy_http_version 1.1.
Location nginx pour le socket
proxy_read_timeout relève la limite d'inactivité de 60 secondes ; le heartbeat ci-dessus maintient de toute façon le trafic.
Calculateur de délai de reconnexion
Définissez un premier délai, un multiplicateur et un plafond pour voir combien de temps un client attend avant chaque nouvelle tentative, ainsi que l'attente la plus courte et la plus longue une fois le jitter ajouté.
Premier délai (ms)
Multiplicateur
Plafond (ms)
Tentatives à afficher
Ajouter du jitter (attendre entre la moitié et la totalité du délai)