Vous êtes sur la page 1sur 49

Programmation Rseau en C sous Unix

Chapitre 1 : Notions de base


I. Structure dadresse :

En programmation rseau, nous allons utiliser les sockets. Cest un moyen de communication qui se dfinit par un port et une adresse. Elle est reprsente sous la forme dun descripteur de fichier (un entier). Les fonctions permettant de manipuler des sockets prennent en argument une structure dadresse de socket.

a) La structure dadresse de socket IPV4 :


Aussi appel "structure dadresse de socket internet", elle est dfinie dans <netinet/in.h>. struct sockaddr_in { uint8_t sin_len; //taille de la structure sa_family_t sin_family; //famille de la socket in_port_t sin_port; //numro de port TCP ou UDP struct in_addr sin_addr; //adresse IPv4 char sin_zero[8]; //inutilis }; Avec struct in_addr { in_addr_t s_addr; //adresse IPv4 sur 32 bits }; sin_len : utilis uniquement avec les sockets de routage. sin_family : vaut AF_INET pour un socket IPv4. sin_port : numro de port sur 16 bits. sin_addr : adresse IPv4 sur 32 bits.

b) Structure dadresse de socket gnrique :


Les fonctions de gestion de socket doivent fonctionner avec toutes les familles de protocoles supportes, donc avec diffrentes structures de socket.

Programmation Rseau en C sous Unix


Problme : comment faire pour quune fonction accepte des structures diffrentes comme paramtres ? o on peut utiliser un pointeur void* en ANSI C. Ce qui ne fonctionne pas avec les sockets. o On utilise une structure gnrique pouvant contenir toutes les autres struct sockaddr { uint8_t sa_lem; sa_family_t sa_family; char sa_data[14]; }

Tout appel une fonction de socket se fera avec un transtypage de la structure dadresse de socket gnrique. Grce au champ sa_family, le compilateur saura o se trouvent les diffrents champs de la structure. Exemple : struct sockaddr_in servaddr; int sockfd; : //remplissage servaddr : //initialization sockfd connect(sockfd,(struct sockaddr*)&servaddr, sizeof(servaddr));

NB : Avant dutiliser une structure dadresse de socket, il est prfrable de linitialiser 0. Cela se fait avec un des 2 fonctions suivantes : #include <strings.h> void bzero(void* dest, size_t nbytes); #include <string.h> void *memset(void* dest, int c, size_t len);

II.

Ordre doctets :

Soit un entier cod sur 16 bits (2 octets). Il y a 2 faons de le stocker en mmoire : Adresses croissantes 2

Programmation Rseau en C sous Unix


Octet de poids fort Ordre little-endian Octet de poids faible Ordre big-endian Il ny a pas de norme sur la faon dont sont stockes les valeurs multi-octets en mmoire. Ce qui peut poser problme : Exemple : 259 en little-endian : poids fort | poids faible 0000 0001|0000 0011 poids faible| poids fort Lu sur un systme en big-endian : 769 Cette diffrence de stockage est importante en programmation rseau car les protocoles rseaux spcifient tous un ordre rseau doctets. Par exemple, les protocoles internet utilisent les big-endian. Les champs port et adresse des structures de socket doivent tre en ordre rseau. Si lordre de stockage de la machine nest pas le mme que lordre rseau, il faut convertir les valeurs. #include <netinet/in.h> uint16_t htons(uint16_t val); uint32_t htonl(uint32_t val); retournent val en ordre rseau sur 16 et 32 bits uint16_t ntohs(uint16_t val); uint32_t ntohl(uint32_t val); retournent val en ordre machine sur 16 et 32 bits h n s l : : : : hte network short long Octet de poids fort Octet de poids faible

III.

Conversion dadresses :

Les adresses IP sont toujours utilises sous la forme de 32 bits par les fonctions en programmation rseau. Or, cette forme nest pas utilisable par un humain. On utilise gnralement la forme dcimale pointe X.Y.Z.T. On a donc besoin de fonctions de conversions entre ces 2 formes.

Programmation Rseau en C sous Unix


a) Fonctions IPv4 seulement : #include <arpa/inet.h> int inet_aton(const char *str, struct inaddr *addr); convertit ladresse dcimal pointe str en adresse sur 32 bits addr (en ordre rseau) char *inet_ntoa(struct in_addr addr) convertit ladresse sur 32 bits addr et retourne la forme dcimale pointe.

b) Fonctions IPv4 et IPv6 :


#include <arpa/inet.h> int inet_pton(in family, char *str, void *addr); Transforme ladresse dcimale pointe str en adresse sur 32 bits addr. Family = AF_INET pour IPv4 = AF_INET6 pour IPv6 char *inet_ntop(int family, void *addr, char *str, size_t len); Convertit addr sur 32 bits en adresse sous forme dcimale pointe str. Family : cf inet_pton Len : taille de la chaine : IPv4 = 16 IPv6 = 46

Programmation Rseau en C sous Unix


Chapitre 2 : les sockets TCP
I. Fonctionnement dun client et dun serveur TCP

Nous allons tous dabord voir les diffrentes fonctions ncessaires ltablissement, le maintien et la fin dune connexion entre un client et un serveur TCP.

Client

Serveur Socket() Bind()

Socket()

listen()

connect()

accept()

write()

read()

read()

close()

write()

read()

Socket()

II.

La fonction socket :

#include <sys/socket.h> Int socket (int family, int type, int protocole) Retourne un descripteur de socket

Cre un socket avec le type de protocole de communication dsir.

Programmation Rseau en C sous Unix


Family : famille de protocoles AF_INET : protocoles IPv4 AF_INETS : protocoles IPv6 AF_LOCAL : protocoles du domaine Unix AF_ROUTE : sockets du router Type : type de socket SOCK_STREAM -> sockets TCP SOCK_DGRAM -> sockets UDP SOCK_RAW -> raw sockets Protocol : protocol utilise par le socket : |IPPROTO_TCP |IPPROTO_UDP ou 0 : le systme choisit le protocole selon family et type : SOCK_STREAM + AF_INET = TCP SOCK_STREAM + AF_INET6 = TCP SOCK_DGRAM + AF_INET = UCP SOCK_DGRAM + AF_INET6 = UCP

III.

La fonction bind :

Elle associe un socket une adresse locale de protocole. Pour les protocoles internet, ladresse de protocole est compose dune adresse IP (V6 ou V4) et dun port. #include <sys/socket.h> Int bind(int sockfd, const struct sockaddr *addr, socklen_t len);

Sockfd : descripteur du socket laquelle on veut lier ladresse et le port Addr : stucture dadresse contenant lIP et le port lier au socket. Len : taille de cette structure. Bind permet de spcifier par quelle adresse IP et quel port le socket va communiquer. Le serveur doit appeler bind pour au moins spcifier le port sur lequel il coute. Par contre, le client nutilise gnralement pas bind. Il laisse le noyau choisir un port phmre et la meilleur adresse IP selon le routage (si le client plusieurs IP). On peut laisser le choix au noyau de ladresse IP lier au socket en mettant ladresse INADDR_ANY comme adresse dans la structure. Lors de la connexion (TCP) ou de la rception de donnes (UDP), le noyau choisit lIP.

Programmation Rseau en C sous Unix


IV. La fonction listen :
Elle transforme un socket actif (socket client) en un socket passif, prt se mettre en attente de connexion (passage de ltat CLOSED ltat LISTEN). On peut aussi spcifier avec listen le nombre maximum de connexion que le noyau peut mettre en file dattente pour le socket. Int listen(int sockfd, int backlog);

Sockfd : descripteur de socket Backlog : nombre maximal de connexions Backlog = nombre de connexions compltes + nombre de connexions incompltes (juste un SYN reu).

V.

La fonction accept :

Elle est appele par le serveur TCP. Elle le met en attente de connexion et ne retourne une valeur quune fois la connexion complte. #include <sys/socket.h> int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *clilen);

Retourne | un descripteur de socket connecte sir OK | -1 sinon Sockfd : descripteur du socket passif Cliaddr : contiendra la structure dadresse du client connect. Clilen : taille de cette structure. On utilisera le descripteur retourn par accept pour lire et/ou crire dans le socket.

Programmation Rseau en C sous Unix


Int listenfd, connfd ; struct sockaddr_in serraddr, cliaddr ; listenfd = socket(); //remplissage de servaddr bind(listenfd, (struct sockaddr *)&servaddr, ); listen(listenfd,); For( ; ; ) { connfd = accept(listenfd, (struct sockaddr *)&cliaddr, ) ; //traitement de la connexion; . . . }close(connfd) ;

1 seule fois

Pour chaque connexion

VI.

La fonction connect :

Elle est utilise par un client TCP pour tablir une connexion avec un serveur. #include <sys/socket.h> int connect(int sockfd, const struct sockaddr* servaddr, socklen_t addrlen);

Retourne | O si OK | -1 sinon Sockfd : descripteur de socket retourn par la fonction socket. Servaddr : structure dadresse du serveur. Addrlen : taille de cette structure. Connect ne retourne une valeur quune fois le 3way handshake termin ou sil y a une erreur. Les erreurs peuvent tre : Le client ne reoit pas la rponse son SYN. Aprs 75 secondes renvoyer le SYN rgulirement, lerreur ETIMEDOUT est gnre. Le client reoit un RST en rponse son SYN (il ny a pas de processus serveur qui attend une connexion sur le port demand. Lerreur ECONNREFUSED est gnre. 8

Programmation Rseau en C sous Unix


La rponse au SYN est un paquet ICMP "destination unreachable" envoy par un routeur. Comme dans le premier cas le SYN va tre renvoy plusieurs fois et aprs 75s, lerreur EHOSTUNREACH est gnre.

VII.

La fonction close :

Cest la fonction close des E/S de bas niveau : #include <unistd.h> int close(int sockfd);

Elle provoque la fermeture du socket. Il ne sera plus possible dy lire ou dy crire, mais les donnes en attente dans le socket seront quand mme transmises. Close provoque la dcrmentation du compteur de rfrences du socket. Ce compteur reprsente les processus utilisant le descripteur du socket. Cest seulement quand ce compteur passe 0 que la phase de dconnection TCP commence.

VIII.

Serveur concurrent :

Le serveur que nous venons de voir est un serveur itratif. Il accepte une connexion, le traite et se remet en attente. Si le traitement de la connexion est long aucun autre client ne peut se connecter. Pour grer plusieurs clients la fois, on va sparer la partie connexion de la partie traitement. Pour cela, une fois la connexion tablie, le serveur cre un processus serveur fils qui traitera la connexion pendant quil se remettra en attente de client. Structure dun serveur concurrent ;

//phase prliminaire for( ; ; ) { connfd = accept(listenfd, ); pid = fork(); if(pid == 0) { close(listenfd); //fentre de la socket passive traitement(connfd); //traitement de la connexion close(fd); //fin de la connexion exit(0); } close(fd); //fentre de la socket connecte, mais pas de la connexion

Programmation Rseau en C sous Unix


Lors du fork, les descripteurs ouverts sont hrit par le fils. Avant la fin de accept : Client Demande de connexion Connect listenfd Serveur

Aprs accept : Client Connect connfd Aprs fork Client Connect connfd Serveur Listenfd Serveur Listenfd

Serveur fils Listenfd

connfd Aprs close(listenfd) du fils et close(connfd) du pre. Client Connect Serveur Listenfd

Serveur fils 10 connfd

Programmation Rseau en C sous Unix


IX. Gestion des serveurs fils :
Quand une connexion se termine, le processus fils qui en tait charg se termine. Or, son processus pre (le serveur) ne sarrte jamais et na aucune procdure de rcupration des codes retour de ses fils. Ceux-ci deviennent donc des zombies. Pour des raisons doptimisations on dot liminer ces zombies du systme. Pour cela, on va appeler une fonction de type wait quand un fils se termine. On va donc dtourner le signal SIGCHLD reu par le pre lui indiquant la fin dun de ses fils. Signal(SIGCHLD, fin-fils) ; Avec : Void fin-fils(int signo) { wait(NULL) ; }

Ceci pose un problme d au non stockage des signaux. En effet, si 2 fils se terminent en mme temps, le premier SIGCHLD sera trait et le deuxime perdu. Pour viter de ne pas traiter la fin de certain fils, on va appeler une fonction de type wait tant quil y a des fils morts. Pour cela, on utilisera waitpid, qui peut tre rendu non-bloquante et qui retourne 0 sil ny a pas de fils termine attendre. Void fin-fils(int signo) { While(waitpid(-1, NULL, WNOHANG)>0) } Attente de tous les processus statut option qui rend non-bloquant

Avec la gestion du signal SIGCHLD, donc linterruption ventuelle de lappel systme accept, il faut implmenter le mcanisme de relance des appels systmes bloquants interrompus (cf. cours systme). For( ; ; ) { If ((connfd = accept()) <0) { If(errno = EINTR) Si lappel systme accept a t Continue ; interrompu on revient au for 11

Programmation Rseau en C sous Unix


Else{ Printf("lerreur accept\n") ; Exit(-1) ; } } }

12

Programmation Rseau en C sous Unix


Chapitre 3 : Etude dun Client/serveur
I. Introduction

Nous allons crire un client echo et un serveur echo qui auront le fonctionnement suivant : - Le client lit une chaine sur lentre standard et lenvoie ou serveur. - Le serveur affiche la ligne reue et la renvoie au client. - Le client affiche ce quil a reu sur la sortie standard. Client stdin stdout fputs fgets read write Serveur

write

read

Le serveur est un serveur concurrent.

II.

Le client :

Une fois la connexion tablie, le client appellera la fonction traitement client : Void traitement_client (FILE *fp, int sockfd) Entre des donnes (gnralement stdin) { Char sendline[MAXLINE], recvline[MAXLINE]; While(fgets(sendline, MAXLINE, fp) != NULL) { Write(sockfd, sendlin, strlen(sendline)); If(read(sockfd, recvline, MAXLINE) == 0) { Printf("serveur termin\n"); Exit(-1); } Fputs(recvline, stdout); } } socket connecte

13

Programmation Rseau en C sous Unix


III. Lancement du Client/serveur
On va dmarrer notre serveur, puis notre client et observer ce qui se pass au niveau des sockets avec loutil netstat et loption at. 1. On lance le serveur sur le port 1234 avec lIP wildcard. netstat nous donnera : Proto tcp Recv_Q 0 Send_Q 0 Local Address *:1234 Foreign Address *:* State LISTEN

2. On lance le client qui se connecte au serveur sur le mme hte, on a : Serveur-pre Serveur-fils client Tcp Tcp tcp 0 0 0 0 0 0 * : 1234 Localhost : 1234 Localhost : 9342 *:* Localhost : 9342 Localhost : 1234 LISTEN ESTABLISHED ESTABLISHED

On peut aussi afficher les raisons du blocage dun processus avec le champ wchan de ps. Dans notre cas, on aura : Pour le serveur pre : wait_for_connect Pour le serveur fils : tcp_data_wait Pour le client : read_chan

IV.

Fin normal :

Le client envoie des donnes saisies, le serveur les lui retourne. Lutilisateur tape CTRL+D dans le client : 1. Fgets retourne NULL, la fonction traitement_client se termine. 2. Le client tout entier se termine provoquant la fermeture de tous ses descripteurs ouverts. Ce qui provoque lenvoie dun FIN au serveur qui rpond avec un ACK. (serveur en CLOSE_WAIT et client en FIN_WAIT_2) 3. Le read du fils retourne 0 donc le fils se termine et ferme ses descripteurs. Il envoie un FIN au client qui retourne un ACK (fin de la phase de dconnexion). Le client entre dans ltat TIME_WAIT. Etat TIME_WAIT Cest ltat dans lequel se trouve le pair qui a initi la fin de la connexion. La partie de la connexion qui se trouve en TIME_WAIT y reste un temps gal 2 x MSL (Maximum Segment Lifetime), c'est-dire deux fois la dure de vie maximum dun datagramme IP sur un rseau. Cet tat empche lhte qui sy trouve de reconstruire une connexion avec la mme adresse IP et le mme port. Ainsi, grce aux 2 x MSL, on est sur quaucun paquet perdu de la connexion prcdente ne sera prsent sur le rseau et donc susceptible de revenir. Lattente en TIME_WAIT vite la prsence de paquets dupliqus.

14

Programmation Rseau en C sous Unix


Cette persistance dans ltat TIME_WAIT permet aussi de sassurer que la phase de connexion TCP sest droule correctement. En effet, si un des 2 paquets de fin de connexion est perdu, il doit tre possible de le renvoyer, notamment en ce qui concerne lACK final envoy par le pair responsable de la fermeture active.

V.

Scnario 1 : Fin du processus serveur

1. On lance le serveur et le client. On teste la connexion en envoyant des donnes du client. 2. On tue le processus du serveur. Les descripteurs du serveur sont ferms, un FIN est envoy au client, qui rpond avec un ACK. 3. Il ne se passe rien chez le client qui est bloqu dans le fgets. Netstat nous informe que : Le client est en CLOSE_WAIT Le serveur fils est en FIN_WAIT2 ( la moiti de la phase de connexion).

4. On tape une ligne au clavier dans le client, ce qui le dbloque. Il envoie les donnes au serveur (le FIN du serveur indique juste quil nenverra plus de donnes), qui lui retourne un RST. 5. Le client, dans le read, ne voit pas le RST. Read retourne 0 et le client constate que le serveur est termin.

VI.

Scnario 2 : la machine serveur plante

Cest un arrt brutal de la machine aprs tablissement de la connexion. Dans ce cas, rien nest envoy au client pour le prvenir de larrt du serveur. Quand le client envoie les donnes il ne reoit pas dACK et va donc retransmettre le paquet 12 fois (environ gal 9 minutes). Finalement read retourne un erreur avec errno = ETIMEDOUT (ou EHOSTUNREACH si cest un routeur intermdiaire qui a dclar lhte inatteignable).

VII.

Scnario 3 : la machine serveur plante et redmarre

On tablit la connexion, le serveur plante, redmarre et le client envoie des donnes. Le serveur, nayant plus trace de la connexion prcdente, retourne un paquet RST au client. Read retournera une erreur avec errno = ECONNRESET.

15

Programmation Rseau en C sous Unix


Chapitre 4 : Multiplexage dEntre/Sorties
I. Introduction

Nous avons vu dans le chapitre prcdent que notre client avait grer 2 entres la fois : lentre standard et le socket. Cela posait problme notamment lorsque le client bloqu dans le fgets ne voyait pads que le processus serveur tait termin. Il faudrait que nous puissions tre prvenus par le noyau si un vnement (donnes lire, erreur, possibilit dcrire, ) se produisait sur un ou plusieurs descripteurs la fois. Cest le multiplexage d E/S.

II.

La fonction select

Cette fonction permet un processus dinformer le noyau de surveiller des descripteurs en attente dvnements, et de rveiller le processus si un ou des vnements se sont produits. Les vnements dtectables sont : Le descripteur est prt en lecture Le descripteur est prt en criture Un certain temps sest coul.

#include <sys/select.h> #include <sys/time.h> Int select(int masefdp1, fd_set readset, fd_set writeset, fd_set exceptset, struct timeval timeout); Retourne : Le nombre de descripteur prts 0 si le compteur de temps est termin -1 si erreur

Masefdp1 : indique le nombre de descripteurs surveiller. Sa valeur est gale la valeur du plus grand descripteur surveiller (masefd) laquelle on ajout 1 (p1). Readset : ensemble des descripteurs surveiller en lecture. Writeset : ensemble des descripteurs surveiller en criture. Excepset : ensemble des descripteurs surveiller en exception. Timeout : permet de grer le temps dattente de select. 16

Programmation Rseau en C sous Unix


Les ensembles de descripteurs Un ensemble de descripteurs est un tableau dentiers dans lequel chaque bit de chaque entier correspond un descripteur sur 32 bits, la vase 0 contient les descripteurs de 0 31, la case 1 de 32 63, etc La prsence dun descripteur dans lensemble est signale par la valeur 1 du bit correspondant. Cest le type fd_set qui permet de les grer de faon transparente grce des macros : Void FD_ZERO(fd_set *fds) vide lensemble fds Void FD_SET(int fd, fd_set *fds) met le descripteur fd dans lensemble fds Void FD_CLR(int fd, fd_set *fds) retire le descripteur fd de lensemble fds Int FD_ISSET(int fd, fd_set fds) teste la presence du descripteur fd dans lensemble fds

Ex : Fd_set rset; FD_ZERO(&rset); //initialization 0 de rset FD_SET(1, &rset); //Les descripteur 1, 6 et 7 sont mis dans rset FD_SET(6, &rset); FD_SET(7, &rset); Le compteur de temps timeout : Cet argument est de type Struct timeval { Long tv_sec; //nombre de secondes Long tv_usec; //nombre de microsecondes } Il y a 3 possibilits dattente : Attente infinie : select ne sera dbloqu que lorsquun des descripteurs surveills sera prt. Timeout doit tre NULL. Attente finie : si aucun des descripteurs surveills nest prt avant le temps spcifi, alors select est dbloqu et retourne 0. Pour cela, timeout.tv_sec != 0 ou timeout.tv_usec != 0. Attente nulle : select nest pas bloquant et retourne 0 si aucun descripteur nest prt. Dans ce cas, timeout.tv_sec = timeout.tv_usec = 0. 17

Programmation Rseau en C sous Unix


La notion de descripteur prt : Un descripteur sera dclar prt en lecture si : Le nombre doctets de donnes dans le buffer de rception du socket est suprieur ou gal la taille du low_water mark du buffer de rception (= taille minimale des donnes reues avant quon puisse les lire, par dfaut 1). La moiti en lecture de la connexion est ferme (on a reu un FIN) read sera alors non-bloquant et retournera 0. Il y a une erreur pendante sur le socket. Un read va tre non-bloquant et retournera 1. Le socket est passif et une connexion complte existe. Un accept sur le socket passif sera donc non-bloquant.

Un descripteur sera dclar prt en criture si : La taille de lespace libre dans le buffer dmission de la socket est suprieur ou gal la taille du low_water mark du socket dmission (2048 par dfaut). Une erreur de socket est pendante, un write sera non-bloquant et retournera -1.

III.

Amlioration du client echo

Nous allons maintenant rcrire notre client echo avec select pour grer les scnarios qui posent problme. On peut schmatiser ainsi les conditions surveiller par select. client Donne ou EOF Stdin est prte en lecture erreur EOF Cas o le socket est prte en lecture stdin

socket

RST

Donnes FIN

Algorithme du client : rset : ensemble de descripteurs initialisation de rset (FD_ZERO) Boucle client : - Mettre le descripteur dentre de donnes dans rset (FD_SET) - Mettre le descripteur de socket dans rset (FD_SET) - Calculer le paramtre maxfdp1 de select (maxfdp1 = max(descripteur entre, descripteur socket) +1) 18

Programmation Rseau en C sous Unix


select sur rset en lecture, avec temps dattente infini. Si le descripteur de socket est prt en lecture (FD_ISSET) ( descripteur de socket appartient rset) alors lecture de la socet (read) si tout sest bien pass alors affichage de ce qui a t lu lcran sinon sortie du programme Si le descripteur dentre est prt en lecture (descripteur dentre appartient rset) (FD_ISSET) alors lecture sur ce descripteur (fgets) sil y a quelque chose lire ( != NULL) alors criture dans le socket de ce qui a t lu (write) sinon fin du programme

IV.

Envoie par lots

Jusqu' prsent, nous avons considr que notre programme prenait ses donnes dentre du clavier, en mode interactif. Ce type de fonctionnement dit "stop-and-wait" ne pose pas de problme de vidage de la socket car les envois sont espacs, et la socket nest jamais totalement "remplie". Exple : Pour un RTT (Round Trip Time) de 8 units de temps, on a : T=0 Requte 1

. . . T=3 Requte 1

T=4

Rponse 1 . . T=7

Rponse 1

19

Programmation Rseau en C sous Unix


Le tube nest utilis qua 1/8 de sa capacit. Supposons que lentre de donnes soit un fichier. Les donnes vont se succder sans interruption jusqu la fin du fichier. Le tube sera entirement rempli : Requte 8 Rponse 1 Requte 7 Rponse 2 Requte 6 Rponse 3 Requte 5 Rponse 4

Si la requte 8 est la dernire ligne du fichier , fgets va retourner EOF, et le client se termine. Cela va provoquer la fermeture du socketpar le client. Or, il reste des choses lire dans le socket. La fin du fichier en signifie pas que le travail du client est termin, cela signifie juste que le client ncrira plus dans le socket. Le client doit donc fermer sa socket en criture et ainsi signaler au serveur quil ne lui enverra plus rien, mais quil peut encore lire. Pour cela, quand le client aura lu EOF sur lentre de donnes, il appellera la fonction shutdown. #include <sys/socket.h> Int shutdown(int sockfd, int howto) ;

howto : indique laction de shutdown SHUT_RD : fermeture du socket en lecture. Les donnes dans le buffer de reception sont supprims et plus aucune opration de lecture nest possible. SHUT_WR : fermeture du socket en criture (half-close). Les donnes dans le socket sont envoyes, suivis du dbut de la squence de dconnexion TCP (FIN). SHUT_RDWR : combinaison des 2 prcdents. Modification du client : Dans la version prcdente, plus rien lire en entre signifiait fin du programme. Or, ici nous allons continuer lire dans le socket, mais sans surveiller lentre de donnes dont on sait quelle est termine. On va utiliser un flag, inputeof, pour marquer la detection dun EOF sur lentre. inputeof = 0 sil ny a pas eu EOF sur lentre inputeof = 1 sil y a eu EOF sur lentre.

20

Programmation Rseau en C sous Unix


Algorithme : a. inputeof = 0 b. Initialisation de rset c. Boucle client Insertion du descripteur de socket dans rset Si (inputeof == 0) alors insertion du descripteur dentre dans rset d. Calcul de maxfdp1 e. Select sur rset en lecture avec temps infini f. Si le descripteur dentre est prt en lecture alors si la lecture retourne un EOF alors inputeof = 1 et demi fermeture en criture de la socket sinon crire les donnes lues dans le socket g. Si e descripteur de socket es prt en lecture alors si la lecture retourne 0 alors si inputeof == 1 alors fin normal sinon erreur sinon crire lcran ce qui a t lu.

V.

Serveur echo et select

Nous allons rcrire notre serveur Echo sous la forme dun seul processus capable de grer plusieurs clients la fois grce select. En fait, select surveillera le descripteur de socket passive ainsi que tous les descripteurs de sockets connectes. Ainsi, si une nouvelle connexion arrive, ou quun client connect envoie des donnes (ou se deconnecte) le noyau avertira le serveur quil a grer un vnement. Pour arriver ce rsultat, il nous faudra garder une trace de tous les descripteurs de sockets connectes. En effet, une fois select dbloqu, et aprs avoir vrifi si la socket passive en est responsable (arrive dune nouvelle connexion), on doit parcourir la liste des sockets connectes afin de vois la on lesquelles sont responsables du dblocage. On utilisera un tableau dentiers, clients, o lon rangera les descripteurs des sockets connectes. Exemple : Soit rset lensemble des descripteurs surveiller. Il est vide au lancement du programme (tout 0), ainsi que clients (tout -1). Quand le serveur cre le socket passif, les descripteurs 0, 1 et 2 tant rservs (stdin, stdout et stderr), elle aura le descripteur n3 : Rset : 0 0 0 1 0 0

stdin : 0 stdout : 1

21

Programmation Rseau en C sous Unix


stderr : 2 socket passive : 3 Client : -1 -1 -1

Un client se connecte. Le socket connect se voit attribuer le descripteur n4. On aura donc : Rset : 0 0 0 Clients : 4 -1 1 -1 1 0 0 -1

Un autre cient se connecte : Rset : Clients : 0 4 0 5 0 -1 1 1 -1 1 0 0

Deconnection du premier client : Rset : Clients : 0 -1 0 5 0 -1 1 0 1 -1 0 0

Algorithme :

Rset /*descripteur aprs select*/, allset /*tous les descripteurs surveiller*/ : ensembles de descripteurs nready, maxi /*indice max de clients*/, maxfd /*plus grand descripteur surveiller*/ : entiers . . . Maxfd = listenfd; Maxi = -1; Initialisation des cases de clients -1 Mise zro de rset et allset Listenfd est mis dans allset Boucle du server : Rset <- allset; Nready <- select sur rset; Si (listenfd appartient rset) = connexion Alors accept() Parcours de clients pour trouver la premire case libre i. On y range le descripteur connfd retourn par accept. //sauvegarde du descripteur de socket connecte dans clients. Ajout de ce descripteur dans allset. //ajout dans lensemble des descripteurs surveiller. Si (connfd > maxfd) Alors maxfd <- connfd; Si (i > maxi) Alors maxi <- i; 22

Programmation Rseau en C sous Unix


//M.A.J. des valeurs maximum Si (nready 1 <= 0) //sil ny a aucun autre descripteur prt, on revient la boucle. Alors continue; Pour i de 0 maxi Si (client[i] < 0) Alors continue; Sockfd <- client[i]; Si (sockfd appartient rset) Alors n <- lecture donc socket; Si (n == 0) = fin connexion Alors fermeture de la socket; Sockfd est retir de allset Client[i] <- -1; Sinon on crit ce quon a lu dans la socket. Si (nready 1 <= 0) //il ny a plus de descripteurs //prts Alors break; Fin boucle serveur

Ce serveur, bien que plus compliqu crire, vite de crer autant de processus que de clients. Mais il est vulnrable une attaque classique, le Denial-of-Service. Si un client se connecte et envoie un octet de donnes (par un retour chariot), le serveur va le lire et se bloquer dans read, en attente dautres donnes. Si le client ne les lui envoie pas, il est bloqu et ne pourra rpondre aux autres. Une rgle de scurit veut qun serveur ne doit jamais tre dans un fonction relative un client. Une faon de rendre ce serveur impermable cette attaque, cest de mettre une limite de temps sur les fonctions bloquantes.

23

Programmation Rseau en C sous Unix


Chapitre 5 : Option des socket
I. Getsockopt et setsockopt
Il est possible de changer le comportement par defaut des sockets. La fonction getsockopt permet de rcuprer la valeur dune option, setsockopt permet de modifier cette valeur. #include <sys/socket.h> int getsockopt( int sockfd, int level, int optname, void *optval, socklent_t *optlen); int setsockopt( int sockfd, int level, int optname, const void *optval, const socklent_t *optlen);

sockfd : descripteur de la socket dont on veut rcuprer/modifier une option. level : niveau de la socket o se situe loption. optname : nom de loption. optval : nouvelle valeur attribue loption ou pointeur o sera stocke la valeur de loption. optlen : taille de largument optval. (optval est un void * car les options peuvent prendre diffrents types de valeurs. Cest aussi pour cela quon spcifiera sa taille avec optlen).

II. Etat des sockets


Certaines options ncessitant dtre actives/dsactives un moment prcis selon ltat du socket. Les options suivantes SO_DEBUG, SO_LINGER, SO_KEEPALIVE, SO_RCVBUF, SO_RCVLOWAT, SO_SNDBUF, SO_SNDLOWAT, SO_DONTROUTE sont hrits par la socket connecte TCP de la socket passive. Pour tre sur quune de ces options soit dans ltat voulu ds la phase de connexion termine, il faut lui donner sa valeur ds la socket passive.

III. Options gnriques


Elles sont indpendantes du protocole de la socket, mme si elles ne sappliquent pas forcment toutes les sockets. 1. SO_BROADCAST Elle permet ou interdit au processus denvoyer des paquets en diffusion. Cette option ne peut sappliquer quaux sockets non connects (UDP). 2. SO_DEBUG Quand elle est active (seulement en TCP), le noyau garde une trace de tous les paquets reu ou envoys par la socket. On peut examiner ces traces avec trtp.

24

Programmation Rseau en C sous Unix


3. SO_DONTROUTE Elle forcce les paquets outrepasser les mcanismes normaux de routage. Ils setont envoys linterface locale dtermin avec la partie rseau et al partie sous-rseau de ladresse de destination 4. SO_KEEPALIVE Si ele est active pour un socket TCP, et quaucune donne na t change sur la socket pendant 2 heures, alors TCP envoie automatiquement une sonde keep_alive au pair. Celui-ci doit y rpondre. On a 3 scnarios possibles : a. Le pair rpond avec le ACK attendu. Tout va bien. b. Le pair rpond avec un RST pour informer quil est tomb et a redmarr. Le socket est ferme et lerreur ECONNRESET est retourne. c. Il ny a pas de rponse ; TCP va envoyer jusqu' 8 sondes, espace de 75 secondes. Sil ny a toujours aucune rponse aprs ces 11min 15s, il abandonne, la socket est ferme, lerreur sera ETIMEDOUT. 5. SO_LINGER Elle permet de changer le comportement par defaut de la fonction close sur le socket. Par dfaut, close retourne immdiatement et sil y a des donnes dans le buffer denvoi du socket, le systme va tenter de les faire parvennir au pair. Elle fonctionne avec la structure suivante : struct linger { int l_onoff ; int l_linger ; }

l_onoff :

0 : dsactive loption 1 : active loption

l_linger : dlai en secondes I. II. l_onaff = 0 : le comportement par dfaut de clase est conserv. l_onoff = 1 et l_linger = 0 : la connexion est avorte ds le close : les donnes restantes sont supprimes et un RST est envoy au pair. Cela vite ltat TIME_WAIT, avec les risques inhrents. l_onoff = 1 et l_linger > 0 : Lors de lappel de close, le noyau va attendre. C'est-dire que sil y a des donnes restant dans le buffer denvoi alors le processus est endormi jusqu ce quil ait reu un ACK pour les donnes envoyes ou que le temps l_linger ait expir. Ainsi, on peut tre sur que les dernires donnes envoyes ont bien t reus (mais pas si elles on t lues). 25

III.

Programmation Rseau en C sous Unix


6. SO_RCVBUF et SO_SNDBUF Elles permettent de changer la taille du buffer de reception et du buffer denvoie. Ces options doivent tre mise jour avant la connexion car elles influent sur la taille de la fentre change lors des SYN de connexion (avant connect pour un client, avant listen pour un serveur). Leur taille doit tre au minimum de 4*MSS (maximum segment size) et elle doit tre un multiple de MSS pour ne pas gaspiller despace. 7. SO_RCVLOWAT et SO_SNDLOWAT Elles permettent de modifier les low-water marks en rception et emission (voir chapitre 4). 8. SO_RCVTIMEO et SO_SNDTIMEO Elles permettent de mettre une limite de temps sur les missions et receptions (write et read). Elles utilisent une structure timeval (cf chapitre 4). 9. SO_REUSEADDR Elle a plusieurs utilits : - Elle permet un serveur de dmarrer et de se lier un port mme sil existe des connexions prcdentes tablies sur ce mme port. On rencontre cette situation dans le scnario suivant : o Un serveur est dmarr. o Il cre un fils pour traiter une connexion entrante. o Le pre meurt mais le fils continue traiter avec le client. o Le serveur pre est redmarr. Par dfaut, lors du redmarrage du serveur, le bind retourne une erreur, car le port est dj utilis. - Elle permet un nouveau serveur dtre dmarr sur le mme port quun serveur existant li ladresse wildcard, si chaque instance se lie une adresse IP locale diffrente. Ce cas de figure se prsente notamment pour un site hbergeant de multiples serveurs http avec des alias IP. Il faut des IP diffrents car TCP interdit le "completely duplicate binding". - Elle permet un processus de lier le mme port plusieurs sockets condition de spcifier un adresse IP local diffrente, Ceci est notamment utilis par les serveurs UDP. - Elle permet le "completely duplicate binding" avec les sockets UDP, ce qui est utilis dans le multicasting.

IV. Options au niveau IPv4


Le niveau sera IPPROTO_IP dans getsockopt et setsockopt. 1. IP_OPTIONS Elle permet de modifier les options dans lentte IPv4. Il faut une connaissance intime dIPv4 pour lutiliser. 2. IP_TTL Elle permet de modifier le TIME TO LIVE dun paquet.

V. Options au niveau TCP


Le niveau sera IPPROTO_TCP dans getsockopt et setsockopt.

26

Programmation Rseau en C sous Unix


1. TCP_MAXSEG Elle permet de rcuprer ou modifier le MSS dun connexion TCP. Le Maximum Segment Size est la quantit maximale de donnes que TCP enverra au pair. Cest une valeur infrieur ou gal au MSS envoy par le pair dans le SYN de connexion. On ne peut augmenter la valeur du MSS. 2. TCP_NODELAY Quand elle est active, cette option inhibe lalgorithme Nagle de TCP, qui est activ par dfaut. Le but de Nagle est de rduire le nombre de paquets de petite taille sur les WAN. Le principe est que sil y a des donnes en attente dacknowledgement sur une connexion, alors aucun petit paquet ne sera envoy en rponse un write avant que les donnes existantes naient t accuses. Un petit paquet est un paquet de taille infrieur au MSS. Nagle va permettre dviter quil y ait de multiples petits paquets en attente acknowledgement en mme temps. Telnet gnre de nombreux petits paquets car il en cre un par caractre taps. Nagle est indcelable sur un LAN mais sur un WAN, le temps dacknowledgement entre chaque caractre peut crer un log dans la saisie. Exemple : On tape 6 caractres "Hello!" dans telnet avec 250ms entre chaque caractre. Le RTT du serveur est 600ms. Le serveur retourne immdiatement au client le caractre reu avec le ACK. Avec Nagle :

h 0 e 250 l 500 l 750 o 1000 ! 1250 1500 1750 2000


Sans Nagle :
0 h e 250 l 500 l 750 o 1000 ! 1250 1500 1750 2000

h
600 ms

ACK + h
600 ms

ACK + e
600 ms

ACK + l

27

Programmation Rseau en C sous Unix


Nagle peut aussi interfrrer avec un autre algo, celui des ACK retards. Ce dernier amne TCP ne pas envoyer un ACK ds la reception de donnes, mais attendre de 50 200ms. Ainsi, il y a des chances que dans cet intervalle, il y ait des donnes joindre au ACK. Il se pose alors un problme pour les clients auxquels le serveur ne retourne pas de donnes car ils ne pourront, cause de Nagle, envoyer de donnes au serveur avant de recevoir son ACK retard. Ils doivent donc dsactiver Nagle.

28

Programmation Rseau en C sous Unix


Chapitre 6 : Threads
I. Introduction
Jusqu prsent, pour crer un serveur concurrent, nous avons utilis la cration de processus avec fork afin de grer les connexions indpendemment de leur tablissement. Nanmoins, les processus peuvent poser quelques problmes : La cration de processus est une opration couteuse en temps machine : duplication de zones mmoires, de descripteurs, etc Pour passer des informations entre un pre et un fils, il faut utiliser des mcanismes spcifiques dIPC.

Les threads (ou processus lgers) permettent de rsoudre ces 2 problmes : La cration dun thread prend de 10 100 fois moins de temps que celle dun processus. Les threads dun mme processus partagent le mme espace mmoire global.

Un thread est un fil dexcution dans un processus. Un processus possde au moins un thread.

II. Rappels
1) Donnes partages Les threads, en plus de lespace mmoire globales, partagent dautre donnes : Le code Les descripteurs de fichiers ouverts Les gestionnaire de signaux Le rpertoire courant UID et GID

Chaque thread son propre : Identifiant (TID) Compteur de programme Pile (variables locales et appels de fonctions) Errno Masque de signaux

2) Cration et fin de thread Un thread est cr par la fonction : 29

Programmation Rseau en C sous Unix


#include <pthread.h> int pthread_create( pthread_t *tid, const pthread_attr_t *attr, void *(*func)(void*), void *arg);

tid : identifiant du thread retourn si la cration a fonctionn. attr : attributs du thread (gnralement initialis NULL on la valeur par dfaut). func : fonction support du thread c'est--dire le code excut par le thread. Elle prend en argument un pointeur gnrique arg et retourne un pointeur gnrique. On pourra ainsi lui passer les donnes que lon veut et lui faire retourner ce que lon veut. arg : argument de func. Exemple : pthread_t tid; pthread_createt(&tid, NULL, serr-fils, (void*) connfd);

Un thread peut se terminer de plusieurs faons : La fonction support du thread appel return. La valeur de retour sera alors le statut de sortie du thread. Si la main du processus se termine ou si un des threads du processus appelle exit. En appelant la focntion void pthread_exit(void *status). La valeur de status sera rcupre par pthread_join. ATTENTION : status ne doit pas pointer sur un objet local, celui-ci tant dtruit avec les thread. 3) Gestion des threads Un thread peut rcuprer son identifiant avec la fonction : pthread_t ppthread_dekf() ? On peut attendre la fin dun thread avec la fonction : int pthread_join( pthread_t tid, void *status). tid : identifiant du thread attendre status : si status est non-nul, il contiendra le statut de sortie de return ou de pthread_exit. Un thread peut avoir 2 tats : joint (par dfaut) ou dtach. Quand un thread joint se termine, son tid et son statut de sortie sont concerns jusqu ce quun autre thread appelle pthread_join. Un thread dtach agit comme un processus dmon : quand il se termine, toutes ses ressources sont libres et on ne peut lattendre. On rend un thread dtach avec : int pthread_detach(pthread_t tid). 30

Programmation Rseau en C sous Unix


III. Client Echo et threads
Nous allons rcrire le client Echo vu prcdemment sous 2 formes (stop-and-wait et select) avec des threads. On aura un thread charg de lire lentre de donnes et denvoyer le rsultat dans le socket et un thread charg de lire dans la socket et dafficher le rsultat.

stdout

thread "main"

pthread_create

Serveur

Stdin

thread "envoie"

Il faudra que le thread "envoie" ait accs 2 donnes du thread principale : Le pointeur de fichier dentre, pass en paramtre au programme. Le descripteur de socket connecte.

Pour cela, il y a 2 solutions : 1. Dclarer les 2 variables en global. 2. Les mettre dans une structure et la passer au thread dans le pthread_create. Client : int sockfd_g ; //En global FILE *fp_g ; //En global void client(FILE *fp, int sockfd) { char recvline[MAXLINE] ; pthread_t tid; sockfd_g = sockfd ; fp_g = fp ; pthread_create(&tid, NULL, envoie, NULL); while(read(sockfd, recvline, MAXLINE)>) fputs(recvline, stdout); } void *envoie(void *arg) { char sendline[MAXLINE]; while(fgets(sendline, MAXLINE, fp_g) != NULL) 31

Programmation Rseau en C sous Unix


write(sockfd_g, sendline, strlen(sendline)); shutdown(sockfd, SHUT_WR); return(NULL); }

IV. Serveur Echo et threads


Nous allons rcrire notre serveur Echo concurrent en remplaant la cration de fils avec fork par la cration de threads. Code : . . . for( ; ; ) { connfd = accept(listenfd, cliaddr, &len) ; pthread_create(&tid, NULL, &serv, (void *) connfd) ; } void *serv(void *arg) { pthread_detach(pthread_self()); serveur_echo((int) arg); close((int) arg); }

On passe connfd en paramtre la fonction support des threads, serv, en le transtypant en void *. Dans cette fonction, chaque thread commence par se dtacher du tread principal. Ainci, celui-ci naura pas les attendre. Ensuite, la fonction de traitement, serveur-echo, est appele avec le descripteur de socket connecte pass via arg. Enfin, chaque thread doit fermer explicitement le socket connecte (la fin dun thread nentraine pas la fermeture de ses descripteur comme avec les processus). Cette version peut paser quelques problmes sur les systmes ou le transtypage dun entier par un void * ne fonctionne pas (cest le cas si sizeof(int)<=sizeof(pointeur)). Il faut donc modifier le passage dargument pour rendre notre serveur portable. La solution la plus simple serait de passer comme argument au thread ladresse de connfd : pthreadd_create(&tid, NULL, &serv, &confd) ; void *serv(void *arg) { int connfd; 32

Programmation Rseau en C sous Unix


connfd = *((int *) arg); . . . }

Dans le thread principal la valeur de connfd sera crase chaque nouvelle connexion, mais son adresse sera la mme. Tous les threads rcupreront la valeur de connfd la mme adresse, ce qui peut poser des problmes selon lordonnancement : Une connexion est tablie, accept retourne le descripteur n 6 pour connfd. Le thread 1 est cr et on lui passe ladresse de connfd. Avant que le thread ne dmarre, une nouvelle connexion stablit et le thread principale rcupre la main . Accept retourne connfd = 7. Le thread 2 est cr et on lui passe ladresse de connfd. Quand thread 1 reprendra son xecution , il rcuprera la valeur de connfd ladresse quon lui a pass, et on aura connfd = 7. Ce sera la mme chose pour thread 2. Les 2 threads agiront sur la mme connexion.

Pour viter cela, on va stocker connfd une adresse diffrente pour chaque connexion. Version portable : int *pconnfd ; for( ; ; ) { pconnfd = (int *) malloc (sizeof (int)) ; *pconnfd = accept(listenfd, cliaddr, &len); pthrread_create(&tid, NULL, &serv, pconnfd); } void *serv(void *arg) { int connfd; connfd = *((int *) arg); free(arg); . . . }

33

Programmation Rseau en C sous Unix


Chapitre 7 : Entres /Sorties avances
I. Temporisation et socket
Nous allons voir comment introduire une limite de temps sur une opration dE/S sur un socket. Il y a 3 mthodes pour cela : Utiliser la fonction alarm qui envoie au processus appelant le signal SIGALARM aprs un nombre de secondes dfinies. Utiliser la fonction select, qui permet de dfinir un temps maximal dattente, au lieu de read ou write. Utiliser les options SO_RCVTIMEO et SO_SNDTIMEO.

Ces trois mthodes fonctionnement avec les oprations de lecture et criture. Il peut aussi tre intressant de placer une lilmite de temps sur le connect. Or, les options ne fonctionnent pas sur connect et select ne fonctionne avec connect que si la socket est non bloquante. 1) Limite de temps sur connect On utilise la mtode "alarm". On va construire une fonction connect_time qui prendra 4 arguments : les trois de connect et un nombre de secondes. Le code de cette fonction sera : signal(SIGALARM, sig_connect); alarm(nsec); if((n = connect(sockfd, servaddr, len)) < 0) //si connect a t { //interrompu close(sockfd); if(errno == EINTR) { printf("connect timeout\n"); errno = ETIMEDOUT; } alarm(0); void sig_connect(int s) //traitement du signal { return; }

2) Limite de temps avec select Par exemple, pour placer une limite de temps avec select, sur une opration de lecture, on va crire une fonction qui dira si un descripteur est prt en lecture dans un intervalle de temps donn. 34

Programmation Rseau en C sous Unix


int readable_timeo(int fd, int nbsec) { fd_set rset; struct timeval tr; int nb; FD_ZERO(&rset); //fd surveiller FD_SET(fd, rset); //en lecture. tv.tv_sec = nbsec; //Temps tv.tv_usec = 0; //maximal. nb = select(fd + 1, &rset, NULL, NULL, &tr); Return(nb); }

Si readable_timeo retourne 0, cest que le temps sest coul sans que le descripteur soit prt, sinon, on peut effectuer lopration de lecture. 3) Limite de temps avec SO_RCVTIMEO : On utilise l'option de socket SO_RCVTIMEO pour mettre une limite de temps en rception. Pour cela, on dfinit le temps maximal dans une structure timeval et on appelle setsockopt sur le descripteur du socket. Par rapport aux solutions prcdentes, on active l'option une fois et la limite de temps s'applique pour toutes les lectures. On ne peut par contre, avec cette mthode, ne grer le temps que sur une lecture (SO_RCVTIMEO) ou une criture (SO_SENDTIMEO), pas sur un connect. Exple : void fonc_cli( FILE *fp, int sockfd){ int n; char sendline[MAXLINE], recvline[MAXLINE+1]; struct timeval tv; tv.tv_sec = 5; //limite de tv.tv_usec = 0; //temps = 5 secondes setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); //Active l'option while(fgets(sendline, MAXLINE, fp) != NULL){ write(sockfd, sendline, strlen(sendline)); n = read(sockfd, recvline, MAXLINE); if(n < 0){ if(errno == EWOULDBLOCK){ //erreur indiquant le dpassement de temps limite prinft("Temps dpass\n"); continue; }else{ printf("erreur\n"); exit(-1); } } recline[n] = 0; 35

Programmation Rseau en C sous Unix


} }

II.

Lecture et criture avances :

a. recv et send : Ce sont des fonctions quivalentes read et write avec en argument supplmentaire. #include <sys/socket.h> ssize_t recv(int sockfd, char *buff, ssize_t taille, int flags) ssize_t send(int sockfd, char *buff, ssize_t taille, int flags)

sockfd, buff, taille sont identique ceux de read et write. flags : option supportes : - MSG_DONTROUTE : informe le noyau que la destination est locale et qu'il n'a pas consulter les tables de routages. - MSG_DONTWAIT : rend l'opration non bloquante. - MSG_PEEK : permet de regarder les donnes disponible dans le socket sans les enlever. - MSG_WAITALL : indique au noyau qu'il doit attendre que le nombre d'octets demands soit effectivement lus avant que recv ne se termine. b. readv et writev : Similaires read et write, elles permettent de lire ou d'crire partir de ou vers plusieurs buffers en un seul appel. C'est le scatter read (les donnes d'entre sont disperses dans de multiples buffers) et le gather write (les donnes crire sont runies partir de plusieurs buffers). #include <sys/uio.h> ssize_t readv(int filedes, struct iovec *iov, int iocvnt) ssize_t writev(int filedes, const struct iovec*iov, int iocvnt) struct iovec{ void *iov_base; size_t iov_len; } //adresse du buffer //taille du buffer

iov est un tableau de structures iovec. Ce sont les buffers o seront ranges les donnes lues. iovcnt est le nombre de cases de ce tableau. c. recvmsg et sendmsg : Ce sont les fonctions d'E/S les plus volues. Elle peuvent remplacer toutes les autres vues prcdemment. #include <sys/socket.h> ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags) ssize_t sendmsg(int sockfd, struct msghdr *msg, int flags)

Les arguments sont stocks dans la structure msghdr. 36

Programmation Rseau en C sous Unix


struct msghdr{ void *msg_name; socklen_t msg_namelen; struct ovec *msg_iov; int msg_iovlen; void *msg_control; socklen_t msg_controller; int msg_flags; } //Utiliss par les //sockets non connects //Tableau pour le scatter/gather //Nombre de cases de ce tableau //Pour les donnes //ancillaires (ce contrle) //Option : utilis uniquement //par recvmsg

L'argument flags est utilis comme dans recv et send.

37

Programmation Rseau en C sous Unix


CHAPITRE 8 : Etude de diffrents C/S
I.
-

Introduction :
Le serveur itratif : 1 seul processus. Le serveur concurrent multi-processus. Le serveur concurent multi-threads.

Il y a plusieurs possibilits pour crire un serveur. Les solutions vues jusqu' maintenant sont :

Nous allons dcrire deux nouvelles solutions : Le serveur "prfork" : au dmarrage, le serveur cre un ensemble de processus qu'il distribuera au fur et mesure des connexions. Le serveur "prthread" : idem avec des threads.

Nous testerons ensuite toutes ces possibilits afin de les comparer.

II.

le client de test :

Afin de faire une comparaison quitable, nous utiliserons le mme client pour tous les serveurs. Ce client initiera un grand nombre de connexions simultanes demandant un nombre d'octets dfinis. la ligne de commande du client sera : client IPdest Port nbclient nbconnexions nboctets

nbclient : nombre de client simultan nbconnexions : nombre de connexions par client nboctets : nombre d'octets demands

III.

le client de test :

Pour comparer de manire significative les performances de nos serveurs, nous allons utiliser la fonction getrusage qui retourne l'utilisation de ressources d'un processus, et ventuellement de ses fils. On aura la fois le temps utilisateur total et le temps systme total. Nous allons donc utiliser une fonction quicalculera ces 2 temps. Elle sera appele quand l'utilisation interrompera le serveur avec le signal SIGINT (CTRL + C), ce qu'il fera quand le client aura termin ses requtes. La fonction de calcul de temps sera : void pr_cpu_time(){ double user, sys; struct rusage myusage, childusage; getrusage(RUSAGE_SELF, &myusage); //rcupration du temps du 38

Programmation Rseau en C sous Unix


processus. getrusage(RUSAGE_CHILDREN, &childusage); //rcupration du temps de ses fils. user = (double)myusage.ru_utime.tv_sec + myusage.ru_utime.tv_usec; user += (double)childusage.ru_utime.tv_sec + childusage.ru_utime.tv_usec; sys = (double)myusage.ru_stime.tv_sec + myusage.ru_stime.tv_usec; sys += (double)childusage.ru_stime.tv_sec + childusage.ru_stime.tv_usec; printf("temps utilisateur = %g, temps systme = %g\n", user, sys); Le serveur devra donc appeler cette fonction quand CTRL + C sera press par l'utilisateur.

IV.

Serveur pr-fork :

Au lieu de crer un fils chaque connexion demand, le serveur va crer un ensemble de fils ds son dmarrage. Ainsi, il passera chaque connexion un de ses fils existants. L'avantage principal, c'est que les clients n'ont pas attendre le temps d'un fork pour changer avec le serveur. Le problme, c'est celui du nombre de fils crer initialement. Si le nombre de clients gale celui des fils, alors les clients suivants devront attendre la fin d'une connexion avant d'tre servis. Une solution serait de surveiller le nombre de fils disponibles et s'il descend sous une limite fixes, on en cre de nouveaux. De mme, si trop de fils sont inutiliss, on en supprime quelques-uns. Le pre aura le fonctionnement suivant : Cration de la socket passive. Cration de n fils et sauvegarde de leurs PID dans un tableau. Interception du signal SIGINT : mettre fin tous les fils. calculer les temps d'excutions. Mise en pause.

Les fils auront la structure d'un serveur classique : boucle infinie : accept traitement de la connexion.

Tous les N fils partagent la mme socket passive. De plus, ils sont tous bloqus dans l'appel accept. Lorsque la premire connexion arrive, tous les fils sont rveills, un rcupre la connexion et N-1 se rendorment.

39

Programmation Rseau en C sous Unix


S'il y a trop de fils, cela peut affecter le performances. C'est le "Thundering herd" problem.

V.

Serveur prthread (un accept par thread) :

Nous allons crer un certains nombre de threads au dmarrage du processus. Chacun des threads appellera la fonction accept, mais, pour viter le "thundering herd" problem, on va donner un accs en exclusion mutuelle le fonction accept. Ainsi, seul un thread sera endormi par le accept et donc sera rveill par l'arrive d'une connexion. Les autres seront endormi dans le mutex. Le thread principal fonctionnera comme le serveur pre prcdent. Chaque thread aura le comportement suivant : boucle infinie : acquisition du mutex accept libration du mutex gestion de la connexion

Mme si cette exclusion mutuelle n'est pas obligatoire, les tests dmontrent que cette version est moins gourmande que sans exclusion mutuelle.

VI.

Serveur prthread (un accept dans le main)

Notre serveur va crer un ensemble de threads son dmarrage mais nous naurons quun accept dans le thread principal. Le problme qui se pose, cest comment passer le descripteur de socket connecte un des threads disponibles. Il nest pas ncessaire de passer le descripteur, il suffit que le thread obtienne le numro du descripteur qui lui est attribu. On va crer un tableau appel clifd o le thread principal stockera les descripteurs de sockets connectes. Pour se localiser dans le tableau, on utilisera 2 indices : iput qui indiquera o stocker le prochain descripteur et igets qui indiquera o se trouve le prochain descripteur disponible pour les threads. Les accs ce tableau de vront se faire en exclusion mutuelle. iput, iget et difd seront en effet partags par tous les threads. Dans le thread principal, il faudra, chaque nouvelle connexion, vrifier que iput na pas rejoint iget. Si cest le cas, cest que le tableau est trop petit. Dans les threads secondaires, si iget == iput, elles nont rien faire, si ce nest attendre que cela change. Elles sendormiront dans avec une variable conditionnelle. Cest le thread principal qui les rveillera. Thread principal : //cration socket for( ; ; ){ connfd = accept(...); pthread_mutex_lock(&clifd_mutex); 40

Programmation Rseau en C sous Unix


clifd[iput] = connfd; if(++iput == MAXNCLI) iput = 0; //iput est arriv la fin du tableau if(iput == iget){ printf(erreur : iput = iget = %d\n,iput); exit(-1) ; } pthread_cond_signal(&clifd_cond); //on signale que le tableau a t modifi pthread_mutex_unlock(&clifd_mutex); } Thread secondaire : for( ; ; ){ pthread_mutex_lock(&difd_mutex); while(iget == iput) pthread_cond_wait(&clifd_cond); //plus de connexion traiter donc pause. connfd = clifd[iget]; if(++iget == MAXNCLI) iget = 0; //iget atteint la fin du tableau pthread_mutex_unlock(&clifd_mutex); serv(connfd); //fonction de traitement de la connexion close(connfd); }

VII.

Conclusion

Les rsultats des mesures, pour 5 clients simultans rclamant 4000 octets de donnes au serveur, et ce, 500 fois chacun (soit 2500 connexions TCP) sont, du plus lent au plus rapide : Serveur concurrent avec cration dun fils par connexion Serveur prthread avec un accept dans le main. Serveur prthread avec un accpet par thread. Serveur prfork Serveur multithread.

Les 4 derniers sont environ 10 fois plus rapide quele serveur concurrent.

41

Programmation Rseau en C sous Unix


Chapitre 9 : Sockets UDP
I. Introduction :
Il y a des diffrences fondamentales entre UDP et TCP, qui se retrouvent lorsquon crit des applications : UDP est non-connects et non-fiable. Les fonctions appeler pour un client/serveur UDP sont :

Client UDP

Serveur UDP

Socket

bind Socket

recvfrom

Sendto Bloque jusqua recevoir un datagramme

donnes

Gestion des donnes reus

recvfrom

donnes

Sendto

close

42

Programmation Rseau en C sous Unix


II. Les fonctions recvfrom et sendto :
Elles sont similaires read et write mais recquirent 3 arguments supplmentaires : #include <sys/socket.h> ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen) Les 3 premiers aurguments sont les mmes que ceux de read. flags : voir cours E/S avances . from : contiendra les coordonnes de la provenance des donnes reues. addrlen : contient la taille de la structure dadresse de lexpditeur. Ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags, const struct sockaddr *to, scklen_t addrlen) Les 3 premiers arguments sont les mmes que ceux de write. to : coordonnes du destinataire du datagramme addrlen : taille de to les 2 fonctions retournent la taile des donnes lues ou crites. Il est possible denvoyer un datagramme de taille 0 : il y aura 20 octets dentte IP, 8 octets dentte UDP et 0 octets de donnes. Un recvfrom qui retourne 0 ne signifie pas que le pair a ferm la "connexion".

III.

Serveur Echo UDP :


fgets sendto recvfrom

Client UDP
fputs recvfrom sendto

Serveur UDP

Les diffrences avec le serveur Echo TCP sont : Le second argument de la fonction socket vaudra SOCK_DGRAM. Aprs le bind, plus de accept, le serveur se met immdiatement en listen.

Ce serveur, comme la plupart des serveurs UDP, sera itratif. Il y a donc un seul socket, et les datagrammes reus seront stocks dans une file dattente FIFO, quelle que soit leur provenance.

IV.

Client Echo UDP :

La aussi le 2me argument de socket vaudra SOCK_DGRAM. On nutilise plus la fonction connect, mais directement sendto. Le port phmre quutilisera le client, dtermin en TCP lors dun connect, sera, en UDP, donn par le premier appel sendto.

43

Programmation Rseau en C sous Unix


V. Authentification de la rponse :
Tout hte connaissant le port dcoute du client peut lui envoyer des rponses, qui seront mlangs aux vraies rponses du serveur.pour authentifier la rponse, il faut rcuprer avec recvfrom ladresse de provenance de chaque datagramme et le comparer avec celle de destination. Exple : ... sendto(sockfd, sendline, strlen(sendline), 0, &servaddr, servlen); len = servlen; n = recvfrom(sockfd, recvline, MAXLINE, 0, &reply, &len); if(len != servlen || memcmp(&servaddr, &reply, len) != 0){ printf("rponse de provenance inconnue\n"); continue; } ... Cette mthode fonctionne parfaitement avec un serveur qui a une seule adresse IP, mais peut poser des problmes avec un serveur plusieurs interfaces. Si ce type de serveur se lie ladresse wildcard, ce sont les fonctions de routage du noyau qui choisiront ladresse de sortie pour rpondre au client. Ainsi, si la rponse ne provient pas de la mme interface contacte par le client, le datagramme sera refus de faon errone. Pour rsoudre cela, il faut crer un socket par adresse IP du serveur et le lier explicitement cette adresse. On les surveillera avec un select. Les rponses seront bien envoyes de la socket qui les a reues.

VI.

Erreur de serveur :

Nous allons examiner ce qui se passe si le client envoie des donnes alors que le serveur nest pas dmarr. Le client appelle sendto et se bloque dans recvfrom. Sendto, mme sil na pas pu fonctionner correctement, re retourne pas derreur. Avec tcpdump, on saperoit quune erreur ICMP "port unreachable" a t gnre lors de lenvoie. Cette erreur, dite asynchrone, nest pas retourne au client. En effet, lappel recvfrom ne permet pas de rcuprer le paquet ICMP. Ce type derreur est signal uniquement lorsquon utilise connect en UDP.

VII.

Connect et UDP :

On peut utiliser la fonction connect avec un socket UDP. Cela nentraine pas une connexion comme avec TCP, mais permet de rcuprer les erreurs et denregistrer ladresse IP et le port du pair. Il y a 3 diffrences entre les sockets UDP connects et non-connects : On ne peut plus spcifier pour chaque envoie le destinataire. On nutilisera plus sendto mais write et send.

44

Programmation Rseau en C sous Unix


Il nest plus ncessaire de rcuprer les coordonnes de lexpditeur lors dune rception. On utilisera read et recv la place de recvfrom. Les erreurs asynchronnes seront transmises au processus. -

Un client ou serveur UDP utilisera connect sur un socket UDP seulement sil utilise cette socket pour communiquer avec un hte unique. (Exple : un client DNS configur pour contacter quun serveur).

VIII.

Serveur TCP et UDP :

Nous allons crire un serveur capable de grer des requtes TCP et des requtes UDP. Nous combinerons un serveur concurrent TCP et un serveur itratif UDP. Pour cela, on va crer 2 sockets, une UDP et une TCP, et on ca surveiller les 2 descripteurs avec a fonction select. Une fois les 2 sockets cres, on aura : FD_ZERO(&rset); maxfdp1 = max(udpfd, listenfd)+1; for( ; ; ){ FD_SET(listenfd, &rset); FD_SET(udpfd, &rset); if((nready=select(maxfdp1, &rset, NULL , NULL, NULL))<0){ if(errno == EINTR){ Continue; } else{ printf(erreur select\n); exit(-1); } if(FD_ISSET(listenfd, &rset)){ len = sizeof(cliaddr); connfd = accpet(); if((pid = fork()) == 0){ close(listenfd); traitementserveur(connfd); exit(0); } Close(connfd); } if(FD_ISSET(udpfd, &rset)){ len = sizeof(cliaddr); n = recvfrom(); sendto(); } } }

IX.

Choix TCP/UDP :

Mme en connaissant les diffrences entre TCP et UDP (la fiabilit), on peut se poser la question : quand utiliser UDP la place de TCP ? Les avantages dUDP sont les suivants :

45

Programmation Rseau en C sous Unix


Il permet le multicasting et le broadcasting, pas TCP. Pour un simple change requte/rponse, il ne demande que 2 paquets, contre 11 pour TCP si cest une nouvelle connexion ;

De son cot, TCP apporte des fonctionnalits par dfaut qui devront tre ajoutes UDP en cas de besoin : Accuss de rception, retransmission des paquets perdus, dtection des doublons et squenage des paquets rordonns. Contrle du flux de donnes par le receveur (fentrage). Contrle du flux par lexpditeur (congestion avoidance).

Pour rsumer : UDP doit tre utiliser pour : o Multicasting et broadcasting : Lajout de contrle derreur en multicasting est compens par le gain de performances entre envoyer le paquet N destinataires et envoyer N paquets via N connexion TCP. UDP peut tre utilis pour : o Des applications du type requte/rponse. Nanmoins, il faut ajouter le contrle derreur, soit, au minimum, accuss de rception, dlais et retransmission. Le contrle de flux est gnralement inutile si les requtes et rponses ne sont pas trop grandes. UDP ne devrait pas tre utilis pour : o Des transferts important de donnes . En effet, lajout de toutes les fonctionnalits de base de TCP revient rinventer TCP. Une exception ce dernier point est TFTP. Son utilisation courante est le chargement du bootstrap sur des terminaux. Or, le code requis en UDP est 5 fois moins long q uen TCP et les transferts ne se font que sur un LAN, pas sur un WAN.

X.

Fiabilit et UDP :

Comme on vient de le voir, si on veut crer une application requte/rponse en UDP, il faut implmenter 2 fonctionnalits supplmentaires notre client/ Les numros de squences pour vrifier quune rponse correspond bien une requte. Les dlais et retransmissions pour grer les paquets perdus.

La plupart des applications UDP existants utilisent ces 2 fonctionnalits : DNS, TFTP, SNMP et RPC. Lajout des numros de squences est facile. Il suffit, dans chaque requte, daccoler un numro de squences que le serveur retournera avec la rponse. 46

Programmation Rseau en C sous Unix


Le problme qui se pose, cest pour le dlai avant retransmission. En effet, le temps requis pour un datagramme pour effectuer un aller-retour varie normement selon le rseau, la distance et lencombrement, et varie dans le temps. On utilise donc pas directement le RTT pour estimer le dlai. On utilisera le RTO (Recovery Time Objective) calcul partir des RTT. Il se calcule avec lalgorithme de jacobson, qui introduit aussi la notion dattente exponentielle : si un RTO de 2s ne suffit pas recevoir la rponse, le prochain sera de 4s, etc... Nanmoins, il subsiste un problme : lambigut de retransmission. 3 scnarios peuvent se produire :

requte
Perte de la requte RTO retransmission requte

RTO

Perte de la rponse

requte
rponse

rponse

Perte de la requte

Perte de la rponse

requte RTO rponse requte rponse

RTO trop court


Quand le client reoit une rponse pour une requte retransmise, il ne peut savoir quelle requte elle correspond.

47

Programmation Rseau en C sous Unix


Chapitre 10 : Les raw sockets
I.
-

Introduction
Elles permettent dcrire des paquets ICMP et IGMP. Elles permettent un processus de pouvoir recevoir et mettre des datagrammes IPv4 avec un champ protocole non support par le noyau. (La plupart des noyaux ne supportent que les valeurs suivants pour ce champ : 1 (ICMP), 2 (IGMP), 6 (TCP), 17 (UDP)). Par exemple un champ protocole gal 89, le programme qui limplmente utilise donc une raw socket. Elles permettent un processus de crer sa propre entte IPv4, grce loption IP_HRDINCL.

Elles apportent des fonctionnalits supplmentaires par rapport aux sockets classiques TCP et UDP :

II.

Cration dune socket RAW

Les tapes permettent de crer une raw socket sont : - La fonction socket prend comme second argument la valeur SOCK_RAW : int sockfd; sockfd = socket(AF_INET, SOCK_RAW, protocole); Seul le super-utilisateur peut crer des sockets raw. - Si on veut crer sa propre entte IP, on doit activer loption IP_HDRINCL : const int on = 1; setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on));

Bind est trs rarement utilis sur une raw socket. Il ny a pas de notion de port. Si bind est utilis, cest uniquement pour donner ladresse de sortie des datagrammes. Connect est rarement utilis sur une raw socket et permet dutiliser write au lieu de sendto, ladresse de destination tant donne.

III.
-

Envoi sur une socket raw


Lenvoie se fait avec sendto ou sendmsg en spcifiant ladresse de destination. Si la socket est connecte, ou utilise write, writev ou send. Si loption IP_HDRINCL nest pas mise, ladresse de dpart des donnes envoyer sera celle du premier octet suivant lentte IP. Si loption IP_HDRINCL est active, ladresse de dbut des donnes envoyer par le noyau sera celle de lentte IP. La taille des donnes crire devra inclure celle de lentte. Le processus doit rcrire toute lentte IP sauf : o Le champ didentification IPv4 qui peut tre mise 0 pour laisser le noyau le remplir. 48

Il suit les rgles suivantes :

Programmation Rseau en C sous Unix


o Le checksum qui est calcul par le noyau. o Les options IP. o Le noyau fragmente les paquets raw qui dpassent le MTU de linterface de sortie. o Avec IPv4, cest lutilisateur de calculer les checksums ventuels contenus aprs lentte IPv4.

IV.

Rception sur une socket raw

La premire chose savoir, cest quels sont les paquetsreus que le noyau transmet une socket raw ? On a les rgles suivantes : Aucun paquet TCP ou UDP nest pass par une socket raw. La plupart des paquets ICMP sont passs une socket raw, except les paquets echo request, timestamp request et address mask request. Tous les paquets IGMP sont passs une socket raw, aprs que le noyau ait trait le message IGMP. Tous les paquets dont le champ protocole nest pas comprhensible par le noyau sont passes une socket raw. Le noyau neffectue que des vrifications minimales sur lentte IP : version IP, le checksum IPv4, la taille de lentte et ladresse IP de destination.

Quand le noyau doit passer un paquet IP une raw socket, il examine toutes les raw socket du systme et transmet le paquet toutes les raw sockets correspondantes. Si tous les tests suivants sont concluants, le paquet sera transmis la ran socket : Si un protocole non-nul est spcifi la cration de la socket raw, elle ne recevra que les paquets avec ce protocole. Si une adresse locale est lie la socket avec bind, selon les datagrammes avec cette adresse comme destinataire seront passs. Si une addresse distante a t spcifie avec connect, alors seuls les datagrammes provenant de cette adresse seront transmis.

Si une socket raw est cre avec un protocole de 0, et quelle nappelle ni bind, ni connect, alors elle recevra tous les datagrammes que le noyau passe aux raw sockets.

49

Vous aimerez peut-être aussi