Qu’est-ce que TCP/IP ?
TCP/IP est la suite de protocoles qui supporte l’internet. IP est responsable de l’adressage et du routage des paquets entre les hôtes ; TCP se superpose à IP pour transformer ces paquets non fiables en un flux d’octets ordonné et fiable. Ensemble, ils constituent la couche de transport sous-jacente à HTTP, aux bases de données, aux e-mails, aux WebSocket et à presque tout ce que fait votre serveur.
Il est rare d’écrire du code TCP directement, mais le comprendre permet d’expliquer beaucoup de choses : pourquoi les connexions sont coûteuses, pourquoi le keep-alive est important, pourquoi un pool de base de données a une limite de taille, et pourquoi certains trafics utilisent UDP à la place.
Le modèle en couches
Le réseau est généralement décrit en couches, chacune gérant une tâche spécifique :
| Couche | Rôle | Exemples |
|---|---|---|
| Application | Signification | HTTP, WebSocket, DNS |
| Transport | Fiabilité et ports | TCP, UDP |
| Internet | Adressage et routage | IP, ICMP |
| Liaison | Livraison locale | Ethernet, Wi-Fi |
Chaque couche utilise celle qui se trouve en dessous d’elle. HTTP ne sait pas s’il transite via Ethernet ou Wi-Fi ; il s’appuie sur TCP pour livrer des octets, lequel s’appuie sur IP pour déplacer des paquets. Cette séparation est la raison pour laquelle Internet peut transporter autant de types de trafic différents.
Adresses IP et ports
Une adresse IP identifie un hôte, et un port identifie un service sur cet hôte. Ensemble, ils forment une adresse socket, telle que 203.0.113.10:443.
- Les adresses IPv4 sont sur 32 bits (
203.0.113.10) ; les adresses IPv6 sont sur 128 bits et écrites en hexadécimal. - Les ports inférieurs à 1024 sont réservés aux services bien connus.
- Les clients reçoivent un port éphémère pour la durée d’une connexion.
- Une connexion est identifiée de manière unique par le tuple complet : adresse source, port source, adresse destination et port destination.
C’est grâce à ce tuple qu’un serveur peut accepter des milliers de connexions simultanées sur le port 443 : le port source de chaque client est différent.
Le three-way handshake
Avant tout flux de données, TCP établit une connexion en trois étapes :
- Le client envoie un SYN avec son numéro de séquence initial.
- Le serveur répond par un SYN-ACK, accusant réception de la demande du client et envoyant son propre numéro de séquence.
- Le client envoie un ACK, et la connexion est établie.
Cela coûte un aller-retour (round trip) avant le premier octet de données applicatives, ce qui représente un coût réel en termes de latence. TLS ajoute un autre aller-retour (ou deux), et DNS peut en ajouter un supplémentaire avant cela. La réutilisation des connexions existe précisément pour éviter de payer ces coûts à répétition.
Fiabilité et contrôle de flux
TCP assure la fiabilité grâce à plusieurs mécanismes :
- Les numéros de séquence ordonnent les octets, permettant ainsi au récepteur de les réassembler correctement.
- Les accusés de réception confirment la réception, et les données non acquittées sont retransmises.
- Les sommes de contrôle (checksums) détectent la corruption des données.
- Le contrôle de flux utilise une fenêtre de réception pour éviter qu’un émetteur rapide ne submerge un récepteur lent.
- Le contrôle de congestion adapte le débit d’envoi en fonction du réseau pour éviter l’effondrement de celui-ci.
Le résultat est un flux d’octets qui arrive complet et dans l’ordre, au prix d’une certaine latence et d’un surcoût (overhead).
UDP : l’alternative rapide
UDP est un protocole sans connexion. Il envoie des datagrammes sans établissement de connexion (handshake), sans garantie d’ordre et sans retransmission. Cela peut sembler être un inconvénient, mais pour certaines charges de travail, c’est un avantage :
- La latence prime sur la perfection : la vidéo en direct, la voix et les jeux préfèrent perdre une image plutôt que de la recevoir avec retard.
- L’application gère la fiabilité : QUIC, qui propulse HTTP/3, implémente sa propre couche de fiabilité au-dessus d’UDP.
- Requêtes courtes et sans état : DNS utilise traditionnellement UDP car une seule requête et sa réponse tiennent dans un seul datagramme.
Choisissez TCP lorsque l’exactitude et l’ordre des données sont primordiaux, et UDP lorsque vous avez besoin de vitesse et que vous pouvez tolérer des pertes de données.
Les Sockets dans Node.js
Node expose TCP via node:net et UDP via node:dgram. Un serveur TCP vous fournit un socket par connexion.
// echo.js
import { createServer } from "node:net";
const server = createServer((socket) => {
socket.setKeepAlive(true, 10_000);
socket.setTimeout(30_000);
socket.on("timeout", () => socket.destroy());
socket.on("data", (data) => socket.write(data));
socket.on("error", (err) => console.error(err));
});
server.listen(9000);
Les sockets sont des streams : vous lisez les événements data et vous écrivez avec socket.write(). Comme TCP est un flux d’octets sans délimitation de messages, les protocoles applicatifs doivent définir leur propre cadrage (framing), c’est pourquoi HTTP utilise des headers avec des longueurs de contenu et WebSockets utilise des frames.
Cycle de vie et optimisation des connexions
- Le Keep-alive permet de réutiliser les connexions, évitant ainsi le handshake à chaque requête.
- Les Pools limitent le nombre de connexions simultanées vers une base de données ou un service.
- Les Timeouts ferment les connexions inactives ou bloquées afin de libérer les ressources.
- Le TIME_WAIT est un état normal après la fermeture, mais un trop grand nombre de connexions éphémères peut épuiser les ports.
- Backpressure :
socket.write()renvoiefalselorsque le buffer est plein, et vous devez alors attendredrain.
La plupart des optimisations en production consistent à réutiliser les connexions et à limiter la concurrence, plutôt qu’à modifier les paramètres du kernel.
Bonnes pratiques
- Réutilisez les connexions avec keep-alive ou un pool.
- Configurez des timeouts et le keep-alive sur les connexions longue durée.
- Limitez la concurrence pour éviter d’épuiser les sockets ou les descripteurs de fichiers.
- Gérez les erreurs et la backpressure sur chaque socket.
- Utilisez TCP, sauf si vous avez une raison spécifique d’utiliser UDP.
- Gardez à l’esprit que TCP est un flux d’octets (byte stream) et définissez votre propre framing.
- Surveillez le TIME_WAIT et le nombre de connexions pour détecter un renouvellement excessif des connexions (connection churn).
Erreurs courantes
- Ouvrir une nouvelle connexion pour chaque requête.
- Supposer que TCP préserve les limites des messages.
- Ignorer la backpressure de
socket.write()et mettre en tampon des données non bornées. - Laisser des connexions persistantes sans keep-alive ni timeouts.
- Configurer des tailles de pool bien plus importantes que ce que la base de données peut supporter.
- Utiliser UDP alors que la fiabilité est réellement primordiale.
Et après ?
TCP/IP est la couche sur laquelle tout le reste repose. Approfondissez vos connaissances avec le guide HTTP, comprenez les bases du réseau dans Comment fonctionne Internet, et passez aux connexions persistantes avec WebSockets. Ensuite, inspectez les connexions réelles sur votre machine avec ss ou netstat pendant que votre serveur gère le trafic.