Contactez-nous

Gestion des connexions et des déconnexions

Apprenez a gerer le cycle de vie des connexions WebSocket en Node.js : detection des nouvelles connexions, gestion des deconnexions et erreurs avec ws et Socket.IO.

Le cycle de vie d'une connexion temps réel

Au-delà de l'échange de messages, un aspect crucial de toute application temps réel est la gestion du cycle de vie des connexions. Votre serveur doit savoir quand un nouveau client se connecte, quand un client existant se déconnecte (volontairement ou à cause d'un problème réseau), et comment réagir à ces événements. Une gestion correcte des connexions et déconnexions est essentielle pour :

  • Maintenir un état cohérent de l'application (qui est connecté ? qui est dans quelle salle de chat ?).
  • Allouer et libérer des ressources serveur associées à chaque client.
  • Notifier les autres utilisateurs de l'arrivée ou du départ d'un participant.
  • Nettoyer les données ou les processus liés à un client qui n'est plus actif.

Les bibliothèques `ws` et `Socket.IO` fournissent des mécanismes événementiels pour détecter et gérer ces différentes étapes du cycle de vie.

Gestion avec `ws` : Evénements directs

Avec la bibliothèque `ws`, la gestion se fait en écoutant des événements spécifiques sur l'instance du serveur (`wss`) et sur chaque instance client (`wsClient`).

Détecter une nouvelle connexion :

L'événement `'connection'` est émis sur l'instance `WebSocket.Server` (`wss`) chaque fois qu'un nouveau client établit avec succès la poignée de main WebSocket. Le callback reçoit l'objet `wsClient` représentant cette connexion spécifique.

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

// Utilisation d'un Set pour suivre les clients actifs
const clients = new Set();

wss.on('connection', (wsClient, req) => {
  // req contient des informations sur la requête HTTP initiale (utile pour l'authentification par ex.)
  const clientId = req.headers['sec-websocket-key']; // Exemple d'identifiant potentiel
  console.log(`Client connecté: ${clientId}`);
  
  // Ajouter le client à notre liste de suivi
  clients.add(wsClient);
  console.log(`Nombre de clients connectés: ${clients.size}`);

  // Associer l'ID au client si nécessaire
  // wsClient.clientId = clientId;

  // ... enregistrer les gestionnaires .on('message'), .on('close'), .on('error') pour ce wsClient ...
  wsClient.send('Connexion au serveur ws réussie.');
  
  wsClient.on('close', (code, reason) => {
    console.log(`Client déconnecté: ${clientId} (Code: ${code}, Raison: ${reason.toString()})`);
    // Retirer le client de la liste de suivi lors de la déconnexion
    clients.delete(wsClient);
    console.log(`Nombre de clients connectés: ${clients.size}`);
    // Notifier les autres clients si nécessaire...
    // broadcast(`${clientId} est parti`, wsClient);
  });

  wsClient.on('error', (error) => {
    console.error(`Erreur pour le client ${clientId}:`, error);
    // Une erreur entraîne souvent une fermeture, 'close' sera probablement aussi appelé.
    // On retire aussi en cas d'erreur au cas où 'close' ne serait pas appelé proprement.
    clients.delete(wsClient);
    console.log(`Nombre de clients connectés après erreur: ${clients.size}`);
  });
});

Détecter une déconnexion :

L'événement `'close'` est émis sur l'objet `wsClient` lorsque la connexion avec ce client spécifique est fermée (que ce soit initié par le client, le serveur, ou à cause d'une erreur réseau). Le callback reçoit généralement un code de fermeture et une raison (sous forme de Buffer).

Il est essentiel d'écouter cet événement pour effectuer les nettoyages nécessaires, notamment retirer le client de toute structure de données où vous le suiviez (comme le `Set` dans l'exemple).

Gérer les erreurs de connexion :

L'événement `'error'` est émis sur l'objet `wsClient` si une erreur se produit spécifiquement sur cette connexion. Il est important de gérer cet événement pour logger l'erreur et potentiellement nettoyer les ressources associées au client, car une erreur peut précéder ou causer une déconnexion.

Il existe aussi un événement `'error'` sur l'instance `wss` elle-même pour les erreurs liées au serveur (ex: impossible d'écouter sur le port).

Suivi des clients : Avec `ws`, vous êtes responsable de maintenir la liste des clients connectés si vous avez besoin de leur envoyer des messages de manière ciblée ou de diffuser des informations. Utiliser un `Set` ou un `Map` est une approche courante.

Gestion avec `Socket.IO` : Abstraction et facilités

`Socket.IO` abstrait une partie de cette gestion et fournit des identifiants uniques et des fonctionnalités intégrées.

Détecter une nouvelle connexion :

L'événement `'connection'` est émis sur l'instance principale du serveur `io`. Le callback reçoit l'objet `socket` représentant la connexion unique de ce client. Cet objet `socket` possède un identifiant unique généré automatiquement : `socket.id`.

const { Server } = require("socket.io");
// ... création du serveur http ...
const io = new Server(server);

io.on('connection', (socket) => {
  console.log(`Un utilisateur s'est connecté avec l'ID: ${socket.id}`);

  // Pas besoin de gérer manuellement un Set, Socket.IO le fait.
  // Pour obtenir le nombre de clients connectés :
  // io.sockets.sockets est une Map
  console.log(`Nombre total d'utilisateurs connectés: ${io.sockets.sockets.size}`);

  // ... enregistrer les gestionnaires .on('chat message'), etc. pour ce socket ...
  socket.emit('votre id', socket.id);

  socket.on('disconnect', (reason) => {
    console.log(`Utilisateur ${socket.id} déconnecté. Raison: ${reason}`);
    // Notifier les autres...
    socket.broadcast.emit('info systeme', `L'utilisateur ${socket.id} est parti.`);
    // Le suivi interne de Socket.IO est mis à jour automatiquement.
    // Vous pourriez avoir besoin de nettoyer un état applicatif ici (ex: retirer de la partie)
  });

  socket.on('error', (error) => {
      console.error(`Erreur sur socket ${socket.id}:`, error);
      // Gérer l'erreur applicative si nécessaire
  });
});

Détecter une déconnexion :

L'événement `'disconnect'` est émis sur l'objet `socket` spécifique lorsque ce client se déconnecte. Le callback reçoit une `reason` expliquant la cause (ex: `'client namespace disconnect'`, `'server namespace disconnect'`, `'transport error'`, `'ping timeout'`). `Socket.IO` intègre des mécanismes de heartbeat (ping/pong) pour détecter plus rapidement les déconnexions dues à des problèmes réseau.

Lorsque cet événement est reçu, `Socket.IO` retire automatiquement le `socket` de sa gestion interne (y compris des rooms auxquelles il appartenait). Vous devez principalement vous concentrer sur le nettoyage de l'état de votre application spécifique à ce client.

Gérer les erreurs :

L'événement `'error'` sur l'objet `socket` peut être utilisé pour attraper des erreurs spécifiques à cette connexion. Les erreurs de transport ou de protocole plus graves peuvent aussi déclencher l'événement `disconnect` avec une raison appropriée.

Suivi des clients : `Socket.IO` gère le suivi des clients connectés. Vous pouvez accéder à la liste des sockets connectés via `io.sockets.sockets` (qui est une `Map`). Vous utilisez principalement `socket.id` pour identifier les clients et les méthodes comme `io.to(socketId).emit(...)` pour leur envoyer des messages ciblés.

Importance du nettoyage et de la gestion d'état

Que vous utilisiez `ws` ou `Socket.IO`, la gestion des événements de connexion et surtout de déconnexion est primordiale.

  • Libération des ressources : Si vous allouez des ressources spécifiques à un client (minuteurs, écouteurs d'événements personnalisés, objets en mémoire), assurez-vous de les libérer dans le gestionnaire de déconnexion (`'close'` ou `'disconnect'`) pour éviter les fuites de mémoire.
  • Mise à jour de l'état applicatif : Informez les autres parties de votre application qu'un utilisateur est parti. Par exemple, retirez-le d'une liste d'utilisateurs actifs, mettez fin à sa session de jeu, libérez un pseudo, etc.
  • Notifications : Informez les autres clients connectés (si pertinent) du départ d'un utilisateur, par exemple en diffusant un message système.

Une gestion rigoureuse du cycle de vie des connexions est la clé pour maintenir la stabilité, la performance et la cohérence de vos applications Node.js temps réel.