Tutoriels

WebSockets et Socket.IO : Guide du Temps Réel

Mark Toledo

Mark Toledo

1 janvier 2025

WebSockets et Socket.IO : Guide du Temps Réel

Les applications web modernes nécessitent souvent une communication en temps réel : chat, notifications, mises à jour live, jeux multijoueurs. WebSocket et Socket.IO sont les technologies de choix pour ces cas d'usage.

WebSocket vs Socket.IO

WebSocket natif

WebSocket est un protocole de communication bidirectionnelle sur une connexion TCP persistante. Supporté nativement par les navigateurs.

Socket.IO

Socket.IO est une bibliothèque qui ajoute des fonctionnalités sur WebSocket :

  • Fallback automatique si WebSocket non disponible
  • Reconnexion automatique
  • Rooms et namespaces pour organiser les connexions
  • Acknowledgements pour confirmer la réception
  • Broadcasting simplifié

Installation

# Backend
npm install socket.io express

# Frontend (optionnel si CDN)
npm install socket.io-client

Serveur basique

// server.js
const express = require('express');
const { createServer } = require('http');
const { Server } = require('socket.io');

const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, {
  cors: {
    origin: "http://localhost:3000",
    methods: ["GET", "POST"]
  }
});

// Servir les fichiers statiques
app.use(express.static('public'));

// Gestion des connexions
io.on('connection', (socket) => {
  console.log('Utilisateur connecté:', socket.id);

  // Écouter un événement
  socket.on('message', (data) => {
    console.log('Message reçu:', data);

    // Envoyer à tous les clients
    io.emit('message', {
      id: socket.id,
      text: data.text,
      timestamp: new Date()
    });
  });

  // Déconnexion
  socket.on('disconnect', () => {
    console.log('Utilisateur déconnecté:', socket.id);
  });
});

httpServer.listen(3000, () => {
  console.log('Serveur démarré sur http://localhost:3000');
});

Client basique

<!-- public/index.html -->
<!DOCTYPE html>
<html>
<head>
  <title>Chat Socket.IO</title>
</head>
<body>
  <div id="messages"></div>
  <input type="text" id="input" placeholder="Votre message..." />
  <button onclick="envoyerMessage()">Envoyer</button>

  <script src="/socket.io/socket.io.js"></script>
  <script>
    const socket = io();
    const messagesDiv = document.getElementById('messages');
    const input = document.getElementById('input');

    // Recevoir les messages
    socket.on('message', (data) => {
      const p = document.createElement('p');
      p.textContent = `${data.id}: ${data.text}`;
      messagesDiv.appendChild(p);
    });

    // Envoyer un message
    function envoyerMessage() {
      const text = input.value.trim();
      if (text) {
        socket.emit('message', { text });
        input.value = '';
      }
    }

    // Envoyer avec Entrée
    input.addEventListener('keypress', (e) => {
      if (e.key === 'Enter') envoyerMessage();
    });
  </script>
</body>
</html>

Rooms (salons)

Les rooms permettent de grouper les connexions :

// Serveur
io.on('connection', (socket) => {

  // Rejoindre une room
  socket.on('rejoindre', (room) => {
    socket.join(room);
    console.log(`${socket.id} a rejoint ${room}`);

    // Notifier la room
    socket.to(room).emit('notification', {
      message: `Un utilisateur a rejoint ${room}`
    });
  });

  // Quitter une room
  socket.on('quitter', (room) => {
    socket.leave(room);
    socket.to(room).emit('notification', {
      message: 'Un utilisateur a quitté le salon'
    });
  });

  // Message dans une room spécifique
  socket.on('messageRoom', ({ room, text }) => {
    io.to(room).emit('message', {
      user: socket.id,
      text,
      room
    });
  });
});
// Client
socket.emit('rejoindre', 'salon-general');
socket.emit('messageRoom', {
  room: 'salon-general',
  text: 'Bonjour à tous !'
});

Namespaces

Les namespaces séparent la logique de l'application :

// Serveur
const chatNsp = io.of('/chat');
const notifNsp = io.of('/notifications');

chatNsp.on('connection', (socket) => {
  console.log('Connexion au chat:', socket.id);

  socket.on('message', (data) => {
    chatNsp.emit('message', data);
  });
});

notifNsp.on('connection', (socket) => {
  console.log('Connexion aux notifications:', socket.id);

  // Envoyer des notifications périodiques
  const interval = setInterval(() => {
    socket.emit('notification', {
      type: 'info',
      message: 'Nouvelle notification'
    });
  }, 30000);

  socket.on('disconnect', () => clearInterval(interval));
});
// Client
const chatSocket = io('/chat');
const notifSocket = io('/notifications');

chatSocket.on('message', (data) => {
  console.log('Chat:', data);
});

notifSocket.on('notification', (data) => {
  console.log('Notification:', data);
});

Acknowledgements

Confirmer la réception des messages :

// Client - envoyer avec callback
socket.emit('message', { text: 'Hello' }, (response) => {
  if (response.status === 'ok') {
    console.log('Message délivré !');
  }
});

// Serveur - répondre au callback
socket.on('message', (data, callback) => {
  // Traiter le message...
  saveMessage(data);

  // Confirmer la réception
  callback({ status: 'ok', id: data.id });
});

Exemple complet : Chat avec utilisateurs

// server.js
const express = require('express');
const { createServer } = require('http');
const { Server } = require('socket.io');

const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer);

app.use(express.static('public'));

// Stockage des utilisateurs connectés
const utilisateurs = new Map();

io.on('connection', (socket) => {

  // Connexion d'un utilisateur
  socket.on('login', (pseudo) => {
    utilisateurs.set(socket.id, {
      id: socket.id,
      pseudo,
      connecteA: new Date()
    });

    // Notifier tout le monde
    io.emit('utilisateurs', Array.from(utilisateurs.values()));
    io.emit('systeme', `${pseudo} a rejoint le chat`);
  });

  // Message
  socket.on('message', (text) => {
    const user = utilisateurs.get(socket.id);
    if (user) {
      io.emit('message', {
        id: Date.now(),
        pseudo: user.pseudo,
        text,
        timestamp: new Date()
      });
    }
  });

  // Indicateur de frappe
  socket.on('typing', (isTyping) => {
    const user = utilisateurs.get(socket.id);
    if (user) {
      socket.broadcast.emit('typing', {
        pseudo: user.pseudo,
        isTyping
      });
    }
  });

  // Déconnexion
  socket.on('disconnect', () => {
    const user = utilisateurs.get(socket.id);
    if (user) {
      utilisateurs.delete(socket.id);
      io.emit('utilisateurs', Array.from(utilisateurs.values()));
      io.emit('systeme', `${user.pseudo} a quitté le chat`);
    }
  });
});

httpServer.listen(3000);

Intégration avec Vue.js

<!-- composables/useSocket.js -->
<script>
import { io } from 'socket.io-client'
import { ref, onMounted, onUnmounted } from 'vue'

export function useSocket(url = '/') {
  const socket = ref(null)
  const connected = ref(false)
  const messages = ref([])

  onMounted(() => {
    socket.value = io(url)

    socket.value.on('connect', () => {
      connected.value = true
    })

    socket.value.on('disconnect', () => {
      connected.value = false
    })

    socket.value.on('message', (msg) => {
      messages.value.push(msg)
    })
  })

  onUnmounted(() => {
    socket.value?.disconnect()
  })

  function send(event, data) {
    socket.value?.emit(event, data)
  }

  return { socket, connected, messages, send }
}
</script>

Bonnes pratiques

  1. Authentification : Vérifiez l'identité lors de la connexion
  2. Rate limiting : Limitez le nombre de messages par seconde
  3. Validation : Validez toutes les données reçues
  4. Gestion des erreurs : Gérez les déconnexions gracieusement
  5. Scaling : Utilisez Redis adapter pour plusieurs serveurs
// Exemple d'authentification
io.use((socket, next) => {
  const token = socket.handshake.auth.token;
  if (verifyToken(token)) {
    socket.user = getUserFromToken(token);
    next();
  } else {
    next(new Error('Authentification requise'));
  }
});

Conclusion

Socket.IO simplifie considérablement le développement d'applications temps réel. Avec les rooms, namespaces et acknowledgements, vous pouvez créer des expériences interactives sophistiquées. Combinez-le avec Vue.js ou React pour des applications modernes et réactives.