Pourquoi distribuer ? Premiers pas en TCP
BGD701 — Télécom Paris
Un SSD lit à environ 500 Mo/s. Vous comptez les mots d’un fichier de 1 To.
33 minutes juste pour lire. Avec un traitement, le double ou le triple.
Mille téraoctets. L’ordre de grandeur de Common Crawl, l’archive du Web qui sert à entraîner les IA.
Vingt-trois jours de lecture continue. Sans panne.
À retenir
Le Big Data ne commence pas quand les données sont grosses. Il commence quand une machine ne suffit plus dans un temps acceptable.
scale up — vertical
Une machine plus puissante.
+ simple, code inchangé
− le prix explose
− plafond physique
scale out — horizontal
Cent machines ordinaires.
+ moins cher
+ pas de plafond
− tout devient plus dur
Avec cent machines, la panne n’est plus une hypothèse. C’est une certitude statistique.
Distribuer, c’est échanger un problème de puissance
contre un problème de coordination.
Hadoop, Spark, Kafka sont des réponses industrielles à ce problème. Nous construirons les nôtres, en plus simple.
Identifie la machine.
137.194.2.15
L’adresse de l’immeuble.
Identifie le programme.
De 1 à 65535.
Le numéro d’appartement.
Une machine fait tourner cent programmes réseau : un port chacun.
| Port | Usage |
|---|---|
| 22 | SSH |
| 80 | HTTP |
| 443 | HTTPS |
| 5432 | PostgreSQL |
| 9092 | Kafka |
Sous 1024 : droits administrateur. Ici, on prend des ports hauts.
Vous n’êtes pas seul sur les machines de l’école
Deux personnes sur le port 50000, et l’une des deux obtient :
Address already in use
Prenez un port personnel : les trois derniers chiffres de votre numéro d’étudiant.
127.0.0.1Alias localhost. Votre machine.
Joignable depuis elle seule.
Parfait pour tester.
0.0.0.0Toutes les interfaces.
Joignable depuis le réseau.
Quand d’autres doivent vous contacter.
La relation est asymétrique.
UDP n’offre aucune de ces garanties, mais il est plus léger.
On l’utilise pour la voix, la vidéo, les jeux : là où une donnée en retard est pire qu’une donnée perdue.
Dans ce cours, ce sera toujours TCP.
Retenez la formulation exacte
TCP garantit que les octets arrivent, dans l’ordre.
Il ne garantit pas que vos messages arrivent séparés.
Cette distinction semble anodine. Elle sera le sujet central de la séance 3, et une source classique de bugs.
| Téléphone | Python | Signification |
|---|---|---|
| Acheter un téléphone | socket() |
Créer le point d’accès |
| Obtenir un numéro | bind() |
S’attribuer adresse et port |
| Brancher le téléphone | listen() |
Se déclarer prêt |
| Décrocher | accept() |
Accepter une connexion |
| Composer un numéro | connect() |
Se connecter au serveur |
| Parler | sendall() |
Envoyer des octets |
| Écouter | recv() |
Recevoir des octets |
| Raccrocher | close() |
Fermer |
socket()
bind()
listen()
accept()
connect()
sendall()
recv()
close()
Un socket ne transporte ni texte, ni nombres, ni objets. Il transporte des octets — le type bytes.
Octets littéraux : b"Bonjour". Par défaut, l’encodage est UTF-8.
Oublier .encode()
TypeError: a bytes-like object is required, not 'str'
Et sans .decode() à l’affichage, vous verrez b'Bonjour' au lieu de Bonjour.
import socket
HOTE = "127.0.0.1"
PORT = 50007
serveur = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Autoriser la reutilisation immediate du port
serveur.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# S'attribuer l'adresse et le port
serveur.bind((HOTE, PORT))
# Se declarer pret (5 = taille de la file d'attente)
serveur.listen(5)
print(f"Serveur en ecoute sur {HOTE}:{PORT}")
while True:
# Bloque ici jusqu'a ce qu'un client se connecte
connexion, adresse = serveur.accept()
print(f"Connexion de {adresse}")
# Lire au plus 1024 octets
donnees = connexion.recv(1024)
print(f"Recu : {donnees.decode()}")
# Renvoyer les memes octets
connexion.sendall(donnees)
# Fermer cette connexion, pas le serveur
connexion.close()socket(AF_INET, SOCK_STREAM) — IPv4 et TCP. Toujours les mêmes ici.
setsockopt(… SO_REUSEADDR …) — sans elle, relancer le serveur donne Address already in use. Mettez-la toujours.
bind((HOTE, PORT)) — double parenthèse : bind attend un argument, un tuple.
accept() bloqueLe programme s’arrête là tant que personne ne se connecte.
Il renvoie deux choses : un nouveau socket dédié au client, et l’adresse du client.
Deux sockets, deux rôles
serveur : socket d’écoute. Sert à accepter, rien d’autre.connexion : socket de dialogue avec ce client. C’est là qu’on fait recv et sendall.recv(1024) lit au plus 1024 octets. « Au plus » : on y reviendra en séance 3.
sendall(donnees) envoie tous les octets.
send() existe aussi, mais il peut n’envoyer qu’une partie des données. Utilisez toujours sendall().
Pas de bind, pas de listen, pas de accept. Il se connecte, parle, écoute, raccroche.
Le terminal semble figé. C’est normal : il est bloqué dans accept().
Et dans le terminal 1 :
Le serveur d’abord, le client ensuite
Le client lancé seul donne :
ConnectionRefusedError: [Errno 61] Connection refused
Traduction : personne n’écoute sur ce port.
Pour arrêter le serveur : Ctrl+C.
Le load average : charge moyenne sur 1, 5 et 15 minutes.
Une charge de 1,0 ≈ un cœur occupé en permanence. Sur 8 cœurs : 8 = pleine charge, 0,4 = presque libre.
serveur_charge.pyimport socket
import os
import sys
# Le port peut etre passe en argument, pour lancer plusieurs serveurs
# sur la meme machine.
PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 50007
serveur = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serveur.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serveur.bind(("0.0.0.0", PORT)) # accessible depuis le reseau
serveur.listen(5)
print(f"Serveur de charge en ecoute sur le port {PORT}")
while True:
connexion, adresse = serveur.accept()
charge1, charge5, charge15 = os.getloadavg()
message = f"{charge1:.2f} {charge5:.2f} {charge15:.2f}\n"
connexion.sendall(message.encode())
connexion.close()Cette fois, 0.0.0.0 : on veut être joignable depuis le réseau.
os.getloadavg() n’existe pas sous Windows
C’est une fonction Unix : elle marche à l’école et sur macOS, mais lève AttributeError sous Windows.
Le cours donne une solution de repli, en bibliothèque standard uniquement.
Deux règles : aucune bibliothèque externe (le réseau est coupé en examen, rien ne s’installe), et vérifiez votre code sur une machine de l’école au moins une fois.
client_charge.pyimport socket
import sys
HOTE = sys.argv[1] if len(sys.argv) > 1 else "127.0.0.1"
PORT = 50007
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((HOTE, PORT))
reponse = client.recv(1024).decode().strip()
client.close()
c1, c5, c15 = [float(x) for x in reponse.split()]
print(f"{HOTE} -> 1min={c1} 5min={c5} 15min={c15}")Pourquoi c’est important
Un système distribué commence toujours là : savoir qui est vivant, et dans quel état.
Sans le savoir
Un accord sur la forme des messages :
trois nombres décimaux, séparés par des espaces, terminés par un retour à la ligne
Le client ne décode que parce qu’il connaît cette convention.
Passez aux virgules, et le client plante. Nous formaliserons cela en séance 3.
"12 + 30" → "42".Le même réflexe reviendra en séance 3, avec pickle.
bind + listen + accept. Client : connect.encode() / .decode()recv(n) lit au plus octets ; toujours sendall()