provisoire

Transcription

provisoire
Système et réseau (partie réseau)
provisoire
Christophe Fouqueré ∗
1
Généralités
Un réseau vise à fournir les moyens matériels et logiciels pour faire communiquer et permettre l’échange
d’informations entre plusieurs équipements ou machines informatiques de manière souple et fiable. Le réseau
(network) comprend l’ensemble des équipements. Les réseaux sont omniprésents : Téléphone, Internet, capteurs,
Réseaux transactionnels bancaires, TV, etc.
On distingue parmi les nœuds réseaux :
– Noeud terminal (hôte) : PC, station de travail, imprimante, téléphone filaire, sans fil (DECT, GSM,
UMTS, WiFi, bluetooth), fax, etc.
– Nœud intermédiaire : noeud de commutation (switch, PABX), noeud d’interconnexion (routeur, passerelle)
Les informations à échanger peuvent être de nature multiple :
– Données pures : Parole (conversation téléphonique), Musique (fichiers mp3 ou wav) Données informatiques
(Web, mail etc.), Images fixes, Séquences vidéo, Combinaisons de ces différents médias (information
multimédia)
– Données de signalisation : Messages protocolaires échangés entre les nœuds pour assurer le fonctionnement
du réseau et l’envoi correct des données pures. Exemples : Téléphone : Tonalité, Messages Web : HTTP,
Tarification, Gestion du réseau (e.g. détecter une panne, mesurer l’atténuation du signal..)
1.1
Modèle OSI
La transmission des informations est régi par un ensemble de protocoles correspondant aux différents niveaux
de fonctionnalités. Le modèle en couche simplifie la description globale et permet l’encapsulation des mécanismes.
Le modèle OSI est construit en 7 couches, chaque couche s’appuyant sur la précédente, et céractérisée par un
niveau de fonctionnalité :
– Couche physique : est responsable de la transmission des bits sur un circuit de communication, spécifie
les connecteurs physiques, détermine les caractéristiques électriques des circuits, définit des procédures
d’utilisation des connexions physiques.
– Couche liaison de données : est responsable de la transmission fiable de trames sur une connexion
physique, contrôle d’accès au support, contrôle de flux, contrôle d’erreur.
– Couche réseau : est responsable du transfert de données à travers le réseau : adressage, routage.
– Couche transport : est responsable du transfert de bout en bout, avec fiabilité et efficacité : contrôle de
congestion et reprise sur erreur.
– Couche session : est responsable des mécanismes nécessaires à la gestion d’une session : organisation du
dialogue entre 2 processus, synchronisation du dialogue, établissement et libération d’une session.
– Couche présentation : est responsable de la représentation des données échangées entre applications :
traduction des données, compression, cryptage.
– Couche application : fournit à l’usager des services pour réaliser une application répartie et pour accéder
à l’environnement de communication répartie (OSI, TCP/IP, ...).
∗ LIPN
– UMR 7030, Université Paris-Nord & CNRS, 93430 Villetaneuse, [email protected]
1
2
2.1
Cas TCP/IP
Modèle TCP/IP
couche application
FTP
couche transport
HTTP
TCP
SMTP
UDP
IP
couche IP
couche physique
DNS
WiFi
Ethernet
4G
Outre TCP et UDP, il existe d’autres protocoles de transport (ICMP, ...), il existe aussi de nombreuses
autres applications utilisant tel ou tel protocole de transport !
IP (Internet Process) est le point angulaire du réseau Internet. Il fait abstraction des caractéristiques des
sous réseaux, et réalise le transfert en datagramme.
L’adressage des machines permet d’identifier les noeuds du réseau. 2 formats : IPv4 sur 4 octets (p.e.
178.25.8.14), IPv6 sur 16 octets (p.e. fe80:0000:44ee:9cff:fe98:737c). Dans la suite nous ne parlerons que de IPv4.
Le routage (par les routeurs) consiste à trouver un chemin (une route) entre la source et la destination. Une
table de routage sur chaque routeur permet de préciser les routes possibles.
2
Le protocole IP définit l’unité de donnée transférée dans les interconnexions. Le datagramme est l’unité de
transfert de base dans un réseau internet. Le datagramme est constitué d’un en-tête et d’un champ de données.
IP définit les règles qui permettent la remise de paquets en mode non connecté. Le service offert par le protocole
IP est dit non fiable : remise de paquets non garantie, sans connexion (paquets traités indépendamment les uns
des autres), pour le mieux (best effort, les paquets ne sont pas éliminés sans raison).
Un paquet IP est lui-même encapsulé dans une trame qui sera envoyé par la couche physique. Par exemple :
En-tête
Ethernet
En-tête
TCP/UDP
En-tête
IP
Données de l’application
Bourrage
Ethernet
données
segment TCP ou datagramme UDP
paquet IP
trame Ethernet
2.2
Structure d’un paquet IPv4
0
4
version
8
hlen
16
type de service
24
31
longueur totale
flags
identifiant
TTL
19
protocole
fragment offset
somme de contrôle
adresse IP source
adresse IP destinataire
(options IP)
(bourrage)
(données)
– version : 4 pour IPv4, 6 pour IPv6
– hlen : longueur de l’en-tête en mots de 4 octets (=5 s’il n’y a pas d’option)
– type de service : indique comment le paquet doit être géré (sur 3 bits DTR) : D signifie délai court, T
signifie débit élevé et R signifie grande fiabilité.
– longueur totale : longueur totale du paquet en octets.
– identifiant : entier qui identifie le datagramme initial (utilisé pour la reconstitution à partir des fragments
qui ont tous la même valeur). Chaque fragment a une structure identique à celle du datagramme initial,
seuls les champs FLAGS et FRAGMENT OFFSET sont spécifiques.
– flags : contient un bit appelé ”do not fragment” (01X), un autre bit M appelé ”More fragments”. FLAGS
= 001 signifie d’autres fragments à suivre.
– fragment offset : indique le déplacement des données contenues dans le fragment par rapport au
datagramme initial. C’est un multiple de 8 octets ; la taille du fragment est donc également un multiple de
8 octets.
– TTL : (time to live) Ce champ indique en secondes, la durée maximale de transit du datagramme
sur l’internet. La machine qui émet le datagramme définit sa durée de vie. Les routeurs qui traitent
le datagramme doivent décrémenter sa durée de vie du nombre de secondes (1 au minimum) que le
datagramme a passé pendant son séjour dans le routeur ; lorsque celle-ci expire le datagramme est détruit
et un message d’erreur est renvoyé à l’émetteur.
– protocole : identifiant du protocole de niveau supérieur dont le message est véhiculé dans le champ
données du paquet : 6 = TCP, 17 = UDP, 1 = ICMP.
3
– somme de contrôle : Ce champ permet de détecter les erreurs survenant dans l’en-tête du datagramme,
et par conséquent l’intégrité du datagramme. Le total de contrôle d’IP porte sur l’en-tête du datagramme
et non sur les données véhiculées. Lors du calcul, Ce champ est supposé contenir la valeur 0.
– options : champ facultatif et de longueur variable. Les options concernent essentiellement des fonctionnalités de mise au point. Rarement utilisé, car ralenti les routeurs.
2.3
Fragmentation
Un paquet initial peut être fragmenté si le réseau dans lequel il doit passer n’accepte pas cette taille de
datagramme. Un lien entre 2 machines est caractérisé par une taille maximale de datagramme (MTU : Maximal
Transfer Unite). Le réassemblage ne se fait qu’au niveau du nœud terminal. Il n’est en effet pas certain que tous
les fragments passent par le même chemin. Le destinataire final reconstitue le datagramme initial à partir de
l’ensemble des fragments reçus. La taille de ces fragments correspond au plus petit MTU emprunté sur le réseau.
Si un seul des fragments est perdu, le datagramme initial est considéré comme perdu : la probabilité de perte
d’un datagramme augmente donc avec la fragmentation.
Réseau 1
MTU=1500
Données 1400 octets
En-tête datagramme
EF1 et EF2 ont le bit More (M)
positionné.
Le déplacement (depl) est
relatif au datagramme initial.
R1
Réseau 2
MTU=620
EF1
600 octets
R2
Réseau 3
MTU=1500
EF1
600 octets
EF2 600 octets
EF2 600 octets
EF3 200 oct.
EF3 200 oct.
En-tête fragments: M=0; depl=1200
En-tête fragments: M=1; depl=600
En-tête fragments: M=1; depl=00
2.4
Adressage IP : généralités et classes IP
Dans un réseau une machine est identifiée
– soit par un (ou plusieurs) nom logique : par exemple www.google.com ou smtp.free.fr,
– soit par une (ou plusieurs) adresse IP : par exemple 192.168.0.13
Une carte de communication est identifiée par une adresse physique : par exemple (adresse MAC) 00 :1b :63 :bd :30 :a8.
On mémorise facilement les noms de machines, parfois leur adresses IP, mais jamais leur adresse physique.
Une base de données distribuée contient la traduction de noms de machines en adresses IP. Une partie de cette
base de donnée peut être hébergée par un serveur : le serveur DNS. La machine interroge cette base de données
grâce au protocole DNS.
A chaque interface est allouée une adresse IP. Un routeur ou une passerelle a plusieurs adresses IP : une par
réseau auquel il est connecté. Les adresses doivent être uniques sur le réseau Internet. Il existe des organismes
d’attribution d’adresses ou de plages d’adresses IP : e.g en France, l’AFNIC.
4
Une adresse IP (IP address) est sur 32 bits (en version 4) et peut s’écrire au format binaire ou au format
décimal (4 entiers séparés par des points). Une adresse IP contient :
– l’adresse du réseau (Network Identifier = Netid),
– l’adresse de l’hôte dans le réseau (Host Identifier = Hostid).
Afin de simplifier la création des réseaux (et pour définir des tailles de réseaux différentes), l’adressage IP est
décomposé selon les bits de poids le plus fort :
0
8
16
Classe A : 0 net-id
Classe B : 1 0
Classe C : 1 1 0
24
(subnet+)
net-id
31
host-id
(subnet+)
net-id
host-id
(subnet+)
Classe D : 1 1 1 0
multicast-id
Classe E : 1 1 1 1
(réservé à l’expérimentation)
host-id
2 adresses particulières (qui ne peuvent pas servir à définir des adresses particulières de machines ou de
réseaux) :
– adresse de réseau : mise à 0 des bits host-id (et subnet). Par exemple, 128.8.0.0 est une adresse réseau de
classe B.
– adresse de broadcast réseau : mise à 1 des bits host-id (et subnet). Par exemple, 128.8.255.255 est une
adresse broadcast de classe B : toutes les machines sur le réseau 128.8.0.0 seront destinataires du paquet.
De plus il existe des adresses à signification particulière :
– 127.x.x.x correspond au loopback. Par exemple 127.0.0.1 définit la boucle locale sur l’interface réseau
– 3 plages d’adresses sont dites privées : elles ne sont pas visibles sur le réseau Internet (les paquets IP ne
sont pas tranférés par les routeurs) :
– 10.0.0.0 à 10.255.255.255
– 172.16.0.0 à 172.31.255.255
– 192.168.0.0 à 192.168.255.255
Un routeur effectue la liaison entre deux réseaux différents, un pont effectue la liaison entre deux couches
physiques différentes (par exemple ethernet et wifi) pour le même réseau IP.
Pour résumer :
– Classe A [1.x.x.x ; 126.x.x.x], 27 - 2 = 126 réseaux, 224 - 2 = 16,7 millions d’hôtes / réseau
– Classe B [128.x.x.x ; 191.x.x.x], 214 = 16 384 réseaux, 216 - 2 = 65534 hôtes / réseau
– Classe C [192.x.x. ; 223.x.x.x], 221 = 2 millions de réseaux, 28 - 2 = 254 hôtes / réseau
2.5
Adressage IP : sous-réseau
Problème des classes A/B/C : Les adresses IP sont une ressource rare, qu’on ne peut pas gaspiller. Le champ
IdHost peut servir à identifier X hôtes, mais cette plage d’adresse n’est pas pleinement utilisée sur des réseaux
de petite taille. Ex : une adresse de classe B a assez de place pour 65K hôtes, même si il n’y a que 2K hôtes dans
ce réseau. Le mécanisme CIDR (Classless InterDomain Routing) est une solution à ce problème. Il consiste à
découper le champ machine hôte en deux sous champs : sous-réseau, machine hôte.
Un masque est donné comme une suite de bits à 1 suivi d’une suite de bits à 0. Il permet de ”retrouver” la
paire (adresse réseau, adresse sous-réseau) en effectuant une opération ’AND’ entre l’adresse IP et le masque. Le
nombre de bits du masque peut aussi être donné sous la forme ’/xx’ où xx est le nombre de nits à 1.
Par exemple :
5
128
AND
8
52
/20
108
adresse IP (décimal)
10000000000010000011010001101100
adresse IP (binaire)
11111111111111111111000000000000
masque de sous-réseau
masque sur 20 bits
128
=
8
0
10000000000010000011000000000000
réseau
AND
48
adresse réseau et sous-réseau
sous-réseau
00000000000000000000111111111111
complément du masque
masque complémentaire sur 12 bits
0
=
0
4
108
00000000000000000000010001101100
identifiant machine dans le sous-réseau
id. machine
3
Communication par sockets
Le principe des sockets a été introduit dans la distribution de Berkeley du système d’exploitation Unix,
c’est la raison pour laquelle on parle de sockets BSD (Berkeley Software Distribution). Il s’agit d’un modèle
permettant la communication inter processus (Application logicielle) aussi bien sur une même machine qu’à
travers un réseau TCP/IP. On distingue deux modes de communication :
– le mode connecté, utilisant le protocole TCP
– le mode non connecté, utilisant le protocole UDP
Les sockets se situent juste au-dessus de la couche transport du modéle OSI (protocoles TCP et UDP). Ce
mécanisme utilise les services de la couche réseau (Protocole IP/ARP).
Telnet
FTP
SMTP
DNS
SOCKET
TCP
UDP
IP
ARPANET
Packet#
Radio
SATNET
LAN
Dans ce mode de communication une connexion durable est établie entre les deux processus de la façon
suivante : le processus ‘serveur’ est en attente de connexions de la part de un ou plusieurs processus distants
‘clients’.
6
socket()
connect()
socket()
Etablissement de
la connexion
bind()
listen()
Côté Client :
– crée une socket
– se connecte au serveur
– lit et écrit dans la socket
– ferme la socket
write()
accept()
read()
Transfert de
données
write()
Côté Serveur :
– crée une socket
– associe une adresse à la socket
– se met à l’écoute
– accepte une connexion entrante
– lit et écrit sur la socket
– ferme la socket
read()
close()
close()
Un socket est une structure similaire à un descripteur de fichier, il est référencé par un indice (un entier)
dans la table des sockets. Les opérations de connexions (connect et bind) associent au socket une entrée dans la
table des ports. La table des ports est gérée par le système d’exploitation, un port est identifié par son indice
dans la table (un entier sur 2 octets). Donc l’adresse d’un processus (client comme serveur) est donnée par
l’adresse IP de la machine et le numéro de port sur la machine. Le mécanisme est alors le suivant :
– Attente de connexions du serveur sur la paire (IP-serveur,port-serveur)
– Lorsque le client exécute la fonction connect() : envoi par le client de sa paire (IP-client,port-client) vers
l’adresse (IP-serveur,port-serveur)
– La fonction accept() du serveur est bloquante pour le processus serveur tant que le serveur n’a pas reçu de
demande d’un client. Quand il reçoit une demande de connexion, donc une paire (IP-client,port-client), le
système utilise un nouveau port libre dans sa table des ports et, s’il en trouve, envoi la paire (IP-serveur,
nouveau port-serveur) au client, i.e. à l’adresse (IP-client,port-client). La fonction accept() retourne le
nouveau port serveur et le processus serveur continue son exécution. Ainsi le port-serveur initial est
utilisable pour attendre des connexions d’autres clients.
Sous Unix les numéros de port < 1024 sont réservés à l’administrateur (root). Exemple de numéros réservés :
– FTP : port 21/TCP
– TELNET : port 23/TCP
La liste des numéros de ports et de leur services se trouvent dans le fichier ”/etc/services”.
3.1
Fonctions système (langage C) pour TCP
#include <sys/types.h>
#include <sys/socket.h>
• int socket(int domaine, int type, int protocole) ;
crée une socket et retourne :
– -1 si erreur lors de la création
– son identifiant de socket sinon.
Domaine
– domaine de communication pour le dialogue
– séléctionne la famille de protocole à utiliser
– valeurs définies dans le fichier <sys/socket> :
– AF UNIX, AF LOCAL = communication locale
– AF INET = communication IPv4 protocole internet
– AF INET6 = communication IPv6 protocole internet
– AF IPX = IPX protocole Novell
– AF APPLETALK = AppleTalk protocole Apple
– etc.
Type
– fixe la sémantique des données (intégrité des données, type de flux de données, ordre de délivrance)
– valeurs définies dans le fichier <sys/socket> :
– SOCK STREAM = flux d’octets full-duplex, mode connecté (garantit l’intégrité des données). Opération de
lecture/écriture : read/write ou send/recv.
7
– SOCK RAW = Accès direct aux données du réseau
– SOCK DGRAM, SOCK RDM, SOCK SEGPACKET : mode non connecté (pas de garantie d’intégrité des données).
Opération de lecture/écriture : sendto/recvfrom.
Protocole
– le protocole à employer sur la socket
– normalement, un seul protocole par type de socket
– valeurs définies dans le fichier <sys/socket> :
– 0 : le système choisit le protocole
– IPPROTO UDP pour l’UDP (SOCK DGRAM, SOCK RDM, SOCK SEGPACKET)
– IPPROTO TCP pour le TCP (SOCK STREAM)
– etc.
• int bind(int sock, struct sockaddr * adr, socklen t size)
Associe l’adresse locale (IP+port) adr au socket sock. Le paramètre size est la taille de la structure adr.
Format générique (pour tout domaine) :
struct sockaddr {
short sa family ; /* domaine AF UNIX, AF INET */
char sa data[14] ; /* adresse */
}
Format Internet :
#include <netinet/in.h>
struct in addr { u long s addr ; } ; /* adresse IP au format condensé : entier long non signé */
struct sockaddr in {
short sin family ; /* domaine AF INET */
u short sin port ; /* port */
struct in addr sin addr ; /* adresse IP */
char sin zero[8] ; /* 8 caractères nuls */
};
– Exemple de numéro de port fixé par l’utilisateur :
struct sockaddr in adr ;
adr.sin family = AF INET ; /* réseau IP version 4 */
adr.sin port = 4536 ;
– Numéro de port alloué aléatoirement par le système :
adr.sin port = 0 ;
La représentation des entiers varie d’une machine à l’autre. Par exemple, un entier court non signé, u short
int i = 2231 ; (0x08B7 en représentation hexadécimale) est codé sur deux octets. Sur une machine big-endian,
l’octet de plus fort poids (08) est enregistré à l’adresse mémoire la plus petite, l’octet de poids inférieur (B7)
est enregistré à l’adresse mémoire suivante. Si dans cette machine l’espace mémoire est incrémenté de gauche à
droite alors i sera enregistré 08B7. Sur une machine little-endian, l’octet de plus faible poids (B7) est enregistré
à l’adresse mémoire la plus petite, l’octet de poids fort (08) est enregistré à l’adresse mémoire suivante. Si dans
cette machine l’espace mémoire est incrémenté de gauche à droite alors i sera enregistré B708. Si i est transmis
depuis une machine little-endian A vers une machine big-endian B en écrivant les octets dans le sens de la
lecture de l’espace mémoire : de gauche à droite, alors nous aurons un problème d’inversion. En effet, B recevra
B708 ce qui correspond pour elle à 46856 en décimale.
Pour communiquer il faut donc s’accorder sur l’ordre des octets. Le protocole IP définit un standard, le
network byte order (l’ordre réseau standard) : big-endian. Pour rendre le code portable, il faut alors utiliser les
fonctions suivantes :
#include <arpa/inet.h>
net16value=htons(host16value) ; /* host-to-network-short */
host16value=ntohs(net16value) ; /* network-to-host-short */
net32value=htonl(host32value) ; /* host-to-network-long */
host32value=ntohl(net32value) ; /* network-to-host-long */
8
Les deux premières fonctions sont à utiliser pour affecter à la socket les numéros de port (entier court non
signé : 16 bits). Les suivantes, sont à utiliser pour les adresses IP (entier long non signé : 32 bits). Par exemple :
adr.sin port = htons(4536) ;
Comment mettre au bon format l’adresse IP d’une machine ?
– Premier cas : si on connaı̂t l’adresse IP de la machine distante alors on peut utiliser la fonction suivante :
int inet aton(const char *cp, struct in addr *in) ;
– Transforme une adresse IP donnée du format chaı̂ne de caractères ”a.b.c.d” au format condensée (entier
long non signé) dans la structure in addr.
– Retourne 0 en cas d’erreur. Sinon un entier > 0.
– Par exemple :
inet aton("192.168.50.12", &adr.sin addr) ;
– Inversement, on peut convertir l’adresse IP du format condensées vers le format chaı̂ne de caractères
(au format ”a.b.c.d”) grâce à la fonction suivante :
char * inet ntoa(struct in addr in) ;
Par exemple : printf("Adresse IP : %s", inet ntoa(adr)) ;
– Deuxième cas : si on connaı̂t le nom de la machine distante alors on peut utiliser la fonction suivante :
struct hostent * gethostbyname(char * hostname) ;
– Cette fonction permet d’obtenir via les mécanismes de résolution de noms (DNS) l’adresse IP d’une
machine à partir de son nom. Elle, retourne un pointeur sur une structure du type
struct hostent {
char * h name ;
/* nom de la machine */
char **h aliases ; /* noms alternatifs */
int h addrtype ;
/* type d’adresse : AF INET */
int h length ;
/* taille de l’adresse en octets*/
char ** h addr list ; /* adresses de la machine */
/* de type struct in addr */
}
– Exemple :
struct hostent* hote ;
struct in addr* addr ;
hote = gethostbyname("www.google.fr") ;
adr = (struct in addr*) res->h addr list[0] ;
– On peut convertir l’adresse IP du format condensé vers le format chaı̂ne de caractères (au format
”a.b.c.d”) grâce à la fonction suivante :
char * inet ntoa(struct in addr in) ;
– Par exemple :
printf("Adresse IP : %s", inet ntoa(adr)) ;
– Troisième cas : on veut l’adresse locale :
long gethostid() ;
– Récupére, sous format condensé, l’adresse IP de la machine courante
– Quatrième cas : on veut une des adresses locales :
INADDR ANY
– Cet entier long non signé est une constante, équivalente à une adresse IP ”0.0.0.0”.
– Elle peut être utilisée pour associer la socket à n’importe quelle adresse IP de la machine locale (s’il en
existe plusieurs).
– Elle est souvent utilisée par le programme serveur pour associer une adresse d’écoute à la socket (i.e.
adresse locale du serveur).
– Par exemple :
adr.sin addr.s addr = htonl(INADDR ANY) ;
• int
–
–
–
–
–
listen(int sock, int backlog) ;
Permet de déclarer un service (i.e. un serveur) auprès du système local.
Fonction NON bloquante
renvoi 0 s’il réussit, ou -1 en cas d’échec
sock : le descripteur de socket crée avec la fonction socket()
backlog : indique le nombre maximum de demandes de connexions mises en attentes avant d’être traitèes
par accept()
9
• int accept(int sock, struct sockaddr *adresse, socklen t *longueur) ;
– Fonction BLOQUANTE : en attente de l’arrivée d’une demande de connexion d’une machine distante (le
client)
– Renvoi un nouveau descripteur de socket ou -1 en cas d’échec
– sock : socket qui a été créée avec la fonction socket
– adresse : l’argument adresse est un pointeur sur une structure sockaddr. La structure sera remplie avec
l’adresse du correspondant qui s’est connecté (le client)
– longueur (l’argument longueur est un paramètre résultat) : il doit contenir initialement la taille de la
structure pointée par adresse, et est renseigné au retour par la longueur réelle (en octet) de l’adresse
remplie.
• int connect(int sock, struct sockaddr *addr serv, socklen t longueur) ;
– Fonction BLOQUANTE permettant l’établissement d’une connexion avec une machine où se trouve un
serveur
– Renvoi 0 si la connexion est établie ou -1 en cas d’échec
– sock : le descripteur de la socket
– addr serv : adresse de la machine distante (le serveur)
– longueur : taille de la structure addr_serv
• ssize t write(int sock, const void *msg, size t count) ;
– write écrit jusqu’à count octets dans le descripteur de la socket sock depuis le buffer pointé par msg.
– Fonction BLOQUANTE jusqu’à ce que le transfert soit effectué
– renvoie le nombre d’octets effectivement envoyés. Sinon un entier < 0 en cas d’erreur.
– Peut être utlisé pour les sockets en mode connecté ou en mode non connecté.
• int
–
–
–
–
–
send(int sock, const void *msg, size t count, int flags) ;
send ne peut être utilisé qu’avec les sockets connectées
renvoie le nombre d’octets effectivement envoyés. Sinon un entier < 0 en cas d’erreur.
msg : le tampon contenant les octets à envoyer
count : le nombre d’octets à envoyer
flags : drapeaux correspondant au type d’envoi à adopter. Par exemple :
– le flag 0 indique un envoi normal
– le flag MSG OOB indiquera que les données urgentes (Out Of Band) doivent être envoyées
– le flag MSG DONTROUTE indiquera que les données ne doivent pas être routées
• ssize t read(int sock, void *msg, size t count) ;
– read lit jusqu’à count octets depuis le descripteur de socket sock et les place dans le buffer pointé par
msg.
– renvoie le nombre d’octets effectivement lus. Sinon un entier < 0 en cas d’erreur.
– Fonction BLOQUANTE jusqu’à réception d’un paquet.
– Si count vaut zéro, read renvoie zéro et n’a pas d’autres effets. Si count est supérieur à SSIZE\_MAX, le
résultat est indéfini
• int
–
–
–
–
–
recv(int sock, void *msg, int count, unsigned int flags) ;
recv ne peut être utilisé qu’avec les sockets en mode connecté
renvoie le nombre d’octets effectivement lus. Sinon un entier < 0 en cas d’erreur.
msg : le tampon qui recevra les octets lus
count : le nombre d’octets à lire
flags : drapeaux correspondant au type de lecture à adopter. Par exemple :
– le flag 0 indique une lecture normale
– le flag MSG OOB indiquera que les données urgentes (Out Of Band) doivent être lues
– le flag MSG PEEK indiquera que les données lues ne sont pas retirées de la queue de réception
• int close(int sock) ;
– ferme le descripteur sock en permettant au système d’envoyer les données restantes
– renvoie 0 s’il réussit, ou -1 en cas d’échec
• int shutdown(int sock, int how) ;
– Permet la fermeture partielle d’une socket dans un des deux sens (pour une connexion full-duplex) :
– Si how est égal á 0, le socket est fermé en réception
10
– Si how est égal á 1, le socket est fermé en émission
– Si how est égal á 2, le socket est fermé dans les deux sens
– renvoie 0 s’il réussit, ou -1 en cas d’échec
3.2
Exemple de serveur TCP
#include
#include
#include
#include
#include
#include
#include
#include
#include
<stdio.h>
<unistd.h>
<stdlib.h>
<string.h>
<netdb.h>
<sys/types.h>
<sys/socket.h>
<netinet/in.h>
<arpa/inet.h>
#define MAXMSGSIZE 1024
void erreur(char *message_erreur) {
perror(message_erreur) ;
exit(1) ;
}
int main(int argc, char **argv) {
int sock, newsock ;
unsigned short port ;
struct sockaddr_in serveuradr ;
struct sockaddr_in clientadr ;
char *client_addr ;
char message[MAXMSGSIZE] ;
int nb_octets ;
int len ;
if (argc != 2) {
fprintf(stderr, "usage : %s <port>\n", argv[0]) ;
exit(1) ;
}
port = (unsigned short) atoi(argv[1]) ;
sock = socket(AF_INET, SOCK_STREAM, 0) ;
if (sock < 0)
erreur("Erreur de creation de la socket") ;
len = sizeof(struct sockaddr_in) ;
bzero((char *) &serveuradr, len) ;
serveuradr.sin_family = AF_INET ;
serveuradr.sin_addr.s_addr = htonl(INADDR_ANY) ;
serveuradr.sin_port = htons(port) ;
if (bind(sock, (struct sockaddr *) &serveuradr, len) < 0)
erreur("Erreur attachement socket") ;
if (listen(sock, 5) < 0)
erreur("Erreur d’ecoute sur la socket") ;
while (1) {
newsock = accept(sock, (struct sockaddr *) &clientadr, (socklen_t *)&len) ;
if (newsock < 0) erreur("Erreur accept") ;
printf("connexion etablie avec %s\n", inet_ntoa(clientadr.sin_addr)) ;
bzero(message, MAXMSGSIZE) ;
nb_octets = read(newsock, message, MAXMSGSIZE) ;
if (nb_octets < 0) erreur("Erreur de lecture sur la socket") ;
printf("le serveur a recu %d octets : %s", nb_octets, message) ;
nb_octets = write(newsock, message, strlen(message)) ;
if (nb_octets < 0) erreur("Erreur d’ecriture sur la socket") ;
close(newsock) ;
11
}
}
3.3
Exemple de client TCP
#include
#include
#include
#include
#include
#include
#include
#include
<stdio.h>
<stdlib.h>
<string.h>
<unistd.h>
<sys/types.h>
<sys/socket.h>
<netinet/in.h>
<netdb.h>
#define MAXMSGSIZE 1024
void erreur(char *message_erreur) {
perror(message_erreur) ;
exit(1) ;
}
int main(int argc, char **argv) {
int sock ;
unsigned short port ;
struct sockaddr_in serveuradr ;
struct hostent *serveur ;
char *nom_serveur ;
char message[MAXMSGSIZE] ;
int nb_octets ;
int len ;
if (argc != 3) {
fprintf(stderr,"usage : %s <nom_Serveur> <port>\n", argv[0]) ;
exit(0) ;
}
nom_serveur = argv[1] ;
port = (unsigned short) atoi(argv[2]) ;
len = sizeof(struct sockaddr_in) ;
sock = socket(AF_INET, SOCK_STREAM, 0) ;
if (sock < 0) erreur("Erreur de creation de la socket") ;
serveur = gethostbyname(nom_serveur) ;
if (serveur == NULL) erreur("Le nom de serveur n’est pas connu") ;
bzero((char *) &serveuradr, len) ;
serveuradr.sin_family = AF_INET ;
bcopy((char *)serveur->h_addr, (char *)&serveuradr.sin_addr.s_addr, len) ;
serveuradr.sin_port = htons(port) ;
if (connect(sock, (struct sockaddr *)&serveuradr, len) < 0) erreur("Erreur de connexion") ;
printf("Tapez le message a envoyer : ") ;
bzero(message, MAXMSGSIZE) ;
fgets(message, MAXMSGSIZE, stdin) ;
nb_octets = write(sock, message, strlen(message)) ;
if (nb_octets < 0) erreur("Erreur d’ecriture sur la socket") ;
bzero(message, MAXMSGSIZE) ;
nb_octets = read(sock, message, MAXMSGSIZE) ;
if (nb_octets < 0) erreur("Erreur de lecture depuis la socket") ;
printf("Reponse du serveur : %s", message) ;
close(sock) ;
}
12
3.4
Connexion UDP
Dans ce mode de communication une connexion n’est pas durable. Ce mode nécessite l’adresse de destination
à chaque envoi, et aucun accusé de réception n’est donné. Le principe est défini ci-dessous :
Côté Client :
– crée une socket,
– écrit au serveur (avec
adresse du serveur).
3.5
Client
Serveur
socket()
socket()
sendto()
bind()
recvfrom()
Côté Serveur :
– crée une socket
– associe une adresse à la socket
– reçoit du client (avec adresse du
client)
Fonctions système (langage C) pour UDP
Outre les donctions système déjà vues plus haut.
• int sendto(int sock, const void *msg, size t count,
int flags, const struct sockaddr *destinataire,
socklen t adrlen) ;
–
–
–
–
–
–
–
ne peut être utilisé qu’avec les sockets connectées
renvoie le nombre d’octets effectivement envoyés. Sinon un entier < 0 en cas d’erreur.
msg : le tampon contenant les octets à envoyer
count : le nombre d’octets à envoyer
flags : drapeaux correspond au type d’envoi à adopter. Les mêmes que send.
destinataire : L’adresse de la cible
adrlen : taille en octets de l’adresse de la cible
• int recvfrom(int sock, void *msg, int count,
unsigned int flags,
struct sockaddr *emetteur,
socklen t *adrlen) ;
–
–
–
–
–
–
–
3.6
peut être utilisé avec les sockets en mode connecté ou en mode non connecté
renvoie le nombre d’octets effectivement lus. Sinon un entier < 0 en cas d’erreur.
msg : le tampon qui recevra les octets lus
count : le nombre d’octets à lire
flags : drapeaux correspond au type de lecture à adopter. Les mêmes que recv.
emetteur : L’adresse de l’émetteur
adrlen : taille en octets de l’adresse de l’émetteur
Exemple de serveur UDP
#include
#include
#include
#include
#include
#include
<arpa/inet.h>
<stdio.h>
<unistd.h>
<errno.h>
<string.h>
<stdlib.h>
#define MAXMSGSIZE 1024
void erreur(char *message_erreur) {
perror(message_erreur) ;
exit(1) ;
}
13
int main(int argc, char **argv)
{
int sock ;
struct sockaddr_in serveur_adr ;
struct sockaddr_in client_adr ;
int len ;
int nb_octets ;
char message[MAXMSGSIZE] ;
sock = socket(AF_INET, SOCK_DGRAM, 0) ;
if (sock < 0) erreur("Erreur de creation de la socket") ;
serveur_adr.sin_family = AF_INET ;
serveur_adr.sin_port = htons(5000) ;
serveur_adr.sin_addr.s_addr = htonl(INADDR_ANY) ;
bzero(&(serveur_adr.sin_zero),8) ;
len = sizeof(struct sockaddr_in) ;
if (bind(sock,(struct sockaddr *) &serveur_adr, len) == -1) erreur("Erreur attachement socket") ;
printf("\n Attente client sur port 5000") ;
fflush(stdout) ;
while (1) {
nb_octets = recvfrom(sock, message, MAXMSGSIZE, 0,
(struct sockaddr *) &client_adr,
(socklen_t *) &len) ;
message[nb_octets] = ’\0’ ;
printf("\n(%s",inet_ntoa(client_adr.sin_addr)) ;
printf(",%d)",ntohs(client_adr.sin_port)) ;
printf(" a dit : %s", message) ;
fflush(stdout) ;
}
exit(0) ;
}
3.7
Exemple de client UDP
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
<sys/types.h>
<sys/socket.h>
<netinet/in.h>
<arpa/inet.h>
<netdb.h>
<stdio.h>
<unistd.h>
<errno.h>
<string.h>
<stdlib.h>
#define MAXMSGSIZE 1024
void erreur(char *message_erreur) {
perror(message_erreur) ;
exit(1) ;
}
int main(int argc, char *argv[])
{
int sock ;
struct sockaddr_in serveur_adr ;
char message[MAXMSGSIZE] ;
int len ;
if (argc != 2) {
fprintf(stderr,"usage : %s <@IP_Serveur>\n", argv[0]) ;
exit(0) ;
}
sock = socket(AF_INET, SOCK_DGRAM, 0) ;
14
if (sock < 0) erreur("Erreur de creation de la socket") ;
serveur_adr.sin_family = AF_INET ;
serveur_adr.sin_port = htons(5000) ;
serveur_adr.sin_addr.s_addr = inet_addr(argv[1]) ;
bzero(&(serveur_adr.sin_zero),8) ;
len = sizeof(struct sockaddr_in) ;
while (1) {
printf("Tapez votre message (q ou Q pour quitter) :") ;
gets(message) ;
if ((strcmp(message, "q") == 0) || strcmp(message, "Q") == 0) exit(0) ;
else sendto(sock, message, strlen(message), 0, (struct sockaddr *)&serveur_adr, len) ;
}
}
3.8
Multiplexage
Le multiplexage consiste, pour le serveur, à se remettre en mode “attente de connexion”, donc à créer un
processus fils (ou un thread) pour traiter les requêtes d’un client. Dans chacun des schémas ci-dessous (TCP
et UDP), la figure à gauche correspond au mode de communication non multiplexé, celle à droite multiplexé.
Dans le cas du multiplexage, il est aussi possible de “scruter” l’ensemble des clients actuellement connectés (cf.
sous-section suivante).
Mode connecté (TCP)
Mode non connecté (UDP)
15
Exemple : serveur multiplexé en mode connecté (TCP)
#include
#include
#include
#include
#include
#include
<stdio.h>
<stdlib.h>
<strings.h>
<sys/types.h>
<sys/socket.h>
<netinet/in.h>
void erreur(char *msg)
{
perror(msg) ;
exit(1) ;
}
void activite(int sock)
{
char buffer[256] ;
bzero(buffer,256) ;
if (read(sock,buffer,255) < 0) erreur("Erreur de lecture sur la socket") ;
printf("Voici le message : %s\n",buffer) ;
if (write(sock,"I got your message",18) < 0) erreur("Erreur d’ecriture sur la socket") ;
}
int main(int argc, char *argv[])
{
int sock, newsock, port, clilen, pid ;
struct sockaddr_in serv_adr ;
struct sockaddr_in cli_adr ;
if (argc < 2) {
fprintf(stderr,"usage : %s <port>\n", argv[0]) ;
exit(1) ;
}
sock = socket(AF_INET, SOCK_STREAM, 0) ;
if (sock < 0) erreur("ERROR opening socket") ;
bzero((char *)&serv_adr,sizeof(serv_adr)) ;
port = atoi(argv[1]) ;
serv_adr.sin_family = AF_INET ;
serv_adr.sin_addr.s_addr=htonl(INADDR_ANY) ;
serv_adr.sin_port = htons(port) ;
if (bind(sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) < 0)
erreur("Erreur attachement socket") ;
listen(sock,5) ;
clilen = sizeof(cli_adr) ;
while (1) {
newsock = accept(sock, (struct sockaddr *) &cli_adr, (socklen_t *) &clilen) ;
if (newsock < 0) erreur("Erreur accept") ;
pid = fork() ;
if (pid < 0) erreur("Erreur de creation de processus fork") ;
if (pid == 0) {
close(sock) ;
activite(newsock) ;
exit(0) ;
}
else close(newsock) ;
}
return 0 ;
}
16
3.9
Multiplexage : Mécanisme de scrutation passive
D’un point de vue client, la situation est similaire aux cas non multiplexés. Du point de vue du serveur, on
utilise trois tableaux de structures de test. Une structure de test est un bit associé à un descripteur :
– un tableau de structures de test pour savoir si la lecture est possible sur un ou plusieurs de ces descripteurs,
– un tableau de structures de test pour savoir si l’écriture est possible sur un ou plusieurs de ces descripteurs,
– un tableau de structures de test pour savoir si une exception est détectée pour un ou plusieurs de ces
descripteurs.
Le mécanisme est utilisé ici avec des sockets mais peut aussi être utilisé avec des descripteurs de fichiers.
Le fichier d’en-tête <sys/select.h> est nécessaire.
• int select(int n, fd set * rdfds, fd set * wrfds, fd set * exfds, struct timeval * timeout) ;
– permet de définir les tableaux de descripteurs et le délai entre chaque test. Si un des tableaux est à NULL :
aucun descripteur à tester pour ce type.
– retourne -1 en cas d’erreur, le nombre de structures testées sinon.
– n : nombre maximal de descripteurs + 1 (dimensionnement de masques internes)
– rdfds : descripteurs à surveiller en lecture
– wrfds : descripteurs à surveiller en écriture
– exfds : descripteurs à surveiller pour exception
– timeout : délai d’attente maximal avant le retour (∞ si pointeur nul).
struct timeval {
long tv sec ;
/* secondes */
long tv use ;
/* microsecondes */
}
• FD ZERO(fd set * set)
– Cette macro vide l’ensemble set
• FD SET(int fd,fd set * set)
– Cette macro permet de rajouter le descripteur fd à l’ensemble set
• FD CLR(int fd,fd set * set)
– permet de retirer le descripteur fd de l’ensemble set.
• FD ISSET(int fd,fd set * set)
– permet de vérifier si le descripteur fd est contenu dans l’ensemble set. Cette macro est principalement
utile après le retour de select.
Le principe de fonctionnement est le suivant :
– Initialiser les ensembles de descripteurs avec FD_ZERO
– Insérer les descripteurs avec FD_SET selon l’opération souhaitée
– Lecture : read(), recv(), recvfrom() ou accept()
– Ecriture : write() ou send() (régulation producteur/consommateur)
– Circonstances exceptionnelles : peu utilisé
– Faire un calcul de max. pour déterminer le plus grand descripteur
– Préparer éventuellement un timeout
– Effectuer l’appel à select
– Pour chaque descripteur précédemment inséré
– Tester avec FD_ISSET s’il est toujours présent dans son ensemble
– Si oui : faire l’opération souhaitée (correspondant à l’ensemble)
Exemple de serveur avec multiplexage avec scrutation :
#include
#include
#include
#include
#include
#include
#include
#include
#include
<stdio.h>
<errno.h>
<stdlib.h>
<string.h>
<unistd.h>
<sys/types.h>
<sys/socket.h>
<netinet/in.h>
<netdb.h>
17
#define PORT
#define MAXMSG
5555
512
void erreur(char *msg)
{
perror(msg) ;
exit(1) ;
}
int read_from_client (int sock)
{
char buffer[MAXMSG] ;
int nbytes ;
nbytes = read (sock, buffer, MAXMSG) ;
if (nbytes < 0) erreur("Probleme de lecture") ;
else if (nbytes == 0) return -1 ;
else {
fprintf (stderr, "Le Serveur a recu le message : ‘%s’\n", buffer) ;
return 0 ;
}
}
int main (void)
{
int sock, i ;
fd_set active_fd_set, read_fd_set ;
struct sockaddr_in clientname, serv_adr ;
size_t size = sizeof (struct sockaddr_in) ;
sock = socket(AF_INET, SOCK_STREAM, 0) ;
if (sock < 0) erreur("Erreur d’ouverture de la socket") ;
bzero((char *)&serv_adr,sizeof(serv_adr)) ;
serv_adr.sin_family = AF_INET ;
serv_adr.sin_addr.s_addr=htonl(INADDR_ANY) ;
serv_adr.sin_port = htons(5555) ;
if (bind(sock, (struct sockaddr *) &serv_adr, size) < 0) erreur("Erreur attachement socket") ;
if (listen(sock, 5) < 0) erreur("Erreur d’ecoute sur la socket") ;
FD_ZERO (&active_fd_set) ;
FD_SET (sock, &active_fd_set) ;
while (1) {
read_fd_set = active_fd_set ;
if (select (FD_SETSIZE, &read_fd_set, NULL, NULL, NULL) < 0)
erreur("Erreur dans la primitive Select") ;
for (i = 0 ; i < FD_SETSIZE ; ++i)
if (FD_ISSET (i, &read_fd_set)) {
/* Demande de connexion sur la socket initale */
if (i == sock) {
int new ;
size = sizeof (clientname) ;
new = accept (sock, (struct sockaddr *) &clientname, &size) ;
if (new < 0) erreur("Erreur accept") ;
fprintf (stderr, "Serveur : connectiont depuis") ;
fprintf (stderr, "%s, port",inet_ntoa (clientname.sin_addr)) ;
fprintf (stderr, "%hd.\n", ntohs (clientname.sin_port)) ;
FD_SET (new, &active_fd_set) ;
}
/* Arrivee de donnee sur une socket connectee */
else {
if (read_from_client (i) < 0) {
close (i) ;
FD_CLR (i, &active_fd_set) ;
}
}
}
}
}
18
4
Compléments sur la couche transport : cas de UDP et TCP
La couche transport doit définir la structure des paquets (qui seront encapsulés par IP) contenant les données
et les informations source/destination. Dans le cas de TCP le mécanisme d’envoi/réception doit aussi s’assurer
de la fiabilité des données envoyées/reçues.
4.1
Cas de UDP
UDP (User Datagram Protocol) fournit un service de transport sans connexion. Un transfert UDP est
caractérisé par
– une IP source, une IP destination : informations données dans l’en-tête IP,
– un port source, un port destination : informations fournies dans l’en-tête UDP.
La connexion est à usage unique. Le port client est rendu après utilisation. Le port serveur attend un autre client.
Comme pour TCP, un port est un entier non signé sur 2 octets. La fiabilité est minimale. Toutefois, en option, il
y a possibilité de détection des erreurs sur le contenu reçu, mais sans possibilité automatique de retransmission.
Un segment UDP a la structure suivante :
32 bits
port source
port destination
longueur segment
somme de contrôle
données de l’application
– L’en-tête a une longueur fixe de 8 octets.
– “longueur segment” est la longueur totale du segment UDP, en-tête inclus.
– La “somme de contrôle” vaut 0 par défaut. Sert à la
vérification de l’intégrité de tout le segment : entête
+ données. Si détection d’erreur alors le segment est
détruit.
Le protocole est léger : seulement 8 octets d’en-tête pour (jusqu’à) 64Ko de données. Toutefois, il n’y a
aucune garantie sur l’ordre d’arrivée (si plusieurs paquets doivent être envoyés). C’est un protocole adapté pour
des applications à faible envoi ou très sensibles aux délais (e.g. VoIP), ou celles exigeantes en terme de débit (e.g
Vidéostreaming). Non adapté à des applications nécessitant un transfert fiable (e.g. mail).
4.2
Cas de TCP
TCP (Transport Control Protocol) fournit un service de transport fiable sur IP. La communication est en
mode connecté : Ouverture d’un canal, communication Full-Duplex, fermeture du canal. La connexion fiabilise la
communication : négociation entre source et destination des paramètres de fonctionnement mutuels. La fiabilité
est assurée. Pour la réduction des pertes de paquets, il y a un mécanisme de contrôle de congestion. Pour la
gestion des pertes, il y a un mécanisme de détection et retransmissions.
Pour chaque partie client et serveur, on trouve :
– un tampon pour stocker les données reçues (qui seront ensuite recopiées vers la zone mémopire de
l’application utilisant ce socket)
– un statut déterminant l’état de la connexion (en initialisation, en envoi/réception, en fermeture).
– des valeurs permettant de gérer la communication : taille d’un paquet, valeur du timeout.
– les informations sur les données : adresse de la prochaine donnée en attente de réception, adresse de la
prochaine donnée en attente d’envoi.
La structure d’un paquet TCP est la suivante :
19
0
4
10
16
24
31
port source
port destination
numéro de séquence
numéro d’acquittement
hlen reserved
codes
window
urgency pointer
checksum
options
bourrage
données de l’application
– Ports source et destination (16 bits) : identifient les applications émettrice et réceptrice.
– Numéro de séquence (32 bits) : identifie l’octet dans le flux de données que représente le premier octet
de ce segment.
– Numéro d’acquittement (32 bits) : prochain numéro de séquence que l’émetteur du segment s’attend
à recevoir = numéro de séquence du dernier octet reçu correctement et en séquence + 1.
– hlen (4bits) : longueur de l’en-tête en mots de 32 bits.
– Reserved (6 bits) : champ réservé pour de futures utilisations.
– Codes (6 bits) : bits indiquant le type du segment (multi-information possible)
– URG : le pointeur de données urgentes est valide, les données sont émises sans délai, les données reçues
sont remises sans délai.
– SYN : utilisé à l’initialisation de la connexion pour indiquer où la numérotation séquentielle commence.
SYN occupe lui-même un numéro de séquence bien que ne figurant pas dans le champ de données.
Le Numéro de séquence inscrit dans le datagramme (correspondant à SYN) est un Initial Sequence
Number (ISN) produit par un générateur garantissant l’unicité de l’ISN sur le réseau (indispensable
pour identifier les duplications).
– FIN : utilisé lors de la libération de la connexion.
– ACK : utilisé lorsque le segment transporte un acquittement
– PSH : fonction “push” : un récepteur TCP décodant le bit PSH, transmet à l’application réceptrice, les
données correspondantes sans attendre plus de données de l’émetteur. Exemple : émulation terminal,
pour envoyer chaque caractère entré au clavier (mode caractère asynchrone).
– RST : utilisé par une extrémité pour indiquer à l’autre extrémité qu’elle doit réinitialiser la connexion.
Ceci est utilisé lorsque les extrémités sont désynchronisées.
– Fenêtre (16 bits) : Contrôle de flux et transmission efficiente = nombre d’octets supplémentaires que le
récepteur est prêt à recevoir, i.e. taille courante des tampons de réception du récepteur.
– Somme de contrôle (16 bits) : permet de vérifier si les données sont intactes, si elles ont atteint la
bonne destination et si IP les a passées au bon protocole.
– Pointeur urgence (16 bits) : déplacement positif à ajouter au numéro de séquence du segment pour
récupérer le numéro de séquence du dernier octet de donnée urgente. Permet à une extrémité de la
connexion d’envoyer des données hors bande (spécifiées comme urgentes) sans avoir à attendre que l’autre
extrémité ait consommé les octets déjà dans le flux.
– Options : Permettent de négocier la taille maximale des segments échangés (MSS : Maximal Segment
Size). Cette option n’est présente que dans les segments d’initialisation de connexion (avec bit SYN).
TCP calcule une taille maximale de segment de manière à ce que le datagramme IP résultant corresponde
au MTU du réseau. La recommandation est de 536 octets. La taille optimale du segment correspond au
cas où le datagramme IP n’est pas fragmenté. A noter qu’il n’existe pas de mécanisme pour connaı̂tre le
MTU, que le routage peut entraı̂ner des variations de MTU, que la taille optimale dépend de la taille des
en-têtes (donc des options).
Le principe de communication entre deux machines A et B est le suivant (outre la spécification de ports
source/destinataire) : (on suppose que A commence)
– A et B choisissent chacun un numéro d’identifiant (ISN).
– Phase de synchronisation (3-way handshaking)
– Phases d’envoi/accusé de récpeption et récupération si nécessaire
– Phase de terminaison (2x2-way handshaking)
20
4.2.1
phases d’initialisation et de terminaison
A (actif)
SYN SENT
B (passif)
ISN de A
ISN de A + 1
SYN
temps
1415
5315
21 (
<M
0)
SS 1
024>
SYN RCVD
ESTABLISHED
0)
587 (
23083
8
1
24>
SYN
SS 10
2 <M
2
5
1
3
14155
ACK
ACK 1
8230835
88 (0)
ISN de B
ISN de B + 1
accord sur MSS
ESTABLISHED
A (actif)
temps
FIN WAIT 1
FIN WAIT 2
TIME WAIT
3-way handshaking
En bleu l’état des connexions, en rouge les
“données” transférées :
– A envoie son ISN, avec une proposition de
MSS.
– B envoie son ISN, accuse réception de l’ISN
de A (donc envoie le premier octet qu’il
attend = ISN de A + 1), et accepte le MSS.
– A accuse réception de l’ISN de B (donc envoie le premier octet qu’il attend = ISN de
B + 1).
B (passif)
FIN
1474
6999
AC
99 (
K1
0)
9991
1111
2 (0
)
00000
14747
K
C
A
(0)
1112
99911
1
N
FI
00000
14747
ACK
CLOSE WAIT
LAST ACK
2x2-way handshaking
En bleu l’état des connexions, en rouge les
“données” transférées :
– A envoie un signal de fin FIN, et accuse
réception du dernier paquet reçu de B.
– B envoie l’accusé de réception du dernier
paquet reçu de A.
– B envoie un signal de fin de données.
– A accuse réception de cette fin des données.
ACK 1
823083
588
Le fait que l’initialisation soit en 3 parties et la fin en 4 parties permet éviter les erreurs quand les paquets
arrivent croisés (donc en interprétant mal la demande de l’autre partie).
4.2.2
gestion des pertes, contrôle de flux et contrôle de congestion
En phase d’envoi/réception de paquets, des paquets peuvent être perdus, en particulier un accusé de réception
peut ne jamais arriver. Il est donc nécessaire de définir un délai (timeout) au-delà duquel le paquet ou son accusé
de réception aura été considéré comme perdu. La vitesse d’envoi doit aussi être contrôlée.
– Gestion de pertes : par rapport aux acquittements (ACK), si l’émetteur ne reçoit pas d’acquittement
au délai, alors réenvoi de tous les paquets à partir de celui qui n’a pas été acquitté (sauf si un acquittement
arrive entre-temps).
– Contrôle de flux : par rapport au récepteur, l’émetteur adapte le nombre de paquets envoyés à la taille
du tampon de réception.
– Contrôle de congestion : par rapport au réseau, l’émetteur adapte le débit des données envoyées à
la bande passante instantanée du réseau. Ce n’est pas la taille des paquets, mais leur débit d’envoi qui
change.
Le buffer de réception permet d’entreposer les données reçues avant que celles-ci ne soient traitées par
l’application :
21
prochain octet attendu
dernier octet envoyé à l’application
dernier octet reçu
tampon de réception
fenêtre
Contrôle de flux :
– Système envoyer et attendre (send and wait) : Après l’envoi de chaque paquet, l’émetteur s’arrête
et attend que le récepteur soit prêt (acquittement) à accepter un autre paquet : l’émetteur attend
l’acquittement (ACK) du récepteur avant d’envoyer un nouveau paquet.
– Système de fenêtre glissante (sliding window) : L’émetteur et le récepteur vont utiliser une taille
de fenêtre. LA taille de la fenêtre définit la quantité maximale de données qu’on peut envoyer avant de
recevoir un accusé de réception. La fenêtre peut être de taille fixe ou variable. La valeur est donnée dans
le champ ‘fenêtre/window’ dans un paquet TCP.
Contrôle de congestion :
– But : éviter la surcharge du réseau en déterminant la capacité réseau disponible. Dépend des autres
connexions qui partagent les ressources.
– Stratégie : TCP n’envoie pas plus vite que ce que le composant le plus lent - le réseau ou l’hôte de
destination - peuvent atteindre.
– Hypothèse : une perte est interprétée comme de la congestion dans le réseau
– 2 algorithmes possibles qui contrôlent la quantité d’information injectée dans le réseau :
– Slow Start (démarrage lent) : La transmission des données commence sans avoir connaissance de l’état
du réseau. TCP sonde le réseau en le mettant lentement à l’épreuve pour en déterminer la capacité
disponible. L’algorithme de slow start est utilisé au début d’un transfert ou après la réparation d’une
perte détectée par temporisateur de retransmission. Pendant le slow start, la cwnd (nombre maximal
d’octets que l’émetteur peut envoyer sans recevoir aucun accusé) est augmentée de x octets pour
chaque ACK reçu acquittant de nouvelles données. L’accroissement est multiplicatif jusqu’à détection
de congestion ou atteinte d’un seuil (estimation de la bande passante).
– Congestion Avoidance (évitement de congestion) : idem mais accroissement additif jusqu’à détection de
congestion.
– en cas de congestion (i.e. perte), le décroissement est multiplicatif.
22

Documents pareils