Cours Reseaux Et Java
Cours Reseaux Et Java
Serveur
[Link]:5000
Client A Client C
Client B
1
TABLE DES MATIÈRES 2
4 L’adressage IP 24
4.1 Le rôle de l’adresse IP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.2 Structure d’une adresse IPv4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.3 Le masque de sous-réseau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.3.1 Adresses particulières d’un réseau . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.4 Classes historiques et adresses privées . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
4.5 Le sous-réseautage (subnetting) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
4.6 Attribution des adresses : statique et DHCP . . . . . . . . . . . . . . . . . . . . . . . . . 27
4.7 IPv6 en bref . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4.8 Exercices corrigés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
7 L’architecture client-serveur 43
7.1 Le modèle client-serveur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
7.2 Architectures en tiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
7.3 Le modèle pair-à-pair (P2P), pour comparaison . . . . . . . . . . . . . . . . . . . . . . . 44
7.4 Serveur itératif ou serveur concurrent . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
7.5 Protocole applicatif : le contrat entre client et serveur . . . . . . . . . . . . . . . . . . . . 45
10 Le serveur multi-utilisateurs 65
10.1 Du serveur itératif au serveur concurrent . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
10.1.1 Rappel du problème . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
10.1.2 Le motif « un thread par client » . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
10.2 Implémentation complète en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
10.2.1 La classe ClientHandler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
10.2.2 Le serveur principal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
10.3 Aller plus loin : le pool de threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
10.3.1 La limite du « un thread par client » . . . . . . . . . . . . . . . . . . . . . . . . . 68
10.4 Partager un état entre les handlers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
10.5 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
C QCM de révision 87
C.1 Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
C.2 Corrigé commenté . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
D Glossaire 91
Avant l’apparition des réseaux, chaque ordinateur fonctionnait de manière isolée : pour transférer un fichier
d’une machine à une autre, il fallait utiliser un support physique amovible (disquette, bande magnétique,
clé USB). Cette approche, parfois appelée avec humour sneakernet (le réseau des baskets), était lente, peu
fiable et impossible à automatiser.
Un réseau informatique permet à des machines de communiquer entre elles pour :
Envoyer une lettre suit un « protocole » implicite : on écrit l’adresse du destinataire au recto, celle de
l’expéditeur au dos, on colle un timbre, on poste dans une boîte aux lettres. Si l’on ne respecte pas ces
règles (pas d’adresse, pas de timbre), la lettre n’arrive pas. Les réseaux informatiques fonctionnent de
la même manière : un message doit être correctement adressé, affranchi (encapsulé) et acheminé.
5
CHAPITRE 1. INTRODUCTION AUX RÉSEAUX INFORMATIQUES 6
• Le concentrateur (hub) : équipement obsolète qui répète le signal reçu sur tous ses ports. Toutes
les machines reçoivent toutes les trames, ce qui crée des collisions et des problèmes de sécurité.
• Le commutateur (switch) : équipement de couche 2 qui apprend les adresses MAC des machines
connectées et ne transmet une trame que sur le port du destinataire. C’est l’équipement central des
réseaux locaux modernes.
• Le routeur (router) : équipement de couche 3 qui interconnecte des réseaux différents et achemine
les paquets d’un réseau à l’autre en se basant sur les adresses IP. C’est lui qui relie votre réseau
domestique à Internet.
• Le point d’accès Wi-Fi (access point) : permet aux équipements sans fil de rejoindre le réseau
filaire.
• Le pare-feu (firewall) : filtre le trafic entrant et sortant selon des règles de sécurité.
PC 1 PC 2
PC 3 Serveur
Figure 1.1: Un réseau local typique : les hôtes sont reliés à un switch, lui-même relié à un routeur qui
donne accès à Internet.
• Câble à paires torsadées (Ethernet, RJ45) : le plus courant en réseau local ; catégories 5e, 6, 6a, 7
selon le débit (de 1 à 10 Gbit/s et plus).
• Fibre optique : transmission par la lumière, très hauts débits, longues distances, insensible aux
perturbations électromagnétiques.
• Ondes radio : Wi-Fi (IEEE 802.11), Bluetooth, réseaux cellulaires (4G/5G), liaisons satellite.
Remarque
Internet est le plus grand WAN du monde : c’est un réseau de réseaux (« inter-networks ») qui
interconnecte des millions de LAN via des routeurs et des liaisons longue distance. Le protocole qui
rend cette interconnexion possible est précisément IP (Internet Protocol), que nous étudierons au
chapitre 4.
• Simplex : la communication ne se fait que dans un seul sens (ex. : diffusion radio, capteur de
température qui émet uniquement).
• Half-duplex : la communication se fait dans les deux sens, mais pas en même temps (ex. : talkie-
walkie).
• Full-duplex : la communication se fait dans les deux sens simultanément (ex. : téléphone, liaison
Ethernet moderne, connexion TCP).
simplex
A B
half-duplex
A B
(alterné)
full-duplex (simultané)
A B
• Unicast : un émetteur vers un seul destinataire (cas le plus courant, ex. : navigation web).
• Broadcast : un émetteur vers toutes les machines du réseau local (ex. : requête ARP, découverte
DHCP).
• Multicast : un émetteur vers un groupe de machines abonnées (ex. : diffusion vidéo IPTV).
• Anycast : un émetteur vers le plus proche d’un groupe de serveurs identiques (ex. : serveurs
DNS racine).
Dans la commutation de paquets, les données sont découpées en petits blocs appelés paquets,
chacun étant acheminé indépendamment à travers le réseau. Les liens sont partagés entre toutes les
communications. C’est le principe fondateur d’Internet : aucune ressource n’est réservée, ce qui rend
le réseau robuste et efficace, au prix de délais variables.
paquet 1
R1 R2
Source Destination
paquet 2
R3 R4
Figure 1.3: Commutation de paquets : deux paquets d’un même message peuvent emprunter des chemins
différents et être réassemblés à destination.
Attention
Ne confondez pas octet (byte, 8 bits, noté « o » ou « B ») et bit (noté « b »). Une connexion à
100 Mbit/s permet de télécharger au mieux 12,5 Mo par seconde (100/8 = 12,5).
À retenir
• Un réseau = des hôtes + des équipements d’interconnexion + des supports + des protocoles.
• Internet repose sur la commutation de paquets et le protocole IP.
• Le switch travaille avec les adresses MAC (couche 2), le routeur avec les adresses IP (couche 3).
• Débit en bits/s ; latence en ms ; 1 octet = 8 bits.
1→b (protocole = règles de communication) ; 2→d (hôte = équipement terminal) ; 3→a (routeur =
interconnexion par adresses IP) ; 4→e (switch = commutation par adresses MAC) ; 5→c (latence =
temps de transit).
Pour chaque situation, indiquez l’équipement approprié (switch, routeur, point d’accès, pare-feu) :
a) Relier 24 postes de travail dans un même bureau.
b) Connecter le réseau de l’entreprise à Internet.
c) Permettre aux smartphones des employés d’accéder au réseau.
d) Bloquer les connexions entrantes non autorisées.
Le trafic informatique est sporadique (rafales suivies de silences). Avec la commutation de circuits, les
ressources réservées seraient gaspillées pendant les silences. La commutation de paquets mutualise
les liens : plusieurs communications partagent la même infrastructure, d’où un bien meilleur taux
d’utilisation et une grande robustesse (un paquet peut contourner une panne).
Inconvénient : aucune garantie de délai ni de débit — en cas de congestion, les paquets s’accumulent
dans les files d’attente des routeurs (latence et gigue augmentent) et peuvent être perdus. C’est pourquoi
des mécanismes de qualité de service (QoS) ou des protocoles fiables comme TCP sont nécessaires.
La topologie d’un réseau décrit la manière dont les équipements sont reliés entre eux.
• La topologie physique décrit le câblage réel : où passent les câbles, comment les machines sont
physiquement connectées.
• La topologie logique décrit la manière dont les données circulent : qui peut parler à qui, et dans
quel ordre.
Les deux peuvent différer : un réseau câblé en étoile autour d’un hub se comporte logiquement comme
un bus (tout le monde entend tout).
Tous les équipements sont raccordés à un câble unique (le « bus » ou « backbone »), terminé à chaque
extrémité par un bouchon de terminaison qui absorbe le signal.
PC 1 PC 2 PC 3 PC 4
terminateur terminateur
Câble coaxial unique partagé par toutes les stations
Fonctionnement. Quand une station émet, le signal se propage sur tout le câble : toutes les stations le
reçoivent, mais seule la destinataire (identifiée par son adresse MAC) le traite. Si deux stations émettent
en même temps, il y a collision ; le protocole historique CSMA/CD (Carrier Sense Multiple Access with
Collision Detection) gérait ces collisions : écouter avant d’émettre, détecter la collision, attendre un délai
aléatoire et réémettre.
11
CHAPITRE 2. LES TOPOLOGIES DE RÉSEAUX 12
Avantages Inconvénients
Simple et économique (un seul câble) Une coupure du câble paralyse tout le
réseau
Facile à étendre sur de courtes distances Collisions : performances dégradées
avec le nombre de stations
Peu de câblage Diagnostic des pannes difficile ; sécurité
faible (tout le monde entend tout)
Remarque
La topologie en bus (Ethernet 10Base2 et 10Base5 sur câble coaxial) a pratiquement disparu des
réseaux locaux, mais le concept reste utilisé dans les bus de terrain industriels (CAN, Modbus) et
certains réseaux IoT.
Tous les équipements sont raccordés à un nœud central (aujourd’hui un switch). C’est la topologie
dominante des réseaux locaux modernes.
PC 1
PC 2 PC 5
Switch
PC 3 PC 4
Fonctionnement. Le switch maintient une table d’adresses MAC (table CAM) : il apprend sur quel port
se trouve chaque machine en observant les trames entrantes, puis ne retransmet chaque trame que sur le
port du destinataire. Chaque liaison étant point à point et full-duplex, il n’y a plus de collisions.
Chaque équipement est relié à exactement deux voisins, formant une boucle fermée. Les données circulent
dans un sens (anneau simple) ou dans les deux (anneau double, plus tolérant aux pannes).
Avantages Inconvénients
La panne d’un câble n’isole que la station Le nœud central est un point de défail-
concernée lance unique (SPOF)
Excellentes performances (pas de colli- Coût en câblage (un câble par station)
sions, full-duplex)
Diagnostic et administration faciles La capacité du switch limite le réseau
Ajout/retrait d’une station sans perturber
les autres
PC 1
PC 2 PC 5
circulation
du jeton
PC 3 PC 4
Fonctionnement. L’accès au support est souvent contrôlé par un jeton (token) : seule la station qui détient
le jeton peut émettre, ce qui élimine les collisions de manière déterministe. C’est le principe historique de
Token Ring (IEEE 802.5) et de FDDI.
Avantages Inconvénients
Accès déterministe (pas de collisions) La panne d’une station ou d’un lien peut
rompre l’anneau (sauf anneau double)
Performances stables sous forte charge Latence : chaque trame traverse les sta-
tions intermédiaires
Équitable : chaque station a son tour Ajout d’une station = interruption de
l’anneau
Chaque équipement est relié à plusieurs autres. Dans un maillage complet (full mesh), chaque nœud est
relié à tous les autres ; dans un maillage partiel, seulement à certains.
N1
N2 N5
N3 N4
5×4
Figure 2.4: Topologie maillée complète à 5 nœuds : 2 = 10 liaisons.
n(n − 1)
L= liaisons.
2
Ce nombre croît quadratiquement : 10 nœuds exigent déjà 45 liaisons, ce qui rend le maillage complet
réservé aux cœurs de réseau critiques.
Avantages Inconvénients
Tolérance aux pannes maximale Coût en câblage et en interfaces très élevé
(chemins multiples)
Pas de point de défaillance unique Complexité de configuration et de
routage
Répartition de charge possible Croissance quadratique des liens
Le cœur d’Internet est un gigantesque maillage partiel de routeurs : si une liaison tombe, les protocoles
de routage (OSPF, BGP) recalculent automatiquement de nouveaux chemins. C’est l’héritage direct du
projet ARPANET, conçu pour résister à la destruction d’une partie du réseau.
La topologie en arbre est une hiérarchie d’étoiles : un nœud racine (switch de cœur) relie des switches
de distribution, qui relient eux-mêmes des switches d’accès auxquels sont connectées les stations.
C’est l’architecture classique cœur / distribution / accès des réseaux d’entreprise et de campus.
Cœur
Distrib. 1 Distrib. 2
PC PC PC Serveur
Une topologie hybride combine plusieurs topologies de base : par exemple des étoiles locales
interconnectées par un anneau de fibres optiques (fréquent dans les réseaux métropolitains), ou un
arbre dont le cœur est doublé en maillage pour la redondance.
À retenir
a) Étoile autour d’un switch 48 ports : panne isolée par poste, administration centralisée, perfor-
mances full-duplex.
4×3
b) Maillage complet : avec 4 sites, il ne faut que 2 = 6 liaisons, et chaque site reste joignable
même si deux liaisons tombent.
c) Arbre (hiérarchique) : un cœur de réseau (souvent redondé), un switch de distribution par
bâtiment, des switches d’accès par étage. C’est l’architecture cœur/distribution/accès.
Dans un réseau en étoile de 12 postes autour d’un switch, indiquez les conséquences de chacune des
pannes suivantes :
a) Le câble reliant le poste 5 au switch est sectionné.
b) Le switch tombe en panne.
c) La carte réseau du poste 3 émet en continu des trames erronées (carte « bavarde »).
Comment améliorer la disponibilité de ce réseau ?
a) Seul le poste 5 est isolé ; les 11 autres continuent de communiquer normalement. C’est l’avantage
majeur de l’étoile.
b) Tout le réseau est paralysé : le switch est le point de défaillance unique (SPOF).
c) Un switch moderne isole le trafic par port : seules les communications impliquant le poste 3 sont
perturbées (contrairement à un bus ou un hub où tout le réseau serait pollué). Certains switches
Un réseau de 10 machines est câblé en étoile autour d’un hub (concentrateur). Quelle est sa topologie
physique ? Sa topologie logique ? Quelles conséquences sur les performances et la sécurité ? Que se
passe-t-il si on remplace le hub par un switch ?
Topologie physique : étoile (chaque machine a son câble vers le hub). Topologie logique : bus, car
le hub répète chaque signal sur tous les ports : toutes les machines partagent le même domaine de
collision et entendent tout le trafic.
Conséquences : collisions fréquentes (le débit utile s’effondre avec le nombre de machines actives),
half-duplex obligatoire, et faille de sécurité majeure (n’importe quelle machine peut espionner le trafic
des autres avec un simple analyseur comme Wireshark).
Avec un switch : la topologie logique devient elle aussi une étoile ; chaque port forme son propre
domaine de collision, les liaisons passent en full-duplex, les trames unicast ne sont délivrées qu’au
destinataire — performances et confidentialité nettement améliorées.
Faire communiquer deux programmes situés sur deux machines distantes est un problème d’une immense
complexité : il faut transformer des données applicatives en signaux électriques ou optiques, les acheminer
à travers des dizaines d’équipements hétérogènes, corriger les erreurs, gérer les pertes, retrouver le bon
programme destinataire. . .
Plutôt que de résoudre ce problème d’un bloc, on le découpe en couches : chaque couche résout un
sous-problème précis et offre ses services à la couche supérieure, en s’appuyant sur les services de la
couche inférieure. C’est exactement le principe de la séparation des responsabilités en génie logiciel.
• Chaque couche N fournit des services à la couche N + 1 via une interface bien définie.
• Chaque couche N d’une machine dialogue virtuellement avec la couche N de la machine distante
grâce à un protocole de niveau N (communication horizontale logique).
• Physiquement, les données descendent les couches chez l’émetteur, traversent le support, puis
remontent les couches chez le récepteur (communication verticale réelle).
• On peut remplacer l’implémentation d’une couche sans toucher aux autres (ex. : passer du Wi-Fi
au câble ne change rien à votre navigateur).
18
CHAPITRE 3. LES MODÈLES EN COUCHES : OSI ET TCP/IP 19
Le modèle OSI (Open Systems Interconnection), normalisé par l’ISO en 1984, définit sept couches. Il sert
avant tout de cadre conceptuel et de vocabulaire commun.
Remarque
Moyen mnémotechnique (de bas en haut) : « Pour Le Réseau Tout Se Passe Automatiquement »
(Physique, Liaison, Réseau, Transport, Session, Présentation, Application).
Couche 1 — Physique. Transmettre des bits bruts sur le support : niveaux électriques, longueurs d’onde
optiques, fréquences radio, connecteurs, débits. Unité : le bit. Équipements : câbles, hubs, répéteurs,
transceivers.
Couche 2 — Liaison de données. Organiser les bits en trames (frames), détecter les erreurs de trans-
mission (CRC), gérer l’accès au support partagé et l’adressage physique par adresses MAC (48 bits, ex.
3C:5A:B4:12:9F:01). Protocoles : Ethernet (IEEE 802.3), Wi-Fi (IEEE 802.11). Équipements :
switches, cartes réseau.
Couche 3 — Réseau. Acheminer des paquets de réseau en réseau jusqu’à la destination finale grâce à
l’adressage logique (adresses IP) et au routage. Protocoles : IP, ICMP (ping), ARP (résolution IP→MAC,
à cheval sur 2 et 3). Équipement : routeur.
Couche 4 — Transport. Assurer la communication de bout en bout entre processus (et non plus
seulement entre machines) grâce aux numéros de port. Deux grands protocoles : TCP (fiable, connecté)
et UDP (léger, non connecté). Unités : segment (TCP) et datagramme (UDP). C’est la couche que nous
manipulerons directement en programmation socket.
Couche 5 — Session. Établir, synchroniser et clôturer des sessions de dialogue (points de reprise, gestion
du tour de parole). En pratique, souvent fusionnée dans les applications.
Couche 7 — Application. Les protocoles directement utilisés par les applications : HTTP/HTTPS (web),
SMTP/IMAP (courriel), FTP (fichiers), DNS (résolution de noms), SSH, MQTT (IoT). . .
Le modèle TCP/IP (ou modèle Internet, ou modèle DoD), antérieur au modèle OSI, est celui réellement
implémenté dans tous les systèmes. Il compte quatre couches (parfois cinq dans la présentation académique
séparant physique et liaison).
7. Application
Application
6. Présentation
HTTP, DNS, SMTP, SSH
5. Session
4. Transport Transport
TCP, UDP
Internet
3. Réseau IP, ICMP, ARP
2. Liaison
Accès réseau
Ethernet, Wi-Fi
1. Physique
Tableau 3.1: Les quatre couches du modèle TCP/IP et leurs unités de données (PDU).
Lorsqu’un message descend les couches chez l’émetteur, chaque couche ajoute ses propres informations
de contrôle sous forme d’un en-tête (header) placé devant les données reçues de la couche supérieure
(la couche liaison ajoute aussi une queue de trame, le CRC). C’est l’encapsulation. À la réception,
chaque couche retire et interprète son en-tête : c’est la décapsulation.
Application Données
segment TCP
paquet IP
Accès
En-tête Ethernet En-tête IP En-tête TCP Données CRC
réseau
trame Ethernet
Figure 3.3: Encapsulation : chaque couche enveloppe les données de la couche supérieure.
À retenir
• OSI = 7 couches (modèle de référence) ; TCP/IP = 4 couches (modèle implémenté).
• Chaque couche a son adressage : MAC (liaison), IP (réseau), port (transport).
• PDU : bits → trame → paquet → segment → message.
• Encapsulation : chaque couche ajoute son en-tête en descendant ; décapsulation en remontant.
• L’adresse MAC change à chaque saut ; l’adresse IP de bout en bout reste (en principe) la même.
Machine A Machine B
protocole applicatif (HTTP. . . )
Application Application
protocole TCP/UDP
Transport Transport
protocole IP
Internet Internet
support physique
Accès réseau Accès réseau
Figure 3.4: Communication logique horizontale entre couches homologues (pointillés) et communication
réelle verticale (flèches pleines).
Placez chacun des éléments suivants dans la bonne couche du modèle TCP/IP : HTTP, adresse MAC,
TCP, routeur, fibre optique, adresse IP, numéro de port, switch, DNS, UDP, trame, segment.
Une application envoie un message de 1 460 octets. L’en-tête TCP fait 20 octets, l’en-tête IP 20 octets,
l’en-tête Ethernet 14 octets et le CRC 4 octets.
a) Quelle est la taille de la trame Ethernet émise ?
b) Quel est le pourcentage de « surcharge » (overhead) dû aux en-têtes ?
c) Si le message applicatif faisait 14 600 octets et que TCP le découpe en segments de 1 460 octets
de données, combien de trames seraient émises et combien d’octets au total ?
Conclusion : les adresses IP (couche 3) identifient les extrémités et restent inchangées de bout en bout,
tandis que les adresses MAC (couche 2) ne servent qu’au saut local et changent à chaque traversée de
routeur. C’est l’illustration parfaite de la séparation des couches.
L’adressage IP
L’adresse MAC est comme votre numéro de carte d’identité : unique, attribué une fois pour toutes.
L’adresse IP est comme votre adresse postale : elle change quand vous déménagez (changez de réseau)
et c’est elle qui permet d’acheminer le courrier depuis n’importe où dans le monde.
Une adresse IPv4 est un nombre de 32 bits, noté en décimal pointé : quatre octets séparés par des points,
chacun entre 0 et 255.
| {z } . 10101000
11000000 | {z } . 00000001
| {z } . 00001010
| {z } = [Link]
192 168 1 10
Il existe 232 ≈ 4,3 milliards d’adresses IPv4 — un nombre devenu insuffisant, d’où le NAT et IPv6.
• la partie réseau (network ID) : identifie le réseau, commune à toutes les machines du même réseau ;
• la partie hôte (host ID) : identifie la machine au sein de ce réseau.
24
CHAPITRE 4. L’ADRESSAGE IP 25
32 bits
Le masque indique où s’arrête la partie réseau : c’est un mot de 32 bits formé de n bits à 1 (partie
réseau) suivis de 32 − n bits à 0 (partie hôte). On le note en décimal pointé ([Link]) ou
en notation CIDR /n (ex. /24). L’adresse du réseau s’obtient par un ET logique bit à bit entre
l’adresse IP et le masque.
• l’adresse de réseau : tous les bits d’hôte à 0 (ex. [Link]) — elle désigne le réseau
lui-même ;
• l’adresse de broadcast : tous les bits d’hôte à 1 (ex. [Link]) — un paquet envoyé à
cette adresse est reçu par toutes les machines du réseau.
Nhôtes = 2(32−n) − 2
Un /24 offre 28 − 2 = 254 hôtes. Un /26 offre 26 − 2 = 62 hôtes (plages .0-.63, .64-.127,
.128-.191, .192-.255).
Trois plages sont réservées aux réseaux privés et ne sont jamais routées sur Internet :
• [Link]/8 ([Link] – [Link]) ;
• [Link]/12 ([Link] – [Link]) ;
• [Link]/16 ([Link] – [Link]).
S’y ajoute la boucle locale [Link]/8, dont la célèbre adresse [Link] (localhost) désigne
la machine elle-même — essentielle pour tester des programmes réseau sur un seul PC (chapitre 6).
PC A
[Link]
IP publique
Routeur NAT Internet
ex. [Link]
PC B table NAT :
[Link] [Link]:5034 ↔ [Link]:62001
Figure 4.2: NAT : plusieurs machines privées partagent une adresse publique.
Le subnetting consiste à découper un réseau en plusieurs sous-réseaux plus petits en empruntant des bits à
la partie hôte. Objectifs : segmenter le trafic, isoler des services (administration, Wi-Fi invités, serveurs),
optimiser l’utilisation des adresses.
IPv6 répond à l’épuisement d’IPv4 avec des adresses de 128 bits notées en hexadécimal (8 groupes de 16
bits), par exemple :
2001:0db8:0000:0000:0000:ff00:0042:8329
abrégeable en 2001:db8::ff00:42:8329. Soit 2128 ≈ 3,4 × 1038 adresses — de quoi adresser chaque
grain de sable de la planète. IPv6 supprime le besoin de NAT, intègre l’autoconfiguration (SLAAC) et
simplifie l’en-tête. La boucle locale devient ::1.
À retenir
• IPv4 = 32 bits = 4 octets en décimal pointé ; partie réseau + partie hôte, séparées par le masque.
• Adresse réseau = IP ET masque ; broadcast = bits d’hôte à 1 ; hôtes utilisables = 232−n − 2.
• Plages privées : 10/8, 172.16/12, 192.168/16 ; boucle locale : [Link].
• Le NAT partage une IP publique ; DHCP distribue les adresses (DORA).
Les machines suivantes peuvent-elles communiquer directement (sans routeur) ? Justifiez par le calcul.
a) [Link]/27 et [Link]/27 ;
b) [Link]/27 et [Link]/27 ;
c) [Link]/16 et [Link]/16.
Une école dispose du réseau [Link]/24 et doit créer 4 sous-réseaux : Administration (50
postes), Salle TP1 (40 postes), Salle TP2 (40 postes), Serveurs (10 machines). Proposez un découpage
en sous-réseaux de taille égale et vérifiez qu’il convient.
Le besoin maximal est 50 postes ; il faut donc au moins 50 + 2 = 52 adresses par sous-réseau, soit 6
bits d’hôte (26 = 64 ≥ 52). On emprunte 2 bits : masque /26 ([Link]), 4 sous-réseaux de 62
hôtes chacun :
Vérification : 62 ≥ 50, 62 ≥ 40, 62 ≥ 10 — le plan convient, avec une marge d’évolution. (Un
découpage en tailles variables, VLSM, optimiserait encore l’espace : /26 pour l’administration, /26
pour chaque salle, /28 pour les serveurs.)
La couche Internet (IP) sait acheminer un paquet jusqu’à une machine. Mais une machine exécute des
dizaines de programmes simultanément : navigateur, messagerie, jeu en ligne, mises à jour. . . Quand un
paquet arrive, à quel programme le remettre ?
C’est le rôle de la couche transport : assurer la communication de processus à processus (de programme
à programme), et non plus seulement de machine à machine. L’outil clé est le numéro de port.
30
CHAPITRE 5. LA COUCHE TRANSPORT : TCP ET UDP 31
L’adresse IP est l’adresse de l’immeuble ; le port est le numéro d’appartement. Le facteur (IP)
amène la lettre jusqu’à l’immeuble ; le gardien (couche transport) la dépose dans la bonne boîte (le
bon processus) grâce au numéro d’appartement (le port).
Le couple (adresse IP, numéro de port) forme une adresse de transport, et une communication est
identifiée de façon unique par le quadruplet
(plus le protocole). Le point d’accès logiciel à cette communication s’appelle une socket — nous la
programmerons au chapitre 8.
Données applicatives
Usages typiques d’UDP. DNS (une question, une réponse : inutile d’établir une connexion), streaming
audio/vidéo et visioconférence (mieux vaut perdre une image que d’attendre sa retransmission), jeux en
ligne, DHCP, protocoles IoT légers, et QUIC/HTTP3 (qui réimplémente la fiabilité au-dessus d’UDP).
Client Serveur
SYN (seq =
x)
x + 1)
eq = y, ack =
SYN-ACK (s
ACK (ack =
y + 1)
1. Le client envoie un segment SYN avec un numéro de séquence initial x : « je veux ouvrir une
connexion ».
2. Le serveur répond SYN-ACK : il accepte (ack = x + 1) et propose son propre numéro initial y.
3. Le client confirme par un ACK (ack = y + 1). La connexion est établie, en full-duplex.
Chaque octet est numéroté. Le récepteur acquitte les données reçues ; si l’émetteur ne reçoit pas d’ACK
avant l’expiration d’un temporisateur, il retransmet. La fenêtre glissante autorise l’envoi de plusieurs
segments sans attendre chaque ACK individuellement, ce qui remplit le « tuyau » et maximise le débit ; la
taille de fenêtre annoncée par le récepteur assure le contrôle de flux.
La fermeture est en quatre temps (chaque sens se ferme indépendamment) : FIN → ACK → FIN → ACK.
Une connexion peut ainsi rester « semi-fermée » : un côté a fini d’émettre mais continue de recevoir.
Émetteur Récepteur
ACK 1001
ACK 2001
retransmission seg
3
ACK 3001
À retenir
• La couche transport relie des processus grâce aux ports (16 bits, 0–65535).
• Une communication = quadruplet (IP src, port src, IP dst, port dst).
• TCP : fiable, ordonné, connecté (SYN, SYN-ACK, ACK), flux d’octets.
• UDP : léger, sans connexion ni garantie, messages indépendants.
• Les serveurs écoutent sur des ports fixes et connus ; les clients utilisent des ports éphémères.
a) TCP : un seul bit erroné corromprait l’exécutable ; l’intégrité et l’ordre sont indispensables.
b) UDP : la fluidité prime ; retransmettre une image vieille de 500 ms est inutile, on préfère afficher
la suivante. (Les applications ajoutent leurs propres mécanismes : RTP, correction d’erreur.)
c) UDP : un échange question/réponse tient dans un datagramme ; établir une connexion TCP
triplerait la latence. (TCP est utilisé en repli pour les grandes réponses.)
d) TCP (avec TLS) : fiabilité et ordre obligatoires — aucune perte tolérable.
e) UDP : messages petits et fréquents, la perte occasionnelle d’une mesure est acceptable ;
l’économie d’énergie et de bande passante prime (c’est le choix de nombreux protocoles IoT
comme CoAP).
a) Port 80 en écoute : serveur web (HTTP) ; port 22 : SSH (une session d’administration en cours
depuis [Link]) ; port 53 en UDP : serveur DNS.
b) Deux clients web ([Link] et [Link]). Le serveur les distingue par le quadruplet
complet : même IP/port local ([Link]:80), mais couples (IP, port) distants différents
(52311 vs 61024). Le port 80 ne désigne que le point d’écoute ; chaque connexion établie est un
quadruplet unique.
c) [Link] signifie « toutes les interfaces » : le service accepte les connexions quelle que soit
l’adresse IP locale par laquelle elles arrivent (Ethernet, Wi-Fi, boucle locale).
a) SYN (60) + SYN-ACK (60) = le client peut émettre son ACK et ses données après 120 ms (1
RTT).
b) Handshake : 120 ms ; le client envoie ACK + requête (60 ms) ; le serveur répond (60 ms). Total
= 240 ms (2 RTT).
c) En UDP : requête (60) + réponse (60) = 120 ms (1 RTT), soit deux fois moins. C’est exactement
la motivation de DNS-sur-UDP et de QUIC (qui fusionne handshake transport et TLS pour
économiser des allers-retours).
Une connexion TCP est établie avec un numéro de séquence initial client x = 4000. Le client envoie
ensuite trois segments de données de 500 octets chacun.
a) Quels sont les numéros de séquence des trois segments ?
b) Quel numéro d’acquittement le serveur renvoie-t-il après avoir reçu les trois segments ?
c) Le deuxième segment est perdu. Qu’acquitte le serveur après réception des segments 1 et 3, et
que fait le client ?
a) Le SYN consomme un numéro : les données commencent à 4001. Segments : seq 4001, seq
4501, seq 5001.
b) L’ACK indique le prochain octet attendu : 4001 + 1500 = 5501.
c) Le serveur reçoit 4001–4500 puis 5001–5500 : il y a un trou. Il acquitte ACK 4501 (il répète cet
ACK à la réception du segment 3 : ACK dupliqué). Le client, sur expiration du temporisateur ou
après trois ACK dupliqués (fast retransmit), retransmet le segment seq 4501. Une fois le trou
comblé, le serveur acquitte d’un coup ACK 5501 (acquittement cumulatif).
Ce chapitre est le pivot du cours : il relie les notions réseau (IP, port) à la notion système de processus, et
explique en détail qui parle à qui dans deux scénarios fondamentaux :
• Un programme est un fichier exécutable inerte stocké sur disque (ex. [Link], java).
• Un processus est une instance en cours d’exécution d’un programme : il possède son espace
mémoire, ses fichiers ouverts, ses threads.
• Le PID (Process IDentifier) est le numéro unique que le système d’exploitation attribue à chaque
processus à sa création. Il permet à l’OS (et à l’administrateur) de désigner, surveiller ou tuer un
processus précis.
Un même programme peut être lancé plusieurs fois : chaque instance est un processus distinct avec
son propre PID (ex. : trois fenêtres d’un navigateur, ou deux exécutions de votre programme Java).
Sous Windows : tasklist ou le Gestionnaire des tâches. Sous Linux/macOS : ps -ef ou top.
On y voit pour chaque processus son PID, le programme d’origine et sa consommation de ressources.
Attention
Le PID n’a aucune signification en dehors de la machine. Le PID 4217 de votre PC et le PID 4217
du PC voisin désignent des processus totalement différents. C’est pourquoi le PID ne peut pas servir
d’adresse réseau : pour désigner un processus distant, on utilise le couple (adresse IP, port). Le lien
entre port et PID est établi localement par le système d’exploitation.
36
CHAPITRE 6. COMMUNICATION ENTRE PROGRAMMES : IP, PORT ET PID 37
Pour qu’un message parte d’un programme et arrive à un autre programme, trois identifiants se relaient :
Sous Windows, netstat -ano affiche chaque connexion avec le PID du processus propriétaire
; tasklist | findstr <PID> révèle alors le programme. Sous Linux : ss -tunap ou
netstat -tunap. C’est l’outil indispensable pour répondre à « quel programme occupe le port
8080 ? ».
Considérons un programme client (processus PID 3120 sur le PC A) qui dialogue avec un programme
serveur (processus PID 877 sur le PC B). Le serveur écoute sur le port 5000 ; l’OS du client a attribué au
client le port éphémère 52614.
PC A — [Link] PC B — [Link]
[Link] [Link]
PID 3120 PID 877
Figure 6.2: Deux programmes sur deux machines : la connexion est le quadruplet ([Link], 52614,
[Link], 5000).
1. Le processus client (PID 3120) écrit « Bonjour » dans sa socket — pour lui, c’est aussi simple
qu’écrire dans un fichier.
2. La couche TCP du PC A encapsule les octets dans un segment : port source 52614, port destination
5000.
3. La couche IP ajoute : IP source [Link], IP destination [Link]. Les deux machines étant
sur le même réseau (/24), le paquet est remis directement (sinon il passerait par la passerelle).
4. La couche liaison résout [Link] en adresse MAC via ARP, construit la trame, et la carte réseau
émet les bits.
5. Sur le PC B, la trame remonte les couches ; la couche TCP lit « port destination 5000 ».
6. L’OS consulte sa table : le port 5000 appartient au PID 877 ⇒ les octets « Bonjour » sont déposés
dans le tampon de réception de la socket du serveur, qui les lit par un simple read.
Remarque
Faut-il deux ordinateurs pour faire de la programmation réseau ? Non ! Deux processus du même PC
peuvent communiquer par sockets exactement comme s’ils étaient distants, grâce à l’interface de boucle
locale (loopback).
[Link] [Link]
PID 4502 PID 4498
Figure 6.3: Deux programmes sur le même PC : la communication passe par [Link] et reste à l’intérieur
de la pile TCP/IP.
• Les deux processus ont des PID différents (4498 et 4502), même s’ils proviennent du même
fichier .java ou de programmes différents : ce sont deux instances indépendantes, chacune avec sa
mémoire.
• Les deux processus partagent la même adresse IP ([Link]) : ce sont les ports qui les distinguent.
Le quadruplet de connexion est ([Link], 53120, [Link], 5000) — parfaitement valide car les
ports diffèrent.
• Le code est rigoureusement identique au scénario distant : il suffit que le client se connecte à
localhost au lieu de [Link]. C’est ce qui rend le développement réseau si pratique :
on développe et on teste tout sur sa machine, puis on déploie sans changer une ligne de logique.
• Les performances sont excellentes (pas de matériel traversé), mais le comportement (tampons,
blocages, ordre) est le même qu’en réseau réel.
Quand vous lancez une application Spring Boot (localhost:8080), une base PostgreSQL
(localhost:5432) et un front Angular (localhost:4200), vous avez trois processus serveurs
sur la même machine, distingués uniquement par leurs ports, et votre navigateur (un quatrième proces-
sus) leur parle via la boucle locale. La programmation réseau locale n’est pas un cas d’école : c’est
l’architecture standard du développement moderne et des microservices.
Attention
• Un port (pour un protocole donné) ne peut être mis en écoute que par un seul processus
à la fois. Si le port 5000/TCP est déjà pris, un second bind échoue : c’est la fameuse erreur
Address already in use / BindException en Java. Solution : libérer le port (arrêter
le processus, repéré par netstat -ano) ou en choisir un autre.
• TCP et UDP ont des espaces de ports séparés : 5000/TCP et 5000/UDP peuvent appartenir à
deux processus différents.
• Un même processus peut posséder plusieurs sockets et donc plusieurs ports (ex. : un navigateur
ouvre des dizaines de connexions).
• Sur la plupart des systèmes, écouter sur un port < 1024 exige des privilèges administrateur.
À retenir
• PID : identifiant local d’un processus, attribué par l’OS, sans signification réseau.
• Port : identifiant réseau d’un point de communication ; l’OS maintient la correspondance port
↔ PID.
• IP : identifie la machine ; [Link] / localhost = la machine elle-même.
• Deux processus du même PC communiquent par sockets via la boucle locale, distingués par
leurs ports ; le code est le même qu’en réseau réel.
• Un port d’écoute = un seul processus (par protocole) ; sinon Address already in use.
a) Faux : le PID est unique parmi les processus vivants d’une machine (il peut être réutilisé après
la mort d’un processus).
b) Vrai : le PID est purement local ; chaque OS numérote indépendamment.
c) Faux : un port d’écoute TCP appartient à un seul processus ; le second obtiendrait Address
already in use.
d) Vrai : des milliers de clients se connectent au port 443 d’un même serveur ; chaque connexion
reste unique par son quadruplet (IP et port source différents).
e) Faux : le PID est inconnu et inutilisable à distance. Il faut le couple (IP, port) ; c’est l’OS
distant qui traduit le port en processus.
a) Deux processus (deux instances de la JVM), avec deux PID distincts attribués par l’OS.
b) Par exemple : ([Link], 51873, [Link], 6000) — IP identiques, port serveur fixe 6000, port
client éphémère choisi par l’OS.
c) Non : à destination de [Link], les données restent dans la pile TCP/IP (interface de boucle
locale) et ne touchent aucune interface physique.
d) Le second serveur tente de réserver le port 6000 déjà occupé : il échoue avec une
BindException : Address already in use et s’arrête (si le code ne gère pas
l’exception).
e) Oui : la boucle locale est virtuelle et indépendante de tout matériel réseau. C’est une excellente
preuve que le trafic localhost ne sort pas de la machine.
a) Le PID 6204 est un serveur : il est en LISTENING sur le port 8080, sur toutes les interfaces
([Link]).
b) Non : c’est la même connexion vue des deux extrémités, toutes deux locales ! Ligne 2 : côté
serveur (local 8080, distant 54011, PID 6204). Ligne 3 : côté client (local 54011, distant 8080,
PID 7788). C’est typiquement un test local : le processus 7788 (client) parle au processus 6204
(serveur) via la boucle locale.
c) Le PID 3344 a une connexion sortante vers une IP publique sur le port 443 : c’est une connexion
Décrivez précisément, étape par étape, ce que fait la machine [Link] lorsqu’elle reçoit une trame
contenant un segment TCP dont le port de destination est 5000, sachant que le processus PID 877 a
une socket en écoute sur ce port. Que se passerait-il si aucun processus n’écoutait le port 5000 ?
1. La carte réseau vérifie l’adresse MAC de destination et transmet la trame au pilote ; le CRC est
contrôlé.
2. La couche IP vérifie que l’adresse de destination ([Link]) est bien la sienne et que l’en-tête
est valide, puis passe la charge utile à TCP (champ protocole = 6).
3. TCP lit le quadruplet. Si le segment appartient à une connexion établie, les données vont dans le
tampon de réception de cette connexion ; si c’est un SYN, TCP consulte les sockets en écoute :
le port 5000 est associé (via la table de l’OS) au PID 877.
4. Les données sont déposées dans le tampon de la socket du processus 877 ; si celui-ci est bloqué
dans un read, il est réveillé et reçoit les octets.
Si aucun processus n’écoute le port 5000 : TCP répond par un segment RST (reset), et le client voit
une erreur immédiate Connection refused. (En UDP, la machine renverrait un message ICMP
port unreachable.)
L’architecture client-serveur
1. requête
Serveur
Client
IP fixe : port connu
2. réponse
Le serveur (au sens informatique !) est comme un restaurant : il a une adresse fixe et des horaires
d’ouverture (port en écoute), il ne sait pas qui viendra, et il sert quiconque se présente. Le client choisit
le restaurant, s’y rend (connexion), passe commande (requête) et reçoit son plat (réponse). Plusieurs
clients peuvent être servis « en parallèle » — c’est tout l’enjeu du chapitre 10.
Caractéristiques fondamentales.
43
CHAPITRE 7. L’ARCHITECTURE CLIENT-SERVEUR 44
Remarque
L’architecture n-tiers et les microservices généralisent ce découpage : la couche métier est éclatée en
services indépendants (catalogue, commandes, paiement. . . ), chacun étant à la fois serveur pour les
uns et client pour les autres, souvent derrière une passerelle d’API. On retrouve à chaque liaison le
même schéma élémentaire client-serveur sur socket — d’où l’importance de bien le maîtriser.
Dans un réseau pair-à-pair, chaque nœud est simultanément client et serveur (ex. BitTorrent, blockchain).
Avantages : pas de point central, montée en charge naturelle. Inconvénients : découverte des pairs,
cohérence et sécurité plus complexes. La plupart des systèmes réels sont hybrides (ex. : annuaire central +
échanges directs).
C’est LA question qui prépare les chapitres suivants : que fait le serveur quand un deuxième client se
présente alors qu’il s’occupe du premier ?
Serveur itératif
temps
Serveur concurrent
client 1
client 2
client 3
temps
Figure 7.3: Itératif : les clients sont servis en séquence. Concurrent : les services se chevauchent.
Imaginez un serveur de chat itératif : tant que l’utilisateur A est connecté (potentiellement des heures),
B ne peut même pas se connecter. Un chat, un serveur web, un serveur de jeu sont par nature
concurrents. C’est pourquoi nous étudierons les threads (chapitre 9) avant de construire le serveur
multi-clients (chapitre 10).
Au-dessus de TCP, client et serveur doivent convenir d’un protocole applicatif : format des messages,
vocabulaire des commandes, séquences autorisées, gestion des erreurs. Même pour un mini-projet, écrivez
ce contrat avant de coder.
À retenir
• Le serveur attend sur (IP, port) connus ; le client initie depuis un port éphémère.
• 3-tiers = présentation / métier / données ; les microservices généralisent le modèle.
• Serveur itératif = un client à la fois ; serveur concurrent = un thread (ou équivalent) par client.
• Toujours définir le protocole applicatif (messages, commandes, erreurs) avant de coder.
a) Le téléphone (navigateur/appli) est client ; le portail web est serveur (lui-même client de sa
base de données).
b) Le service « commande » est client du service « stock » (qui est serveur), puis client de Post-
greSQL (serveur). Un même processus cumule donc les deux rôles selon la liaison considérée.
c) Les deux applications des utilisateurs sont clientes du serveur de messagerie ; les messages
transitent par lui (A → serveur → B). Le serveur est le seul à avoir une adresse stable et joignable
— c’est pour cela que les utilisateurs derrière NAT peuvent communiquer.
Concevez le protocole applicatif texte d’un serveur de gestion de tâches (todo) permettant : ajouter
une tâche, lister les tâches, marquer une tâche comme faite, quitter. Donnez la grammaire des requêtes,
les réponses (succès et erreur), et un exemple de session complète.
Requêtes (une ligne, mots séparés par des espaces) : ADD <description> ; LIST ; DONE <id>
; QUIT.
Réponses : OK <info> en cas de succès ; ERR <code> <message> sinon (codes : 400
syntaxe, 404 id inconnu). Pour LIST : OK <n> suivi de n lignes <id> <[X] ou [ ]>
<description>.
Session exemple :
Client : ADD Réviser le chapitre TCP
Serveur : OK 1
Client : LIST
Serveur : OK 1 puis 1 [ ] Réviser le chapitre TCP
Client : DONE 1
Serveur : OK tache 1 terminee
Client : DONE 9
Serveur : ERR 404 id inconnu
Client : QUIT
Serveur : OK au revoir (puis fermeture de la connexion)
L’essentiel : réponses toujours préfixées par un statut analysable par le client, et toute erreur explicite
— un protocole sans cas d’erreur n’est pas un protocole.
persistantes (WebSocket sur TCP) — car le chat exige que le serveur pousse les messages, ce
que le simple cycle requête/réponse HTTP ne fait pas.
• Données : base relationnelle pour cours/devoirs/notes ; le stockage des fichiers de devoirs sur
un service d’objets ; éventuellement un cache (Redis) pour les lectures intensives et la diffusion
des messages de chat entre instances.
Transport du chat : TCP (via WebSocket) — la perte ou le désordre de messages de chat est inacceptable
; UDP serait réservé à de l’audio/vidéo temps réel. On retrouve ici, à l’échelle industrielle, exactement
le serveur de chat multi-utilisateurs que nous programmerons au chapitre 11.
Nous avons vu dans les chapitres précédents comment deux processus situés sur des machines différentes
(ou sur la même machine) peuvent se repérer grâce au triplet adresse IP + protocole + numéro de port. Il
est temps de passer à la pratique : dans ce chapitre, nous écrivons nos premiers programmes réseau en Java
à l’aide de l’API socket.
Une socket est un objet fourni par le système d’exploitation (et exposé par le langage de programmation)
qui représente une extrémité d’une communication réseau. Le programme écrit dans la socket pour
envoyer des données et lit dans la socket pour en recevoir, exactement comme il le ferait avec un
fichier.
Remarque
La grande force du modèle socket est son abstraction : une fois la connexion établie, envoyer un mes-
sage à un programme situé à l’autre bout du monde se résume à écrire dans un flux (OutputStream),
comme dans un fichier local. Toute la complexité (encapsulation, routage, retransmission TCP. . . ) est
gérée par les couches inférieures, étudiées au chapitre 3 si l’on dispose de cette référence, et par le
système d’exploitation.
49
CHAPITRE 8. PROGRAMMATION SOCKET EN JAVA 50
Une communication TCP fait toujours intervenir deux rôles asymétriques (voir le chapitre sur l’architecture
client-serveur) :
• le serveur crée une ServerSocket liée à un port connu, puis attend les connexions avec
accept() ;
• le client crée une Socket en indiquant l’adresse IP et le port du serveur : c’est lui qui initie la
connexion (le fameux three-way handshake du chapitre transport).
SERVEUR CLIENT
new ServerSocket(5000)
connexio
accept() (bloquant) n (SYN. .
.)
new Socket("ip",5000)
échange de données
lecture / écriture (flux) écriture / lecture (flux)
close() close()
Attention
La méthode accept() est bloquante : le programme serveur reste suspendu sur cette ligne tant
qu’aucun client ne se connecte. De même, read() sur un flux d’entrée bloque tant qu’aucune donnée
n’arrive. Ce caractère bloquant est la raison principale pour laquelle nous aurons besoin des threads
(chapitre 9) pour gérer plusieurs clients.
Ces flux d’octets bruts sont presque toujours habillés par des classes plus confortables pour échanger
du texte ligne par ligne :
1 // Lecture de texte ligne par ligne
2 BufferedReader in = new BufferedReader(
3 new InputStreamReader([Link](), "UTF-8"));
4
5 // Écriture de texte avec envoi immédiat (autoflush = true)
6 PrintWriter out = new PrintWriter(
7 new OutputStreamWriter([Link](), "UTF-8"), true);
8
9 String ligne = [Link](); // lit jusqu’au ’\n’ (bloquant)
10 [Link]("Bonjour !"); // envoie une ligne terminée par ’\n’
Remarque
Le « Hello World » de la programmation réseau est le serveur d’écho : il renvoie au client tout ce que
celui-ci lui envoie.
1 import [Link].*;
2 import [Link].*;
3
4 public class ServeurEcho {
5 public static void main(String[] args) throws IOException {
6 final int PORT = 5000;
7
8 // 1) Création de la socket d’écoute liée au port 5000
9 try (ServerSocket serveur = new ServerSocket(PORT)) {
10 [Link]("Serveur en écoute sur le port " + PORT);
11
12 while (true) {
13 // 2) Attente d’un client (bloquant)
14 Socket client = [Link]();
15 [Link]("Client connecté : "
16 + [Link]().getHostAddress()
17 + ":" + [Link]());
18
19 // 3) Préparation des flux
20 BufferedReader in = new BufferedReader(
21 new InputStreamReader([Link](), "UTF-8"));
22 PrintWriter out = new PrintWriter(
23 new OutputStreamWriter([Link](), "UTF-8"),
24 true);
25
26 // 4) Boucle d’écho : on renvoie chaque ligne reçue
27 String ligne;
28 while ((ligne = [Link]()) != null) {
29 [Link]("Reçu : " + ligne);
30 [Link]("ECHO> " + ligne);
31 if ([Link]("bye")) break;
32 }
33
34 // 5) Fermeture de la connexion avec CE client
35 [Link]();
36 [Link]("Client déconnecté.");
37 }
38 }
39 }
40 }
1 import [Link].*;
2 import [Link].*;
3 import [Link];
4
5 public class ClientEcho {
6 public static void main(String[] args) throws IOException {
7 String hote = "[Link]"; // loopback : serveur sur le même PC
8 int port = 5000;
9
10 // 1) Connexion au serveur (three-way handshake)
11 try (Socket socket = new Socket(hote, port);
12 Scanner clavier = new Scanner([Link])) {
13
14 [Link]("Connecté au serveur " + hote + ":" + port);
15 [Link]("Port local choisi par l’OS : "
16 + [Link]());
17
18 BufferedReader in = new BufferedReader(
19 new InputStreamReader([Link](), "UTF-8"));
20 PrintWriter out = new PrintWriter(
21 new OutputStreamWriter([Link](), "UTF-8"),
22 true);
23
24 // 2) Boucle d’envoi : saisie clavier -> serveur -> affichage
25 while (true) {
26 [Link]("Vous > ");
27 String msg = [Link]();
28 [Link](msg); // envoi
29 String reponse = [Link](); // réception (bloquant)
30 [Link]("Serveur < " + reponse);
31 if ([Link]("bye")) break;
32 }
33 }
34 [Link]("Connexion fermée.");
35 }
36 }
On lance le serveur puis le client dans deux terminaux du même PC (scénario 2 du chapitre 6, via
[Link]) :
$ java ServeurEcho
Serveur en écoute sur le port 5000
Client connecté : [Link]:52614
Reçu : salut
Reçu : bye
Client déconnecté.
$ java ClientEcho
On retrouve exactement les notions du chapitre 6 : le serveur occupe le port 5000 (choisi par le
programmeur), le client reçoit un port éphémère 52614 (choisi par l’OS), et les deux processus ont
des PID différents bien qu’ils partagent la même adresse IP.
À retenir
• Côté serveur : ServerSocket(port) → accept() → Socket dédiée.
• Côté client : new Socket(ip, port) initie la connexion.
• On échange ensuite du texte via [Link]() et
[Link]() (avec autoflush !).
• accept() et readLine() sont bloquants.
• readLine() renvoie null quand l’autre extrémité ferme la connexion : c’est le signal de fin.
Attention
Trois exceptions reviennent sans cesse en programmation socket :
• BindException: Address already in use — le port demandé par
ServerSocket est déjà occupé par un autre processus (revoir la règle d’unicité du
chapitre 6) ;
• ConnectException: Connection refused — aucun processus n’écoute sur le cou-
ple (IP, port) visé : serveur non lancé, mauvais port, ou pare-feu ;
• SocketException: Connection reset — l’autre extrémité a fermé brutalement la
connexion (processus tué, câble débranché. . . ).
Avec UDP, il n’y a ni connexion, ni flux : on envoie des datagrammes indépendants, chacun portant
l’adresse complète du destinataire.
• DatagramSocket : la « boîte aux lettres » du processus. Le serveur la lie à un port fixe (new
DatagramSocket(6000)), le client laisse l’OS choisir (new DatagramSocket()).
• DatagramPacket : l’enveloppe. En émission, elle contient les données et l’adresse de destination
; en réception, elle est remplie avec les données et l’adresse de l’expéditeur.
1 import [Link].*;
2 import [Link];
3
4 public class ServeurUDP {
5 public static void main(String[] args) throws Exception {
6 try (DatagramSocket socket = new DatagramSocket(6000)) {
7 [Link]("Serveur UDP sur le port 6000");
8 byte[] tampon = new byte[1024];
9
10 while (true) {
11 // 1) Réception d’un datagramme (bloquant)
12 DatagramPacket requete =
13 new DatagramPacket(tampon, [Link]);
14 [Link](requete);
15
16 String texte = new String([Link](), 0,
17 [Link](), "UTF-8");
18 [Link]("Reçu de "
19 + [Link]().getHostAddress()
20 + ":" + [Link]() + " -> " + texte);
21
22 // 2) Réponse : on renvoie la date au MÊME expéditeur
23 byte[] reponse = ("Il est " + new Date()).getBytes("UTF-8");
24 DatagramPacket paquet = new DatagramPacket(
25 reponse, [Link],
26 [Link](), // IP de l’expéditeur
27 [Link]()); // port de l’expéditeur
28 [Link](paquet);
29 }
30 }
31 }
32 }
1 import [Link].*;
2
3 public class ClientUDP {
4 public static void main(String[] args) throws Exception {
5 try (DatagramSocket socket = new DatagramSocket()) { // port choisi par l’OS
6 InetAddress serveur = [Link]("[Link]");
7
8 // 1) Envoi de la requête
9 byte[] donnees = "Quelle heure est-il ?".getBytes("UTF-8");
10 [Link](new DatagramPacket(donnees, [Link],
11 serveur, 6000));
12
13 // 2) Réception de la réponse (avec délai maximal de 2 s)
14 [Link](2000);
15 byte[] tampon = new byte[1024];
16 DatagramPacket reponse =
17 new DatagramPacket(tampon, [Link]);
18 [Link](reponse);
19
20 [Link](new String([Link](), 0,
21 [Link](), "UTF-8"));
22 }
23 }
24 }
Remarque
setSoTimeout(2000) est indispensable côté client UDP : comme UDP ne garantit pas la livrai-
son, la réponse peut se perdre et receive() bloquerait alors pour toujours. Avec le timeout,
une SocketTimeoutException est levée après 2 secondes et le programme peut réessayer ou
abandonner proprement.
8.4 Exercices
1 import [Link].*;
2 import [Link].*;
3 import [Link];
4
5 public class ClientMeteo {
6 public static void main(String[] args) throws IOException {
7 try (Socket s = new Socket("[Link]", 7700);
8 Scanner clavier = new Scanner([Link])) {
9
10 BufferedReader in = new BufferedReader(
11 new InputStreamReader([Link](), "UTF-8"));
12 PrintWriter out = new PrintWriter(
13 new OutputStreamWriter([Link](), "UTF-8"), true);
14
15 [Link]("Ville ? ");
16 [Link]([Link]()); // requête
17 [Link]([Link]()); // réponse
18 } // fermeture automatique (try-with-resources)
19 }
20 }
Points clés : try-with-resources pour fermer la socket, autoflush du PrintWriter, une seule paire
requête/réponse puis fermeture.
1 import [Link].*;
2 import [Link].*;
3
4 public class ServeurCalcul {
5 public static void main(String[] args) throws IOException {
6 try (ServerSocket serveur = new ServerSocket(7000)) {
7 while (true) {
8 try (Socket c = [Link]()) {
9 BufferedReader in = new BufferedReader(
10 new InputStreamReader([Link](), "UTF-8"));
11 PrintWriter out = new PrintWriter(
12 new OutputStreamWriter([Link](),
13 "UTF-8"), true);
14 String ligne;
15 while ((ligne = [Link]()) != null) {
16 String[] t = [Link]().split("\\s+");
17 if (t[0].equalsIgnoreCase("BYE")) {
Le serveur valide chaque ligne, répond ERR en cas de commande mal formée (un bon protocole prévoit
toujours les cas d’erreur) et ferme la session sur BYE.
(a) TCP (Socket) : la moindre perte d’octet corromprait le fichier ; il faut la fiabilité et l’ordre
garantis par TCP.
(b) UDP (DatagramSocket) : une mesure perdue est remplacée par la suivante une seconde
plus tard ; inutile de payer le coût d’une connexion et des retransmissions.
(c) TCP : les messages ne doivent être ni perdus ni désordonnés ; c’est le choix que nous ferons
pour le chat du chapitre 11.
(d) UDP : requête/réponse courtes ; le client réessaie en cas de timeout (comme notre
setSoTimeout), ce qui est plus rapide qu’un handshake TCP complet.
Le serveur d’écho du chapitre 8 souffre d’un défaut majeur : il ne sait servir qu’un seul client à la fois. La
cause est claire : les appels accept() et readLine() sont bloquants, et notre programme n’a qu’un
seul fil d’exécution. Pour servir plusieurs clients simultanément, il faut donner plusieurs fils d’exécution à
notre processus : c’est tout l’objet des threads.
Un thread est une unité d’exécution à l’intérieur d’un processus. Tous les threads d’un même
processus partagent le même espace mémoire (mêmes variables, mêmes objets), mais chacun possède
sa propre pile d’appels et avance indépendamment des autres, sous le contrôle de l’ordonnanceur du
système d’exploitation.
Remarque
À ne pas confondre avec le chapitre 6 : deux processus sont isolés l’un de l’autre (chacun son PID, sa
mémoire, ses sockets), alors que deux threads du même processus partagent tout, ce qui rend leur
communication triviale. . . mais dangereuse (voir la section 9.3).
58
CHAPITRE 9. LES THREADS EN JAVA 59
Attention
On appelle toujours start(), jamais run() directement !
• start() demande à l’OS de créer un nouveau fil d’exécution qui exécutera run() en
parallèle ;
• run() appelé directement s’exécute. . . dans le thread courant, comme une méthode ordinaire :
aucun parallélisme !
() TERMINATED
un
der
start() fin
NEW sl
RUNNABLE
ee ré
p( ve
), il
wai
t(
), WAITING /
E/S
blo BLOCKED
qu
an
te
Les lignes de fichier-A et fichier-B s’entrelacent dans un ordre non déterministe : c’est la
signature de l’exécution concurrente.
Puisque les threads partagent la mémoire, deux threads peuvent modifier la même variable en même temps,
avec des résultats imprévisibles.
1 class Compteur {
2 private int valeur = 0;
3 public void incrementer() { valeur++; } // PAS atomique !
4 public int get() { return valeur; }
5 }
6 public class TestCompteur {
valeur++ cache en réalité trois opérations (lire, additionner, écrire). Si deux threads lisent la même
valeur avant d’écrire, un incrément est perdu.
Une section critique est un bloc de code qui manipule une ressource partagée et qui ne doit être
exécuté que par un seul thread à la fois. En Java, le mot-clé synchronized associe un verrou
(lock) à un objet : un seul thread peut détenir le verrou à un instant donné, les autres attendent.
1 class CompteurSur {
2 private int valeur = 0;
3 public synchronized void incrementer() { valeur++; }
4 public synchronized int get() { return valeur; }
5 }
6 // Variante : bloc synchronisé sur un objet précis
7 synchronized (listeClients) {
8 [Link](nouveauClient);
9 }
Attention
La synchronisation a un coût (les threads s’attendent) et un risque : l’interblocage (deadlock), où deux
threads attendent chacun le verrou détenu par l’autre. Règle pratique : garder les sections critiques les
plus courtes possible et toujours prendre les verrous dans le même ordre.
Plutôt que de synchroniser à la main, Java fournit des structures déjà sûres, très utiles pour nos serveurs :
Pourquoi ce chapitre au milieu d’un cours réseau ? Parce que le thread est la réponse au blocage des sockets
:
• un serveur peut dédier un thread par client : le thread principal boucle sur accept(), et chaque
client est servi en parallèle (chapitre 10) ;
• un client de chat doit lire et écrire en même temps : un thread écoute le serveur pendant que le
thread principal lit le clavier (chapitre 11).
À retenir
• Un thread est un fil d’exécution dans un processus ; les threads partagent la mémoire du
processus.
• On définit le travail dans run() (via Runnable) et on lance avec start().
• Toute donnée partagée entre threads doit être protégée : synchronized, collections concur-
rentes, classes atomiques.
• Threads + sockets = serveurs multi-clients et clients full-duplex.
9.5 Exercices
Quel est l’affichage (ou les affichages possibles) des deux programmes suivants ?
1 // Programme 1 // Programme 2
2 Thread t = new Thread(() -> Thread t = new Thread(() ->
3 [Link]("B")); [Link]("B"));
4 [Link](); [Link]();
5 [Link]("A"); [Link]("A");
Programme 1 : affiche toujours B puis A. run() est un simple appel de méthode exécuté séquen-
tiellement par le thread principal : aucun nouveau thread n’est créé.
Programme 2 : start() crée un vrai thread ; les deux affichages sont concurrents. Selon
l’ordonnanceur, on peut obtenir B A ou A B : le résultat est non déterministe.
1. Scénario d’entrelacement : T1 évalue solde >= 80 (vrai, solde = 100) ; avant que T1
n’écrive, T2 évalue aussi solde >= 80 (toujours vrai, solde encore 100). Les deux effectuent
alors la soustraction : 100 − 80 − 80 = −60. Le test et la modification ne forment pas une
opération atomique.
2. Il suffit de rendre la méthode atomique vis-à-vis du verrou de l’objet :
1 public synchronized void retirer(int montant) {
2 if (solde >= montant) solde -= montant;
3 }
Écrivez un programme dont le thread principal lit des commandes au clavier (quit pour sortir) pendant
qu’un second thread affiche l’heure toutes les 5 secondes. L’horloge doit s’arrêter automatiquement
quand le programme se termine.
1 import [Link];
2 import [Link];
3
4 public class Horloge {
5 public static void main(String[] args) {
6 Thread horloge = new Thread(() -> {
7 while (true) {
8 [Link]("[Horloge] " + new Date());
9 try { [Link](5000); }
10 catch (InterruptedException e) { return; }
11 }
12 });
13 [Link](true); // meurt avec le thread principal
14 [Link]();
15
16 Scanner sc = new Scanner([Link]);
17 while (![Link]().equalsIgnoreCase("quit")) { /* boucle */ }
18 [Link]("Au revoir !");
19 }
20 }
Le point clé est setDaemon(true) : sans lui, le thread horloge (boucle infinie) empêcherait la
JVM de se terminer après quit. C’est exactement le schéma « un thread pour le clavier, un thread
pour le reste » que nous réutiliserons dans le client de chat.
Dans un serveur de chat, tous les threads clients partagent une ArrayList<PrintWriter> pour
diffuser les messages. Quels problèmes peut poser ce partage et comment les résoudre (deux solutions)
?
Le serveur multi-utilisateurs
Nous disposons désormais de toutes les briques : les sockets (chapitre 8) pour communiquer, et les threads
(chapitre 9) pour exécuter plusieurs traitements en parallèle. Assemblons-les pour construire un serveur
concurrent, capable de servir des dizaines de clients simultanément — le modèle de tous les serveurs réels.
Le serveur d’écho du chapitre 8 est itératif : la boucle de service d’un client empêche de revenir sur
accept(). Pendant qu’Alice discute avec le serveur, Bob attend, parfois indéfiniment.
Remarque
Relisons cette figure avec les lunettes du chapitre 6 : il n’y a qu’un seul processus serveur (un seul
PID) et un seul port d’écoute (5000). Mais chaque connexion acceptée crée une socket distincte,
identifiée par son quadruplet (IP client, port client, IP serveur, 5000). C’est ce
quadruplet, et non le seul port 5000, qui permet à l’OS de remettre chaque segment au bon handler.
1 import [Link].*;
2 import [Link].*;
65
CHAPITRE 10. LE SERVEUR MULTI-UTILISATEURS 66
Figure 10.1: Serveur concurrent : un thread « handler » et une socket dédiée par client.
3
4 public class ClientHandler implements Runnable {
5 private final Socket socket;
6 private final int numero; // numéro du client (pour les logs)
7
8 public ClientHandler(Socket socket, int numero) {
9 [Link] = socket;
10 [Link] = numero;
11 }
12
13 @Override
14 public void run() {
15 String id = "client #" + numero + " ("
16 + [Link]().getHostAddress()
17 + ":" + [Link]() + ")";
18 [Link]("[+] " + id + " pris en charge par "
19 + [Link]().getName());
20 try (Socket s = socket; // fermeture garantie
21 BufferedReader in = new BufferedReader(
22 new InputStreamReader([Link](), "UTF-8"));
23 PrintWriter out = new PrintWriter(
24 new OutputStreamWriter([Link](), "UTF-8"),
25 true)) {
26
27 [Link]("Bienvenue, vous êtes le " + id);
28 String ligne;
29 while ((ligne = [Link]()) != null) {
30 if ([Link]("bye")) break;
31 [Link]("ECHO> " + ligne);
32 }
33 } catch (IOException e) {
34 [Link]("[!] Erreur avec " + id + " : "
35 + [Link]());
36 }
37 [Link]("[-] " + id + " déconnecté.");
38 }
39 }
Listing 10.1: [Link] — service d’un client dans son propre thread.
1 import [Link].*;
2 import [Link].*;
3
4 public class ServeurMulti {
5 public static void main(String[] args) throws IOException {
6 final int PORT = 5000;
7 int compteur = 0;
8
9 try (ServerSocket serveur = new ServerSocket(PORT)) {
10 [Link]("Serveur multi-clients sur le port " + PORT);
11
12 while (true) {
13 Socket socketClient = [Link](); // bloquant
14 compteur++;
15 // Un thread dédié par client : le main repart
16 // immédiatement sur accept()
17 Thread t = new Thread(
18 new ClientHandler(socketClient, compteur),
19 "handler-" + compteur);
20 [Link]();
21 }
22 }
23 }
24 }
$ java ServeurMulti
Serveur multi-clients sur le port 5000
[+] client #1 ([Link]:52614) pris en charge par handler-1
[+] client #2 ([Link]:52615) pris en charge par handler-2
[+] client #3 ([Link]:52890) pris en charge par handler-3
[-] client #2 ([Link]:52615) déconnecté.
Les trois clients dialoguent simultanément : le départ du client #2 n’affecte ni #1 ni #3. Chaque
handler vit et meurt indépendamment.
À retenir
Le squelette universel d’un serveur TCP concurrent :
1. ServerSocket liée au port de service ;
2. boucle infinie : Socket s = [Link]();
3. new Thread(new ClientHandler(s)).start();
4. toute la logique métier vit dans le run() du handler.
Créer un thread n’est pas gratuit (mémoire de pile, travail de l’OS). Avec 10 000 clients, 10 000 threads
satureraient la machine. Les serveurs professionnels utilisent un pool de threads : un nombre fixe de
threads ouvriers se partagent les clients.
1 import [Link].*;
2 import [Link].*;
3
4 public class ServeurPool {
5 public static void main(String[] args) throws Exception {
6 ExecutorService pool = [Link](20);
7 try (ServerSocket serveur = new ServerSocket(5000)) {
8 int n = 0;
9 while (true) {
10 Socket s = [Link]();
11 [Link](new ClientHandler(s, ++n));
12 }
13 }
14 }
15 }
Remarque
Avec newFixedThreadPool(20), au plus 20 clients sont servis en parallèle ; les suivants at-
tendent qu’un ouvrier se libère. C’est un compromis maîtrisé entre réactivité et consommation de
ressources. Les serveurs à très haute charge vont plus loin encore (entrées/sorties non bloquantes
[Link], threads virtuels de Java 21), mais le principe pédagogique reste celui de ce chapitre.
Dès que les clients doivent interagir (compteur global, liste des connectés, diffusion de messages. . . ), les
handlers partagent des données : tous les réflexes du chapitre 9 s’appliquent.
1 import [Link];
2
3 public class Statistiques {
4 private static final AtomicInteger connectes = new AtomicInteger(0);
5
6 public static int entrer() { return [Link](); }
7 public static int sortir() { return [Link](); }
8 public static int actuels() { return [Link](); }
9 }
10 // Dans [Link]() :
11 // début : [Link]();
12 // fin : [Link]();
AtomicInteger garantit des incréments atomiques sans synchronized explicite : aucun visiteur
Attention
Erreur classique : stocker les PrintWriter des clients dans une ArrayList ordinaire et la
parcourir depuis plusieurs handlers. On obtient des ConcurrentModificationException
aléatoires. Utiliser une collection synchronisée ou concurrente (voir l’exercice 9.4 et le chapitre
suivant).
10.5 Exercices
Sur la figure 10.1, le client B ([Link]:52615) envoie un segment TCP au serveur. Décrivez
le trajet complet du segment jusqu’au code Java qui le lira, en précisant le rôle du quadruplet de
connexion.
Un serveur itératif met en moyenne 4 secondes à servir un client. Un serveur concurrent (un thread par
client) sert chaque client en 4 secondes également, mais peut en traiter jusqu’à 50 en parallèle.
1. Combien de clients chaque serveur peut-il servir par minute (au mieux) ?
2. Dix clients arrivent en même temps. Quel est le temps d’attente du dernier client dans chaque
cas ?
connexion.
Le compteur est un AtomicInteger (donnée partagée entre handlers) et la décrémentation est dans
un finally : elle a lieu même si le client se déconnecte brutalement.
1. La connexion TCP du 21e client est bien acceptée (le handshake aboutit), mais sa tâche est
placée dans la file interne de l’ExecutorService : il ne recevra aucune réponse applicative
tant qu’un ouvrier ne se sera pas libéré. Du point de vue du client, le serveur « rame » mais ne
refuse pas la connexion.
2. Avantage : consommation de ressources bornée et prévisible (20 piles de threads au maximum),
protection naturelle contre un afflux massif ou une attaque par épuisement. Inconvénient : si
les 20 ouvriers sont monopolisés par des clients lents (ou malveillants), les nouveaux clients
attendent indéfiniment ; le dimensionnement du pool devient un paramètre critique.
Ce dernier chapitre est le couronnement du cours : nous construisons une application de chat complète,
où plusieurs utilisateurs connectés au même serveur échangent des messages en temps réel. Tous les
concepts vus jusqu’ici y trouvent leur place : IP et ports, processus, client-serveur, sockets TCP, threads et
synchronisation.
À retenir
1. Serveur : un thread par client (chapitre 10), plus une structure partagée et synchronisée qui
associe chaque pseudo à son PrintWriter — c’est elle qui rend la diffusion possible.
2. Diffusion : « envoyer à tous » = parcourir la map et écrire dans chaque flux. Ce parcours est une
section critique (chapitre 9).
3. Client : il doit pouvoir recevoir à tout moment, même pendant que l’utilisateur tape. Solution :
72
CHAPITRE 11. PROJET : UNE APPLICATION DE CHAT MULTI-UTILISATEURS 73
Map<String,PrintWriter> partagée
pseudo → flux de sortie
Alice Chaimae
Bob
deux threads — l’un lit le clavier et envoie, l’autre écoute le serveur et affiche.
Comme tout bon protocole (chapitre 7), le nôtre est défini avant d’écrire le code. Messages en texte, une
ligne = un message :
1 import [Link].*;
2 import [Link].*;
3 import [Link].*;
4 import [Link].*;
5
6 public class ServeurChat {
7
8 /** Map partagée : pseudo -> flux de sortie du client.
9 * ConcurrentHashMap : sûre pour les accès multi-threads. */
10 private static final ConcurrentHashMap<String, PrintWriter> clients
11 = new ConcurrentHashMap<>();
12
13 public static void main(String[] args) throws IOException {
14 final int PORT = 5555;
15 try (ServerSocket serveur = new ServerSocket(PORT)) {
16 [Link]("Serveur de chat sur le port " + PORT);
17 while (true) {
18 Socket socket = [Link]();
19 new Thread(new HandlerChat(socket)).start();
20 }
21 }
22 }
23
24 /** Diffuse un message à TOUS les clients connectés. */
25 static void diffuser(String message) {
26 [Link](message); // trace serveur
27 for (PrintWriter w : [Link]())
28 [Link](message);
29 }
30
31 /** Handler : gère un client du début à la fin. */
32 static class HandlerChat implements Runnable {
33 private final Socket socket;
34 private String pseudo;
35
36 HandlerChat(Socket socket) { [Link] = socket; }
37
38 @Override
39 public void run() {
40 try (Socket s = socket;
41 BufferedReader in = new BufferedReader(
42 new InputStreamReader([Link](), "UTF-8"));
43 PrintWriter out = new PrintWriter(
44 new OutputStreamWriter([Link](),
45 "UTF-8"), true)) {
46
47 // ---- 1) Négociation du pseudo (unique) ----
48 while (true) {
49 [Link]("PSEUDO?");
50 pseudo = [Link]();
51 if (pseudo == null) return; // client parti
52 pseudo = [Link]();
53 if (![Link]()
54 && [Link](pseudo, out) == null)
55 break; // pseudo accepté
56 [Link]("ERREUR pseudo déjà pris");
57 }
58 diffuser("*** " + pseudo + " a rejoint le chat ***");
59
60 // ---- 2) Boucle de réception / diffusion ----
61 String ligne;
62 while ((ligne = [Link]()) != null) {
63 if ([Link]("/quit")) break;
64 if ([Link]("/list")) {
65 [Link]("Connectés : "
Remarque
Le client a une particularité essentielle : il doit afficher les messages des autres pendant que l’utilisateur
tape. Un seul thread ne suffit pas, car readLine() sur le clavier et readLine() sur la socket sont
tous deux bloquants. D’où le schéma à deux threads :
écran clavier
réception envoi
Serveur de chat
1 import [Link].*;
2 import [Link].*;
3 import [Link];
4
5 public class ClientChat {
6 public static void main(String[] args) throws IOException {
7 String hote = ([Link] > 0) ? args[0] : "[Link]";
8 int port = 5555;
9
10 try (Socket socket = new Socket(hote, port);
11 Scanner clavier = new Scanner([Link])) {
12
13 BufferedReader in = new BufferedReader(
14 new InputStreamReader([Link](), "UTF-8"));
15 PrintWriter out = new PrintWriter(
16 new OutputStreamWriter([Link](),
17 "UTF-8"), true);
18
19 // ---- Thread d’écoute : socket -> écran ----
20 Thread ecoute = new Thread(() -> {
21 try {
22 String msg;
23 while ((msg = [Link]()) != null)
24 [Link](msg);
25 } catch (IOException e) { /* socket fermée */ }
26 [Link]("(connexion terminée)");
27 });
28 [Link](true); // meurt avec le programme
29 [Link]();
30
31 // ---- Thread principal : clavier -> socket ----
32 while ([Link]()) {
33 String ligne = [Link]();
34 [Link](ligne);
35 if ([Link]("/quit")) break;
36 }
37 }
38 [Link]("Au revoir !");
39 }
40 }
Trois utilisateurs, trois machines (ou trois terminaux du même PC via [Link] — les deux scénarios
du chapitre 6 fonctionnent à l’identique) :
$ java ServeurChat
Serveur de chat sur le port 5555
*** Alice a rejoint le chat ***
*** Bob a rejoint le chat ***
[Alice] Salut Bob !
[Bob] Salut ! Tu as fini le TP réseaux ?
*** Chaimae a rejoint le chat ***
[Chaimae] Bonjour à tous
*** Bob a quitté le chat ***
PSEUDO?
Alice
*** Alice a rejoint le chat ***
Salut Bob !
[Alice] Salut Bob !
[Bob] Salut ! Tu as fini le TP réseaux ?
/list
Connectés : Alice, Bob
Remarque
Alice reçoit aussi [Alice] Salut Bob ! : le serveur diffuse à tous, y compris l’auteur. C’est
un choix de protocole (l’écho confirme à l’auteur que son message est bien passé) ; l’exercice 11.2
propose la variante inverse.
11.7 Exercices
1. Pourquoi le client a-t-il besoin de deux threads alors que le handler côté serveur n’en utilise
qu’un seul par client ?
2. Que se passerait-il si la map clients était une HashMap ordinaire ?
3. Pourquoi le retrait du pseudo est-il placé dans un bloc finally ?
1. Le handler n’a qu’une source d’entrée à attendre : la socket de son client (quand il faut diffuser, il
écrit, opération non bloquante en pratique). Le client, lui, doit attendre deux sources bloquantes
à la fois : le clavier et la socket. Un seul thread bloqué sur le clavier ne verrait jamais arriver les
messages des autres — d’où un thread par source.
2. HashMap n’est pas thread-safe : une diffusion (parcours de la map) simultanée à une connexion
(put) ou un départ (remove) peut lever une ConcurrentModificationException,
perdre des destinataires, voire corrompre la structure interne. De plus, le test d’unicité du pseudo
ne serait plus atomique.
3. Pour garantir le nettoyage dans tous les cas de sortie : départ propre (/quit), fin de flux
(readLine() == null) ou exception (déconnexion brutale). Sans finally, un pseudo
resterait bloqué pour toujours après un crash du client, et les diffusions échoueraient sur son flux
mort.
Modifiez le serveur pour qu’un message ne soit pas renvoyé à son auteur. Écrivez la nouvelle méthode
de diffusion.
Côté client, on peut alors afficher localement « Moi > . . . » au moment de l’envoi pour conserver une
trace de ses propres messages.
Ajoutez la commande /mp <pseudo> <message> : le message n’est envoyé qu’au destinataire
(préfixé [privé de X]), avec un message d’erreur si le pseudo n’existe pas. Donnez le code à
insérer dans la boucle du handler.
s+", 3) limite le découpage à 3 morceaux pour préserver les espaces du message ; [Link]
sur la ConcurrentHashMap est sûr sans synchronisation supplémentaire ; l’émetteur reçoit une
confirmation (bon réflexe de protocole).
Conclusion du cours
Du bit qui circule sur un câble jusqu’au message « [Alice] Salut Bob ! » qui s’affiche sur trois écrans à la
fois, nous avons remonté toute la pile : topologies et supports, modèles en couches, adressage IP, transport
TCP/UDP, identification des processus par les ports, architecture client-serveur, et enfin la programmation
socket et les threads qui donnent vie à tout cela. Le chat de ce chapitre n’est qu’un point de départ : HTTP,
les API REST, les microservices ou les brokers de messages reposent exactement sur les mêmes fondations.
À vous de construire la suite !
Cette annexe regroupe les commandes indispensables pour observer et déboguer tout ce qui a été étudié
dans ce cours. Les colonnes Windows / Linux donnent les équivalents sur chaque système.
C:\> ipconfig
Carte Ethernet :
Adresse IPv4 . . . . . . . . . : [Link]
Masque de sous-réseau . . . . .: [Link]
Passerelle par défaut . . . . .: [Link]
La machine est dans le réseau [Link]/24 ; tout trafic hors de ce réseau passe par le routeur
[Link] (chapitre 4).
80
ANNEXE A. COMMANDES RÉSEAU UTILES 81
Commande Usage
ping <ip|nom> Teste l’accessibilité d’un hôte (ICMP) et mesure le RTT.
ping [Link] valide la pile TCP/IP locale.
tracert <ip> (Win) / Affiche la liste des routeurs traversés jusqu’à la destina-
traceroute (Linux) tion.
nslookup <nom> Interroge le DNS : nom de domaine → adresse IP.
telnet <ip> <port> / Teste si un port TCP est ouvert et permet de dialoguer
nc <ip> <port> « à la main » avec un serveur (idéal pour tester nos
serveurs des chapitres 8 à 11).
$ nc [Link] 5555
PSEUDO?
testeur
*** testeur a rejoint le chat ***
/list
Connectés : Alice, Bob, testeur
/quit
netcat se comporte comme un client TCP minimal : précieux pour valider un protocole applicatif
avant même d’écrire le client Java.
Commande Usage
netstat -ano (Windows) Liste connexions et ports en écoute avec le PID de
chaque processus.
tasklist | findstr Retrouve le nom du programme à partir du PID.
<PID>
ss -tunap (Linux) Équivalent moderne : sockets TCP/UDP, état, pro-
cessus.
lsof -i :5000 (Linux/ma- Quel processus occupe le port 5000 ?
cOS)
taskkill /PID <pid> /F Termine le processus qui bloque un port
(Win) / kill <pid> (BindException).
Le port 5000 est en écoute (LISTENING) par le processus [Link] de PID 4312 : c’est notre
ServeurMulti. Pour libérer le port : taskkill /PID 4312 /F.
Code Effet
new ServerSocket(p) Met le port TCP p en écoute.
[Link]() Attend (bloquant) et renvoie la Socket d’un
client.
new Socket(ip, p) Connexion TCP vers ip:p (handshake).
[Link]() / Flux de réception / d’émission.
getOutputStream()
new DatagramSocket(p) Boîte UDP liée au port p.
[Link](ms) Délai maximal d’attente d’une lecture.
new Thread(r).start() Lance [Link]() dans un nouveau thread.
Collections. Liste protégée pour partage entre threads.
synchronizedList(l)
new ConcurrentHashMap<>() Map thread-safe (diffusion du chat).
Trois séances de travaux pratiques, de difficulté croissante, pour mettre en œuvre le cours. Chaque TP
indique les prérequis, le travail demandé étape par étape, puis une solution complète commentée.
Prérequis : chapitres 4 et 6. Durée indicative : 1 h. Matériel : un PC, idéalement deux sur le même
réseau local.
1. Relevez l’adresse IP, le masque et la passerelle de votre machine. Déduisez-en l’adresse du réseau,
l’adresse de broadcast et le nombre d’hôtes possibles.
2. Vérifiez la pile locale avec ping [Link], puis testez la passerelle et une machine voisine.
3. Listez les ports TCP en écoute sur votre machine et identifiez le programme derrière chacun des trois
premiers.
4. Lancez le ServeurEcho du chapitre 8 et retrouvez-le dans la liste des ports. Connectez un client
et observez la connexion établie (ports des deux côtés).
5. Lancez un second ServeurEcho : notez l’erreur, expliquez-la et corrigez-la sans fermer le premier
serveur.
83
ANNEXE B. TRAVAUX PRATIQUES GUIDÉS 84
4. Après lancement : ligne TCP [Link]:5000 ... LISTENING <pid java>. Une fois
le client connecté, deux lignes ESTABLISHED apparaissent si tout est local : [Link]:5000
↔ [Link]:52614 vue des deux côtés — exactement la sortie analysée au chapitre 6.
5. BindException: Address already in use : le port 5000 est déjà lié au premier pro-
cessus. Correction sans fermer le premier : modifier la constante PORT (ex. 5001) ou passer le port
en argument du programme — deux serveurs identiques cohabitent alors sur la même IP grâce à des
ports distincts.
1 import [Link].*;
2 import [Link].*;
3
4 public class ServeurQuiz {
5 static final String[][] QA = {
6 {"Quel protocole garantit la livraison : TCP ou UDP ?", "TCP"},
7 {"Quelle adresse désigne la machine locale ?", "[Link]"},
8 {"Quel champ identifie le processus destinataire ?", "port"}
9 };
10 // État partagé entre tous les handlers
11 private static int record = 0;
12 private static String recordman = "personne";
13
14 /** Mise à jour atomique du record. */
15 static synchronized String majRecord(String pseudo, int score) {
16 if (score > record) { record = score; recordman = pseudo; }
17 return "Record actuel : " + record + "/" + [Link]
18 + " par " + recordman;
19 }
20
21 public static void main(String[] args) throws IOException {
22 try (ServerSocket srv = new ServerSocket(6500)) {
23 [Link]("Serveur quiz sur le port 6500");
24 while (true) {
25 Socket s = [Link]();
26 new Thread(() -> jouer(s)).start();
27 }
28 }
29 }
30
31 static void jouer(Socket socket) {
32 try (Socket s = socket;
33 BufferedReader in = new BufferedReader(
34 new InputStreamReader([Link](), "UTF-8"));
35 PrintWriter out = new PrintWriter(
36 new OutputStreamWriter([Link](), "UTF-8"),
37 true)) {
38
39 [Link]("Bienvenue au quiz ! Votre pseudo ?");
40 String pseudo = [Link]();
41 if (pseudo == null) return;
42
43 int score = 0;
44 for (String[] qa : QA) {
45 [Link]("QUESTION : " + qa[0]);
46 String rep = [Link]();
47 if (rep == null) return; // abandon
48 if ([Link]().equalsIgnoreCase(qa[1])) {
49 score++; [Link]("Bonne réponse !");
50 } else {
51 [Link]("Non, c’était : " + qa[1]);
52 }
53 }
54 [Link]("Score final : " + score + "/" + [Link]);
55 [Link](majRecord(pseudo, score));
56 } catch (IOException e) { /* client parti */ }
57 }
58 }
Points de correction. (1) Le couple (record, recordman) est une donnée partagée modifiée par
tous les handlers : la méthode majRecord est synchronized pour que la comparaison et la double
écriture forment une opération atomique — sans cela, deux scores simultanés pourraient produire un record
attribué au mauvais joueur. (2) Chaque readLine() est testé contre null : un client peut abandonner à
tout moment. (3) On peut tester sans écrire de client, avec nc localhost 6500 (annexe A).
2) /who — le handler doit mémoriser la socket de chacun. On remplace la map des clients par une
ConcurrentHashMap qui associe chaque pseudo à son HandlerChat (le handler garde socket et
out en attributs) :
1 } else if ([Link]("/who")) {
2 StringBuilder sb = new StringBuilder("Connectés :");
3 for ([Link]<String, HandlerChat> e : [Link]()) {
4 Socket so = [Link]().socket;
5 [Link]("\n ").append([Link]())
6 .append(" - ").append([Link]().getHostAddress())
7 .append(":").append([Link]());
8 }
9 [Link](sb);
10 }
Remarque : [Link]() puis l’insertion ne sont pas atomiques ; en toute rigueur, un 11e client
peut se glisser entre les deux. Pour un chat pédagogique c’est acceptable ; sinon, on protège l’admission
par un Semaphore(10).
4) Analyse — avec l’alternance stricte clavier/socket, le client ne lit la socket qu’après une saisie
clavier : les messages des autres n’apparaissent que lorsqu’on tape soi-même quelque chose. Pire, s’il
y a deux messages en attente, un seul est affiché par tour : le retard s’accumule. Et réciproquement,
[Link]() bloque s’il n’y a rien à lire : impossible de taper deux messages de suite. Deux flux
d’événements indépendants exigent deux threads (ou des entrées/sorties non bloquantes) — c’est la leçon
centrale du chapitre 11.
QCM de révision
Vingt questions couvrant tout le cours. Une seule bonne réponse par question ; le corrigé commenté se
trouve en fin d’annexe. Comptez 30 minutes.
C.1 Questions
Q3. Dans le modèle OSI, le routage des paquets est assuré par la couche :
(a) un processus (b) une carte réseau (c) un réseau logique (d) une session TCP
87
ANNEXE C. QCM DE RÉVISION 88
(a) SYN, ACK, FIN (b) SYN, SYN-ACK, ACK (c) ACK, SYN, ACK (d) HELLO, OK,
GO
(a) le transfert de fichiers (b) le DNS (c) le web (HTTP) (d) la messagerie (SMTP)
Q13. Deux programmes sur le même PC communiquent en TCP via [Link]. Qu’est-ce qui les
distingue dans la connexion ?
(a) leurs adresses IP (b) leurs adresses MAC (c) leurs numéros de port (d) rien, c’est
impossible
Q20. Dans le client de chat, le second thread est indispensable parce que :
Q1. (b) Tout le trafic transite par le centre (switch) : sa panne isole tous les hôtes — c’est le point faible
de l’étoile (chapitre 2).
Q3. (b) La couche 3 (réseau/IP) choisit le chemin des paquets ; la couche 2 ne gère que le lien local.
Q4. (b) Chaque couche ajoute son en-tête en descendant la pile à l’émission, et le retire à la réception
(chapitre 3).
Q5. (b) L’adresse MAC est gravée dans la carte réseau ; le processus est identifié par le port, le réseau
logique par l’IP.
Q6. (b) /22 ⇒ blocs de 4 dans le 3e octet : le réseau [Link]/22 couvre [Link] à
[Link], dernier = broadcast.
Q8. (c) Loopback : les paquets ne quittent jamais la machine (chapitre 6).
Q9. (b) SYN du client, SYN-ACK du serveur, ACK du client (chapitre 5).
Q10. (b) DNS : requête/réponse courtes, retransmission gérée par le client ; les trois autres exigent la
fiabilité de TCP.
Q12. (c) Le PID est un identifiant purement local attribué par l’OS ; il ne circule jamais dans les paquets —
c’est le port qui sert d’« adresse » du processus vis-à-vis du réseau.
Q13. (c) Même IP ([Link]) de part et d’autre : seuls les ports (écoute fixe d’un côté, éphémère de
l’autre) distinguent les deux processus dans le quadruplet.
Q14. (b) Un port ne peut être mis en écoute que par un seul processus ; identifier l’occupant avec netstat
-ano.
Q15. (b) Le serveur attend, le client initie : asymétrie fondamentale du modèle (chapitre 7).
Q16. (b) accept() bloque jusqu’à l’arrivée d’un client et renvoie la socket dédiée.
Q17. (b) null signale la fin du flux : l’autre côté a fermé proprement sa socket. Une ligne vide renvoie
"", pas null.
Q18. (b) start() crée le fil d’exécution ; run() appelé directement s’exécute séquentiellement
(chapitre 9).
Q19. (b) synchronized pose un verrou d’exclusion mutuelle sur la section critique.
Q20. (b) Bloqué sur le clavier, un thread unique ne verrait jamais les messages entrants ; un thread par
source bloquante (chapitre 11).
Remarque
Barème indicatif : 18–20 : excellente maîtrise ; 14–17 : solide, revoir les questions manquées ;
10–13 : relire les chapitres concernés ; < 10 : reprendre le cours chapitre par chapitre en refaisant les
exercices.
Glossaire
Adresse IP
Identifiant logique d’une interface réseau (32 bits en IPv4, notés en décimal pointé : [Link]).
Elle localise la machine dans l’inter-réseau et peut changer (DHCP), contrairement à l’adresse MAC.
Adresse MAC
Identifiant physique d’une carte réseau (48 bits, ex. A4:5E:60:1B:2C:3D), unique au monde et
utilisé par la couche liaison pour la livraison sur le réseau local.
Broadcast
Émission d’un message vers toutes les machines d’un réseau. Désigne aussi la dernière adresse d’un
réseau IP (tous les bits hôte à 1) — et, dans notre chat, la diffusion d’un message à tous les clients.
CIDR
Classless Inter-Domain Routing : notation adresse/n où n est le nombre de bits du préfixe réseau
(ex. [Link]/8).
Client
Processus qui initie une connexion vers un serveur pour demander un service. Son port local est
généralement éphémère, choisi par l’OS.
Datagramme
Unité de données UDP (et, par extension, paquet IP) : message autonome, envoyé sans connexion ni
garantie de livraison.
DHCP
Protocole d’attribution automatique de la configuration IP (adresse, masque, passerelle, DNS) via
l’échange Discover, Offer, Request, Ack.
DNS Domain Name System : annuaire distribué qui traduit les noms de domaine ([Link])
en adresses IP.
Encapsulation
Ajout, à chaque couche traversée en émission, d’un en-tête propre à la couche autour des données
reçues de la couche supérieure ; opération inverse (désencapsulation) à la réception.
Ethernet
Technologie dominante des réseaux locaux filaires (couches 1–2), normalisée IEEE 802.3.
Handler
Dans un serveur concurrent, objet Runnable exécuté dans un thread dédié et chargé de toute la
91
ANNEXE D. GLOSSAIRE 92
Interblocage (deadlock)
Situation où plusieurs threads s’attendent mutuellement (chacun détient un verrou que l’autre
demande) : le programme se fige. Prévention : prendre les verrous toujours dans le même ordre.
LAN / WAN
Local / Wide Area Network : réseau local (bâtiment, campus) / réseau étendu (échelle d’un pays ou
du monde, comme Internet).
Loopback (bouclage)
Interface virtuelle [Link] (nom localhost) : tout paquet qui lui est destiné reste dans la
machine. Indispensable pour tester un client et un serveur sur le même PC.
Masque de sous-réseau
Suite de bits à 1 (partie réseau) puis à 0 (partie hôte) qui, combinée par ET logique avec une adresse
IP, donne l’adresse du réseau.
NAT Network Address Translation : mécanisme du routeur qui remplace les adresses privées internes par
son adresse publique, permettant à tout un LAN de partager une seule IP publique.
OSI (modèle)
Modèle de référence en 7 couches (physique, liaison, réseau, transport, session, présentation, applica-
tion) servant de cadre d’analyse ; TCP/IP en est la mise en œuvre pratique en 4 couches.
Paquet
PDU de la couche réseau : en-tête IP (adresses source et destination) + données. Par abus de langage,
désigne souvent toute unité de données réseau.
PID Process IDentifier : numéro attribué par le système d’exploitation à chaque processus. Purement
local : il ne figure dans aucun en-tête réseau ; la table de l’OS fait le lien port ↔ PID.
Port Nombre sur 16 bits (0–65535) identifiant, sur une machine donnée, le service ou le processus
destinataire d’un segment. Ports connus (0–1023), enregistrés (1024–49151), éphémères (49152–
65535).
Processus
Programme en cours d’exécution : code + mémoire + ressources + PID. Deux processus sont isolés
l’un de l’autre, même issus du même programme.
Protocole
Ensemble de règles (format des messages, ordre des échanges, gestion des erreurs) que deux entités
conviennent de respecter pour communiquer.
Quadruplet de connexion
(IP source, port source, IP destination, port destination) : identifie de façon unique une connexion
TCP et permet à l’OS d’aiguiller chaque segment vers la bonne socket.
Race condition
Bug de concurrence : le résultat dépend de l’entrelacement imprévisible des threads accédant à une
donnée partagée. Remède : synchronisation ou structures atomiques.
Routeur
Équipement de couche 3 qui interconnecte des réseaux IP différents et choisit le chemin des paquets
(routage).
Segment
PDU de la couche transport (TCP) : en-tête (ports, numéros de séquence. . . ) + données.
Serveur
Processus qui attend les connexions sur un port connu et rend un service aux clients. Itératif (un
client à la fois) ou concurrent (plusieurs en parallèle, via des threads).
Socket
Point d’extrémité d’une communication réseau, vu par le programme comme un descripteur dans
lequel on lit et on écrit. En Java : Socket, ServerSocket (TCP), DatagramSocket (UDP).
Switch (commutateur)
Équipement de couche 2 qui relie les machines d’un même réseau local et achemine les trames vers
le bon port grâce aux adresses MAC.
Synchronisation
Coordination de threads accédant à des ressources partagées, par verrous (synchronized), collec-
tions concurrentes ou classes atomiques, afin d’éviter les race conditions.
TCP Transmission Control Protocol : transport fiable, ordonné et orienté connexion (handshake, accusés
de réception, retransmissions). Support de HTTP, du courrier, de notre chat. . .
Thread
Fil d’exécution à l’intérieur d’un processus ; les threads d’un même processus partagent sa mémoire
mais s’exécutent indépendamment.
Three-way handshake
Établissement d’une connexion TCP en trois segments : SYN (client), SYN-ACK (serveur), ACK
(client).
Trame
PDU de la couche liaison : en-tête (adresses MAC) + paquet IP + contrôle d’erreur, transmise sur le
support physique.
UDP
User Datagram Protocol : transport minimal, sans connexion ni garantie, mais léger et rapide —
adapté au DNS, à la voix, aux jeux en temps réel.