🔌 Qu'est-ce que WebSocket ?
📖 Définition
WebSocket est un protocole qui permet la communication bidirectionnelle en temps réel entre le client et le serveur. Contrairement à HTTP, une fois la connexion établie, elle est maintenue de manière persistante et le serveur peut envoyer des données au client en premier (push). Il est utilisé dans le chat, les notifications en temps réel, les jeux, les outils de collaboration, etc.
🎯 Comprendre par analogie
Téléphone vs Courrier
HTTP (Courrier)
├─ Envoyer une lettre (requête)
├─ Recevoir une réponse (réponse)
├─ Écrire une nouvelle lettre (requête)
└─ Besoin d'une nouvelle lettre à chaque fois (surcharge)
WebSocket (Téléphone)
├─ Une fois connecté au téléphone
├─ Conversation continue possible
├─ Les deux peuvent parler en premier
└─ Connexion maintenue jusqu'à ce qu'on raccroche
Livraison vs Tuyau direct
HTTP
┌─────────┐ Requête ┌─────────┐
│ Client │ ──────────→ │ Serveur │
└─────────┘ └─────────┘
┌─────────┐ Réponse ┌─────────┐
│ Client │ ←────────── │ Serveur │
└─────────┘ └─────────┘
Nouvelle connexion à chaque fois !
WebSocket
┌─────────┐ ┌─────────┐
│ Client │ ←─────────→ │ Serveur │
└─────────┘ Connexion └─ ────────┘
persistante
↕ Bidirectionnel
⚙️ Principe de fonctionnement
1. Processus de connexion WebSocket
1. Requête de handshake HTTP
GET /chat HTTP/1.1
Upgrade: websocket
Connection: Upgrade
2. Approbation du serveur
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
3. Connexion WebSocket établie
↕ Début de la communication bidirectionnelle
4. Transmission de données
Message ←→ Message ←→ Message
5. Fermeture de la connexion
Appel de close()
2. HTTP vs WebSocket
HTTP (Unidirectionnel, Requête-Réponse)
Client → Serveur: Requête
Client ← Serveur: Réponse
[Connexion fermée]
WebSocket (Bidirectionnel, Persistant)
Client ↔ Serveur
- Le serveur peut envoyer des messages en premier
- Maintien de la connexion
- Communication en temps réel
💡 Exemples pratiques
WebSocket de base (Client)
// Dans le navigateur web
const ws = new WebSocket('ws://localhost:8080');
// Connexion réussie
ws.onopen = () => {
console.log('Connexion établie !');
ws.send('Bonjour !'); // Envoi de message
};
// Réception de message
ws.onmessage = (event) => {
console.log('Message reçu:', event.data);
};
// Gestion des erreurs
ws.onerror = (error) => {
console.error('Erreur:', error);
};
// Fermeture de connexion
ws.onclose = () => {
console.log('Connexion fermée');
};
// Envoi de message
ws.send('Hello Server!');
ws.send(JSON.stringify({ type: 'chat', message: 'Salut' }));
// Fermer la connexion
ws.close();
Serveur WebSocket de base (Node.js)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
console.log('Serveur WebSocket en cours d\'ex écution: ws://localhost:8080');
// Lors de la connexion du client
wss.on('connection', (ws) => {
console.log('Client connecté !');
// Message de bienvenue
ws.send('Bienvenue !');
// Réception de message
ws.on('message', (message) => {
console.log('Message reçu:', message.toString());
// Diffusion à tous les clients
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(message.toString());
}
});
});
// Fermeture de connexion
ws.on('close', () => {
console.log('Connexion client fermée');
});
// Gestion des erreurs
ws.on('error', (error) => {
console.error('Erreur:', error);
});
});
Socket.io (Application de chat pratique)
// ============ Serveur (server.js) ============
const express = require('express');
const http = require('http');
const socketIO = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = socketIO(server);
app.use(express.static('public'));
const users = new Map(); // Utilisateurs connectés
io.on('connection', (socket) => {
console.log('Utilisateur connecté:', socket.id);
// Entrée de l'utilisateur
socket.on('join', (username) => {
users.set(socket.id, username);
// Notification d'entrée (à tous)
io.emit('user-joined', {
username,
userCount: users.size
});
console.log(`${username} est entré (total ${users.size} personnes)`);
});
// Message de chat
socket.on('chat-message', (message) => {
const username = users.get(socket.id);
// Diffusion à tous
io.emit('chat-message', {
username,
message,
timestamp: new Date()
});
});
// En cours de frappe
socket.on('typing', () => {
const username = users.get(socket.id);
socket.broadcast.emit('user-typing', username);
});
// Fermeture de connexion
socket.on('disconnect', () => {
const username = users.get(socket.id);
users.delete(socket.id);
// Notification de sortie
io.emit('user-left', {
username,
userCount: users.size
});
console.log(`${username} est sorti (restant: ${users.size} personnes)`);
});
});
server.listen(3000, () => {
console.log('Serveur en cours d\'exécution: http://localhost:3000');
});
<!-- ============ Client (public/index.html) ============ -->
<!DOCTYPE html>
<html>
<head>
<title>Chat en temps réel</title>
<style>
#messages {
height: 300px;
overflow-y: auto;
border: 1px solid #ccc;
padding: 10px;
margin-bottom: 10px;
}
.message {
margin-bottom: 10px;
}
.system {
color: #999;
font-style: italic;
}
</style>
</head>
<body>
<h1>Chat en temps réel</h1>
<div id="messages"></div>
<div id="typing"></div>
<input id="message-input" type="text" placeholder="Saisir un message...">
<button id="send-btn">Envoyer</button>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
const messagesDiv = document.getElementById('messages');
const messageInput = document.getElementById('message-input');
const sendBtn = document.getElementById('send-btn');
const typingDiv = document.getElementById('typing');
// Saisie du nom d'utilisateur
const username = prompt('Entrez votre nom d\'utilisateur:') || 'Anonyme';
socket.emit('join', username);
// Notification d'entrée d'utilisateur
socket.on('user-joined', (data) => {
addMessage(`${data.username} est entré. (${data.userCount} connectés)`, 'system');
});
// Notification de sortie d'utilisateur
socket.on('user-left', (data) => {
addMessage(`${data.username} est sorti. (${data.userCount} connectés)`, 'system');
});
// Réception de message de chat
socket.on('chat-message', (data) => {
const time = new Date(data.timestamp).toLocaleTimeString();
addMessage(`[${time}] ${data.username}: ${data.message}`);
});
// Affichage de la frappe en cours
socket.on('user-typing', (username) => {
typingDiv.textContent = `${username} est en train de taper...`;
setTimeout(() => {
typingDiv.textContent = '';
}, 1000);
});
// Envoi de message
function sendMessage() {
const message = messageInput.value.trim();
if (message) {
socket.emit('chat-message', message);
messageInput.value = '';
}
}
sendBtn.addEventListener('click', sendMessage);
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendMessage();
}
});
// Événement de frappe
let typingTimeout;
messageInput.addEventListener('input', () => {
clearTimeout(typingTimeout);
socket.emit('typing');
typingTimeout = setTimeout(() => {
// Arrêt de la frappe
}, 500);
});
// Ajout de message
function addMessage(text, className = '') {
const messageEl = document.createElement('div');
messageEl.className = `message ${className}`;
messageEl.textContent = text;
messagesDiv.appendChild(messageEl);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
</script>
</body>
</html>
Fonction Room (Chat de groupe)
// Serveur
io.on('connection', (socket) => {
// Entrée dans la salle
socket.on('join-room', (roomId) => {
socket.join(roomId);
console.log(`${socket.id} est entré dans la salle ${roomId}`);
// Envoi uniquement aux personnes dans la même salle
socket.to(roomId).emit('user-joined-room', {
userId: socket.id,
roomId
});
});
// Envoi de message dans une salle spécifique
socket.on('room-message', ({ roomId, message }) => {
// Envoi uniquement à cette salle
io.to(roomId).emit('room-message', {
userId: socket.id,
message
});
});
// Sortie de la salle
socket.on('leave-room', (roomId) => {
socket.leave(roomId);
socket.to(roomId).emit('user-left-room', socket.id);
});
});
Système de notifications en temps réel
// Serveur
const notifications = io.of('/notifications');
notifications.on('connection', (socket) => {
console.log('Connexion de notification:', socket.id);
// Authentification de l'utilisateur
const userId = socket.handshake.query.userId;
// Entrée dans la salle personnelle
socket.join(`user-${userId}`);
// Envoi de notification à un utilisateur spécifique
socket.on('send-notification', ({ targetUserId, notification }) => {
notifications.to(`user-${targetUserId}`).emit('notification', notification);
});
});
// Envoi de notification depuis n'importe où
function sendNotification(userId, notification) {
notifications.to(`user-${userId}`).emit('notification', {
type: notification.type,
message: notification.message,
timestamp: new Date()
});
}
// Exemple: Notification de nouvelle commande
app.post('/api/orders', async (req, res) => {
const order = await createOrder(req.body);
// Notification en temps réel au vendeur
sendNotification(order.sellerId, {
type: 'new-order',
message: `Une nouvelle commande est arrivée !`,
orderId: order.id
});
res.json(order);
});
Collaboration en temps réel (Édition collaborative)
// Serveur
io.on('connection', (socket) => {
socket.on('join-document', (docId) => {
socket.join(docId);
// Envoi du contenu actuel du document
const document = getDocument(docId);
socket.emit('document-loaded', document);
});
// Modification du document
socket.on('document-change', ({ docId, changes }) => {
// Mise à jour du document
updateDocument(docId, changes);
// Envoi des modifications aux autres utilisateurs
socket.to(docId).emit('document-updated', {
userId: socket.id,
changes
});
});
// Partage de la position du curseur
socket.on('cursor-move', ({ docId, position }) => {
socket.to(docId).emit('cursor-update', {
userId: socket.id,
position
});
});
});
🤔 Questions fréquentes
Q1. WebSocket vs HTTP Polling ?
R:
// HTTP Polling (Inefficace)
// Le client envoie régulièrement des requêtes au serveur
setInterval(() => {
fetch('/api/messages')
.then(res => res.json())
.then(messages => {
// Vérifier les nouveaux messages
});
}, 1000); // Requête toutes les secondes
// Problèmes:
// - Requêtes inutiles (requête même sans nouveau message)
// - Délai (jusqu'à 1 seconde)
// - Charge serveur
// WebSocket (Efficace)
// Communication bidirectionnelle en temps réel
const socket = io();
socket.on('new-message', (message) => {
// Réception immédiate des nouveaux messages !
});
// Avantages:
// - Réception immédiate (pas de délai)
// - Le serveur push
// - Efficace
Q2. WebSocket est-il toujours meilleur ?
R:
// ✅ WebSocket est bon dans ces cas
1. Chat en temps réel
2. Notifications en temps réel
3. Outils de collaboration (édition collaborative)
4. Jeux en temps réel
5. Informations boursières (données en temps réel)
6. Contrôle de dispositifs IoT
// ❌ WebSocket n'est pas nécessaire dans ces cas
1. Sites web généraux (blog, actualités)
2. REST API (CRUD)
3. Contenu statique
4. Pas besoin de mises à jour en temps réel
// HTTP est meilleur dans ces cas:
// - Requête-réponse simple
// - Besoin de mise en cache
// - Conception RESTful
Q3. Socket.io vs WebSocket natif ?
R:
// WebSocket natif
const ws = new WebSocket('ws://localhost:8080');
ws.send('Hello');
// Avantages: Léger et rapide
// Inconvénients: Pas de fonctionnalités supplémentaires, pas de fallback
// Socket.io
const socket = io();
socket.emit('message', 'Hello');
// Avantages:
// 1. Reconnexion automatique
// 2. Support de Room/Namespace
// 3. Fallback (Polling si WebSocket n'est pas disponible)
// 4. Basé sur les événements
// 5. Support binaire
// Inconvénients:
// 1. Lourd (taille de la bibliothèque)
// 2. Pas compatible avec WebSocket natif
// Critères de sélection:
// Projet simple → WebSocket natif
// Projet complexe → Socket.io
Q4. Sécurité WebSocket ?
R:
// 1. Utiliser wss:// (WebSocket Secure)
// Comme HTTP → HTTPS
// ws:// → wss://
const socket = new WebSocket('wss://example.com'); // ✅ Chiffré
const socket = new WebSocket('ws://example.com'); // ❌ Texte brut
// 2. Authentification
// Socket.io
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (isValidToken(token)) {
next();
} else {
next(new Error('Échec d\'authentification'));
}
});
// Client
const socket = io({
auth: {
token: 'user-token'
}
});
// 3. Vérification des autorisations
socket.on('delete-message', (messageId) => {
const userId = socket.userId;
if (canDeleteMessage(userId, messageId)) {
deleteMessage(messageId);
} else {
socket.emit('error', 'Vous n\'avez pas l\'autorisation');
}
});
// 4. Limitation de débit
const rateLimit = require('socket.io-rate-limit');
io.use(rateLimit({
max: 10, // Maximum 10
interval: 1000 // Par seconde
}));
// 5. Validation des entrées
socket.on('message', (message) => {
// Prévention XSS
const sanitized = sanitizeHtml(message);
if (sanitized.length > 1000) {
return socket.emit('error', 'Message trop long');
}
broadcast(sanitized);
});
Q5. Gestion de la connexion WebSocket ?
R:
// 1. Reconnexion automatique (Socket.io)
const socket = io({
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000
});
socket.on('disconnect', () => {
console.log('Connexion coupée');
});
socket.on('reconnect', () => {
console.log('Reconnexion réussie');
});
// 2. Heartbeat (Vérification de la connexion)
// Serveur
setInterval(() => {
wss.clients.forEach((ws) => {
if (ws.isAlive === false) {
return ws.terminate(); // Fermeture si pas de réponse
}
ws.isAlive = false;
ws.ping(); // Envoi de ping
});
}, 30000);
ws.on('pong', () => {
ws.isAlive = true;
});
// 3. Gestion de la mémoire
const MAX_CONNECTIONS = 1000;
wss.on('connection', (ws) => {
if (wss.clients.size > MAX_CONNECTIONS) {
ws.close(1008, 'Le serveur est plein');
return;
}
// Traitement de la connexion...
});
// 4. Arrêt gracieux
process.on('SIGTERM', () => {
console.log('Arrêt du serveur en cours...');
// Notification de fermeture à toutes les connexions
wss.clients.forEach((ws) => {
ws.send(JSON.stringify({ type: 'server-shutdown' }));
ws.close();
});
wss.close(() => {
console.log('Serveur WebSocket arrêté');
process.exit(0);
});
});
🎓 Prochaines étapes
Si vous avez compris WebSocket, essayez d'apprendre ensuite:
- Qu'est-ce que Node.js ? - Construction de serveur WebSocket
- Qu'est-ce que React ? - Implémentation d'interface en temps réel
- Qu'est-ce que Docker ? (Documentation à créer) - Déploiement de serveur WebSocket
À pratiquer
# Créer une application de chat Socket.io
# 1. Initialisation du projet
mkdir chat-app
cd chat-app
npm init -y
# 2. Installation des packages
npm install express socket.io
# 3. Création du serveur (voir l'exemple ci-dessus)
# server.js
# 4. Exécution
node server.js
# 5. Accès à http://localhost:3000
🎬 Résumé
WebSocket est la technologie centrale du web en temps réel:
- Communication bidirectionnelle: Serveur ↔ Client
- Connexion persistante: Une fois connecté, utilisation continue
- Temps réel: Transmission de données instantanée
- Efficace: Beaucoup plus efficace que le Polling
Si vous avez besoin de fonctionnalités en temps réel, utilisez WebSocket ! 🔌✨