Vous êtes sur la page 1sur 94

Programmation Syst`me et Rseau sous Unix e e

M. Billaud <billaud@info.iuta.u-bordeaux.fr> 18 Octobre 2002

Ce document est un support de cours pour les enseignements de Syst`me et de Rseau. Il prsente quelques appels e e e syst`me Unix ncessaires ` la ralisation dapplications communicantes. Une premi`re partie rappelle les notions e e a e e de base indispensables ` la programmation en C : printf, scanf, exit, communication avec lenvironnement, a allocation dynamique, gestion des erreurs. Ensuite on prsente de faon plus dtailles les entres-sorties gnrales e c e e e e e dUNIX : chiers, tuyaux, rpertoires etc., ainsi que la communication inter-processus par le mcanisme des sockets e e locaux par ots et datagrammes. Viennent ensuite les processus et les signaux. Les mcanismes associs aux e e threads Posix sont dtaills : smaphores, verrous, conditions. Une autre partie dcrit les IPC, que lon trouve e e e e plus couramment sur les divers UNIX : segments partags smaphores et les de messages. La derni`re partie e e e aborde la communication rseau par linterface des sockets, et montre des exemples dapplications client-serveur e avec TCP et UDP.

Contents
1 Avant-Propos 1.1 1.2 1.3 Objectifs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Copyright . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Obtenir la derni`re version . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e 4 4 4 4 5 5 5 5 6 6 7 8 9 10 10 11 12 14 14 15

2 Rappels divers 2.1 2.2 2.3 printf()/scanf() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . sprintf()/sscanf() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

system() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

3 Communication avec lenvironnement 3.1 3.2 3.3 3.4 Param`tres de main(...) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e getopt() : analyse de param`tres de la ligne de commande . . . . . . . . . . . . . . . . . . . e Variables denvironnement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . exit() : Fin de programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

4 Erreurs 4.1 4.2 errno, perror() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Traitement des erreurs, branchements non locaux . . . . . . . . . . . . . . . . . . . . . . . .

5 Allocation dynamique 6 Manipulation des chiers, oprations de haut niveau e 6.1 6.2 Flots standards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Oprations sur les ots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e

CONTENTS

6.3 6.4

Positionnement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Divers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

16 17 17 17 19 19 19 20 21 22 23 25 25 25 26 27 28 29 32 32 32 33 33 35 36 37 37 45 48

7 Manipulation des chiers, oprations de bas niveau e 7.1 7.2 7.3 7.4 7.5 7.6 7.7 7.8 Ouverture, fermeture, lecture, criture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e Suppression . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Positionnement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Verrouillage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Informations sur les chiers/rpertoires/... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e Parcours de rpertoires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e Duplication de descripteurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . mmap() : chiers mapps en mmoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e e

8 Pipes et FIFOs 8.1 8.2 8.3 FIFOs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Pipes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Pipes depuis/vers une commande . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

9 select() : attente de donnes e 9.1 9.2 Attente de donnes provenant de plusieurs sources . . . . . . . . . . . . . . . . . . . . . . . . e Attente de donnes avec dlai maximum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e e

10 Communication interprocessus par sockets locaux 10.1 Cration dun socket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e 10.2 Adresses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.3 Communication par datagrammes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

10.3.1 La rception de datagrammes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e 10.3.2 Emission de datagrammes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.3.3 Emission et rception en mode connect . . . . . . . . . . . . . . . . . . . . . . . . . . e e 10.4 Communication par ots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.4.1 Architecture client-serveur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.5 socketpair() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 Processus 11.1 fork(), wait() 11.2 waitpid() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

48 50 51 52 52

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

11.3 exec() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.4 Numros de processus : getpid(), getppid() . . . . . . . . . . . . . . . . . . . . . . . . . . . e 11.5 Programmation dun dmon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e

CONTENTS

12 Signaux 12.1 signal() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.2 kill() 12.3 alarm() 12.4 pause() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

53 53 54 55 55 55 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 55 57 58 58 59 60 61 61 61 62 65 68 71 71 72 72 72 73 73 74 74 74 74 74 75 79 79 80

13 Les signaux Posix 13.1 Manipulation des ensembles de signaux

13.2 sigaction() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 Les processus lgers (Posix 1003.1c) e 14.1 Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.2 Verrous dexclusion mutuelle (mutex) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.3 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.4 Smaphores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e 14.5 Conditions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 Communication entre processus (IPC System V) 15.1 ftok() constitution dune cl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e 15.2 Mmoires partages e e . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

15.3 Smaphores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e 15.4 Files de messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 Communication par le rseau TCP-IP e 16.1 Sockets, addresses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16.2 Remplissage dune adresse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16.2.1 Prparation dune adresse distante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e 16.2.2 Prparation dune adresse locale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e 16.2.3 Examen dune adresse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16.3 Fermeture dun socket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16.4 Communication par datagrammes (UDP) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16.4.1 Cration dun socket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e 16.4.2 Connexion de sockets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16.4.3 Envoi de datagrammes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16.4.4 Rception de datagrammes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e 16.4.5 Exemple UDP : serveur dcho . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e 16.5 Communication par ots de donnes (TCP) . . . . . . . . . . . . . . . . . . . . . . . . . . . . e 16.5.1 Programmation des clients TCP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16.5.2 Exemple : client web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1. Avant-Propos

16.5.3 Raliser un serveur TCP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e 16.6 Exemple TCP : un serveur Web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16.6.1 Serveur Web (avec processus) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16.6.2 Serveur Web (avec threads) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16.6.3 Parties communes aux deux serveurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 Documentation

82 82 82 85 89 93

1
1.1

Avant-Propos
Objectifs

Ce document prsente quelques appels syst`me utiles ` la ralisation dapplication communicantes sous e e a e UNIX. Pour crire de telles applications il faut savoir faire communiquer de processus entre eux, que ce soit sur la e mme machine ou sur des machines relies en rseau (Internet par exemple). e e e Pour cela, on passe par des appels syst`mes pour demander au syst`me dexploitation deecter des actions : e e ouvrir des voies de communication, expdier des donnes, crer des processus etc. On parle de programmation e e e syst`me lorsquon utilise explicitement ces appels sans passer par des biblioth`ques ou des modules de haut e e niveau qui les encapsulent pour en cacher la complexit (suppose).1 . e e Les principaux appels syst`mes sont prsents ici, avec des exemples dutilisation.2 e e e

1.2

Copyright

(c) 1998-2002 Michel Billaud Ce document peut tre reproduit en totalit ou en partie, sans frais, sous rserve des restrictions suivantes : e e e cette note de copyright et de permission doit tre prserve dans toutes copies partielles ou totales e e e toutes traductions ou travaux drivs doivent tre approuvs par lauteur en le prvenant avant leur e e e e e distribution si vous distribuez une partie de ce travail, des instructions pour obtenir la version compl`te doivent e galement tre fournies e e de courts extraits peuvent tre reproduits sans ces notes de permissions. e Lauteur dcline tout responsabilit vis-`-vis des dommages rsultant de lutilisation des informations et des e e a e programmes qui gurent dans ce document,

1.3

Obtenir la derni`re version e

La derni`re version de ce document peut tre obtenue depuis la page Web http://www.labri.fr/~billaud e e
Sur le march, on trouve par exemple des composants serveur-TCP tout faits pour tel ou tel langage de e programmation. 2 Attention, ce sont des illustrations des appels, pas des recommandations sur la bonne mani`re de les employer. e En particulier, les contrles de scurit sur les donnes sont tr`s sommaires. o e e e e
1

2. Rappels divers

2
2.1

Rappels divers
printf()/scanf()
int printf(const char *format, ...); int scanf(const char *format, ...);

Ces instructions font des critures et des lectures formattes sur les ots de sortie et dentre standard. Les e e e spcications de format sont dcrites dans la page de manuel printf(3). e e

2.2

sprintf()/sscanf()
int sprintf(char *str, const char *format, ...); int sscanf(const char *str,const char *format, ...);

Similaires aux prcdentes, mais les oprations lisent ou crivent dans le tampon str. e e e e Remarque : la fonction sprintf() ne connait pas la taille du tampon str; il y a donc un risque de dbordement. Il faut prvoir des tampons assez larges, ou (mieux) utiliser la fonction e e
#include <stdio.h> int snprintf(char *str, size_t size, const char *format, ...);

de la biblioth`que GNU, qui permet dindiquer une taille ` ne pas dpasser. e a e

2.3

system()
#include <stdlib.h> int system (const char * string);

permet de lancer une ligne de commande (shell ) depuis un programme. Lentier retourn par system() est e le code de retour fourni en param`tre ` exit() par la commande. e a Exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 /* * envoifichier.c * * Illustration de system(); */ #include <stdio.h> #include <stdlib.h> #define TAILLE_MAX_COMMANDE 100 #define TAILLE_MAX_DEST 100 #define TAILLE_MAX_NOMFICHIER 100 int main(void) { char dest[TAILLE_MAX_DEST], nomfichier[TAILLE_MAX_NOMFICHIER], commande[TAILLE_MAX_COMMANDE];

3. Communication avec lenvironnement

16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

int reponse; printf("destinataire ?\n"); scanf("%s", dest); /* dangereux */ printf("fichier ` envoyer ?\n"); a scanf("%s", nomfichier); /* dangereux */ snprintf(commande, TAILLE_MAX_COMMANDE, "uuencode %s %s | mail %s", nomfichier, nomfichier, dest); reponse = system(commande); if (reponse == EXIT_SUCCESS) printf("Ok\n"); else printf("La commande retourne : %d\n", reponse); exit(EXIT_SUCCESS); }

3
3.1

Communication avec lenvironnement


Param`tres de main(...) e

Le lancement dun programme C provoque lappel de sa fonction principale main(...) qui a 3 param`tres e optionnels: argc : nombre de param`tres sur la ligne de commande (y compris le nom de lexcutable lui-mme) ; e e e argv : tableau de cha nes contenant les param`tres de la ligne de commande ; e envp : tableau de cha nes contenant les variables denvironnement au moment de lappel, sous la forme variable=valeur. Remarque les noms argc, argv, envp sont purement conventionnels. lusage de envp est dconseill. Voir plus loin getenv(). e e Exemple :
1 2 3 4 5 6 7 8 9 /* * env.c * consultation des variables denvironnement via le troisi`me e * param`tre de main(). e * Cette pratique est considre comme obsol`te, e e e * utiliser plut^t getenv() o */ #include <stdio.h> #include <stdlib.h>

3. Communication avec lenvironnement

10 11 12 13 14 15 16 17 18 19 20

int main(int argc, char *argv[], char *envp[]) { int k; printf("Appel avec %d param`tres\n", argc); e for (k = 0; k < argc; k++) printf(" %d: %s\n", k, argv[k]); printf("Variables denvironnement\n"); for (k = 0; envp[k] != NULL; k++) printf(" %d: %s\n", k, envp[k]); exit(EXIT_SUCCESS); }

3.2

getopt() : analyse de param`tres de la ligne de commande e

Lanalyse des options dune ligne de commande est facilit par lemploi de getopt(). e
#include <unistd.h> int getopt(int argc, char * const argv[], const char *optstring); extern char *optarg; extern int optind, opterr, optopt;

getopt() analyse le tableau de param`tres argv ` argc lments, en se basant sur la cha de spcication e a ee ne e doptions optstring. Par exemple la cha "vhx:y:" dclare 4 options, les deux derni`res tant suivies ne e e e dun param`tre. e A chaque tape, optarg retourne le nom de loption (ou un point dinterrogation pour une option non e reconnue), et fournit ventuellement dans optarg la valeur du param`tre associ. e e e A la n getopt retourne -1, et le tableau argv a t rarrang pour que les param`tres supplmentaires ee e e e e soient stocks ` partir de lindice optind. e a
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 /* essai-getopt.c */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char *argv[]) { char *opt_a = NULL; int opt_x = 0; int index; char c; while ((c = getopt(argc, argv, "hxa:")) != -1) switch (c) { case h: printf ("Help: %s [options...] parametres ... \n\n",

3. Communication avec lenvironnement

16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45

argv[0]); printf("Options:\n" "-h\tCe message daide\n" "-x\toption x\n" "-a nom\t param`tre optionnel\n"); e exit(EXIT_SUCCESS); break; case x: opt_x = 1; break; case a: opt_a = optarg; break; case ?: fprintf(stderr, "Option inconnue -%c.\n", optopt); exit(EXIT_FAILURE); default: exit(EXIT_FAILURE); }; printf("= option -x %s\n", (opt_x ? "active" : "dsactive")); e e e if (opt_a == NULL) printf("= param`tre -a absent\n"); e else printf("= param`tre -a prsent = %s\n", opt_a); e e printf("%d param`tres supplmentaires\n", argc - optind); e e for (index = optind; index < argc; index++) printf(" -> %s\n", argv[index]); exit(EXIT_SUCCESS); }

Exemple.
% = = 3 essai-getopt -a un deux trois -x quatre option -x active e param`tre -a prsent = un e e param`tres supplmentaires e e -> deux -> trois -> quatre

3.3

Variables denvironnement

La fonction getenv() permet de consulter les variables denvironnement : #include <stdlib.h> char *getenv(const char *name); Exemple :

3. Communication avec lenvironnement

1 2 3 4 5 6 7 8 9 10 11 12 13

/* getterm.c */ #include <stdlib.h> #include <stdio.h> int main(void) { char *terminal = getenv("TERM"); printf("Le terminal est "); if (terminal == NULL) printf("inconnu\n"); else printf("un %s\n", terminal); exit(EXIT_SUCCESS); }

Exercice : Ecrire un programme exoenv.c qui ache les valeurs des variables denvironnement indiques. e Exemple dexcution: e
% exoenv TERM LOGNAME PWD TERM=xterm LOGNAME=billaud PWD=/net/profs/billaud/essais %

Voir aussi les fonctions


int putenv(const char *string); int setenv(const char *name, const char *value, int overwrite); void unsetenv(const char *name);

qui permettent de modier les variables denvironnement du processus courant et de ses ls. Il ny a pas de moyen de modier les variables du processus p`re. e Exercice. Ecrire un petit programme pour mettre en vidence le fait que le troisi`me param`tre de e e e main(...) indique les valeurs des variables denvironnement au moment de lappel , et non pas les valeurs courantes.

3.4

exit() : Fin de programme

Un programme se termine gnralement par un appel ` la fonction exit(): e e a


#include <stdlib.h> void exit(int status);

Le param`tre status est le code de retour du processus. On utilisera de prfrence les deux constantes e ee EXIT SUCCESS et EXIT FAILURE dnies dans stdlib.h. e

4. Erreurs

10

4
4.1

Erreurs
errno, perror()

La plupart des fonctions du syst`me peuvent chouer pour diverses raisons. On peut alors examiner la e e variable errno pour dterminer plus prcisment la cause de lchec, et agir en consquence. e e e e e
#include <errno.h> extern int errno;

La fonction perror() imprime sur la sortie derreur standard un message dcrivant la derni`re erreur qui e e sest produite, prcd par la cha s. e e e ne
#include <stdio.h> void perror(const char *s);

Enn, la fonction strerror() retourne le texte (en anglais) du message derreur correspondant ` un numro. a e
#include <string.h> char *strerror(int errnum);

Exemple : programme qui change les droits dacc`s ` des chiers grce ` la commande syst`me chmod(2). e a a a e
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 /* droits.c */ /* * met les droits 0600 sur un ou plusieurs fichiers * (illustration de chmod() et errno) */ #include #include #include #include #include <stdlib.h> <stdio.h> <sys/stat.h> <errno.h> <string.h>

int main(int argc, char *argv[]) { int k; for (k = 1; k < argc; k++) { printf("%s: ", argv[k]); if (chmod(argv[k], S_IREAD | S_IWRITE) != 0) switch (errno) { case EACCES: printf ("impossible de consulter un des " "rpertoires du chemin"); e break; case ELOOP: printf ("trop de liens symboliques (boucles ?)"); break; case ENAMETOOLONG:

4. Erreurs

11

28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43

printf("le nom est trop long"); break; case ENOENT: printf("le fichier nexiste pas"); break; case EPERM: printf("permission refuse"); e break; default: printf("erreur %s", strerror(errno)); break; }; printf("\n"); }; exit(EXIT_SUCCESS); }

4.2

Traitement des erreurs, branchements non locaux


#include <setjmp.h> int setjmp(jmp_buf env); void longjmp(jmp_buf env, int val);

Ces deux fonctions permettent de raliser un branchement dune fonction ` une autre (la premi`re doit avoir e a e t appele, au moins indirectement, par la seconde). Cest un moyen primitif de raliser un traitement ee e e derreurs par exceptions. La fonction setjmp() sauve lenvironnement (contexte dexcution) dans la variable tampon env, et retourne e 0 si elle a t appele directement. ee e La fonction longjmp() rtablit le dernier environnement qui a t sauv dans env par un appel ` setjmp(). e ee e a Le programme continue ` lendroit du setjmp() comme si celui-ci avait retourn la valeur val. (Si on met a e val ` 0 la valeur retourne est 1). a e Exemple.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 /* jump.c */ #include <setjmp.h> #include <stdio.h> #include <stdlib.h> #define EQUATION_SANS_SOLUTION 1 #define EQUATION_TRIVIALE 2 jmp_buf erreur; float resolution(int a, int b) { if (a == 0) { if (b == 0) longjmp(erreur, EQUATION_TRIVIALE); else longjmp(erreur, EQUATION_SANS_SOLUTION);

5. Allocation dynamique

12

15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40

}; return (-b / (float) a); } int main(void) { int a, b; float x; int ret; printf("Coefficients de ax+b=0\n"); scanf("%d %d", &a, &b); ret = setjmp(erreur); if (ret == 0) { x = resolution(a, b); printf("Solution = %f\n", x); exit(EXIT_SUCCESS); }; switch (ret) { case EQUATION_SANS_SOLUTION: printf("Cette quation na pas de solution\n"); e break; case EQUATION_TRIVIALE: printf("Cette quation est toujours vraie.\n"); e break; }; exit(EXIT_FAILURE); }

Allocation dynamique
#include <stdlib.h> void *malloc(size_t size); void free(void *ptr); void *realloc(void *ptr, size_t size);

malloc() demande au syst`me dexploitation lallocation dun espace mmoire de taille suprieure ou gale e e e e a ` size octets. La valeur retourne est un pointeur sur cet espace (NULL en cas dchec). e e free() restitue cet espace au syst`me. realloc() permet dagrandir la zone alloue. e e Il est tr`s frquent de devoir allouer une zone mmoire pour y loger une copie dune cha de caract`res. e e e ne e On utilise pour cela la fonction strdup()
#include <string.h> char *strdup(const char *s);

Linstruction nouveau=strdup(ancien) est approximativement quivalente ` : e a


nouveau=strcpy((char *) malloc(1+strlen(ancien)),ancien);

Exemple : la fonction lireligne() ci-dessous lit une ligne de lentre standard et retourne cette ligne dans e un tampon dune taille susante. Elle renvoie le pointeur NULL si il ny a plus de place en mmoire. e

5. Allocation dynamique

13

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47

/* lireligne.c */ #include <stdlib.h> #include <stdio.h> #define TAILLE_INITIALE 16 char *lireligne(void) { /* Renvoie un tampon de texte contenant une ligne lue sur lentre standard. e Un nouveau tampon est cr ` chaque invocation. ee a Renvoie NULL en cas de probl`me dallocation e */ char *chaine; int taille_allouee, taille_courante; int c; chaine = (char *) malloc(TAILLE_INITIALE); if (chaine == NULL) return (NULL); taille_courante = 0; taille_allouee = TAILLE_INITIALE; while (1) { c = getchar(); if ((c == \n) || (c == EOF)) break; chaine[taille_courante++] = c; if (taille_courante == taille_allouee) { char *tempo; taille_allouee *= 2; tempo = (char *) realloc(chaine, taille_allouee); if (tempo == NULL) { free(chaine); return (NULL); }; chaine = tempo; }; }; chaine[taille_courante] = \0; return (chaine); } int main(void) { char *chaine; printf("tapez une grande cha^ne\n"); chaine = lireligne(); if (chaine == NULL) { fprintf(stderr, "Plus de place en mmoire\n"); e

6. Manipulation des chiers, oprations de haut niveau e

14

48 49 50 51 52 53

exit(EXIT_FAILURE); } printf("%s\n", chaine); free(chaine); exit(EXIT_SUCCESS); }

Attention : dans lexemple ci-dessus la fonction lirechaine() alloue un nouveau tampon ` chaque invocaa tion. Il est donc de la responsabilit du programmeur de librer ce tampon apr`s usage pour viter les fuites e e e e mmoire. e

Manipulation des chiers, oprations de haut niveau e

Ici on utilise le terme chier dans son sens Unixien le plus large, ce qui inclue les priphriques (comme e e /dev/tty, /dev/audio,...) aussi bien que les pseudo-chiers comme /proc/cpuinfo, /proc/sound, etc.

6.1

Flots standards

Il y a trois ots prdclars, qui correspondent ` lentre et la sortie standards, et ` la sortie derreur : e e e a e a
#include <stdio.h> FILE *stdin; FILE *stdout; FILE *stderr;

Quelques fonctions agissent implicitement sur ces ots: int printf(const char *format, ...); int scanf(const char *format, ...); int getchar(void); char *gets(char *s); printf(...) crit sur stdout la valeur dexpressions selon un format donn. scanf(...) lit sur stdin la e e valeur de variables.
1 2 3 4 5 6 7 8 9 10 11 12 /* facture.c */ /* exemple printf(), scanf() */ #include <stdlib.h> #include <stdio.h> int main(void) { char article[10]; float prix; int quantite; int n; printf("article, prix unitaire, quantit ?\n"); e n = scanf("%s %f %d", article, &prix, &quantite);

6. Manipulation des chiers, oprations de haut niveau e

15

13 14 15 16 17 18 19 20

if (n != 3) { /* on na pas russi ` lire 3 choses ? */ e a printf("Erreur dans les donnes"); e exit(EXIT_FAILURE); } printf("%d %s ` %10.2f = %10.2f\n", a quantite, article, prix, prix * quantite); exit(EXIT_SUCCESS); }

scanf() renvoie le nombre dobjets qui ont pu tre eectivement lus sans erreur. e gets() lit des caract`res jusqu` une marque de n de ligne ou de n de chier, et les place (marque non e a comprise) dans le tampon donn en param`tre, suivis par un caract`re NUL. gets() renvoie nalement e e e ladresse du tampon. Attention, prvoir un tampon assez grand, ou (beaucoup mieux) utilisez fgets(). e getchar() lit un caract`re sur lentre standard et retourne sa valeur sous forme dentier positif, ou la e e constante EOF (-1) en n de chier.

6.2

Oprations sur les ots e


#include <stdio.h> FILE *fopen(char *path, char *mode); int fclose(FILE *stream); int fprintf(FILE *stream, const char *format, ...); int fscanf(FILE *stream, const char *format, ...); int fgetc(FILE *stream); char *fgets(char *s, int size, FILE *stream);

fopen() tente douvrir le chier dsign par la cha path selon le mode indiqu, qui peut tre "r" (lecture e e ne e e seulement), "r+" (lecture et criture), "w" (criture seulement), "w+" (lecture et criture, eacement si le e e e chier existe dj`), "a" (criture ` partir de la n du chier si il existe dj`), "a+" (lecture et criture, ea e a ea e positionnement ` la n du chier si il existe dj`). a ea Si louverture choue, fopen() retourne le pointeur NULL. e
int fprintf(FILE *stream, const char *format, ...); int fscanf(FILE *stream, const char *format, ...); int fgetc(FILE *stream); char *fgets(char *s, int size, FILE *stream);

Les trois premi`res fonctions ne di`rent de printf(), scanf() et getchar() que par le premier param`tre, e e e qui prcise sur quel ot porte lopration. e e Comme gets(), fgets() lit une ligne de caract`res pour la placer dans un tampon, mais e 1. la longueur de ce tampon est prcise (ceci empche les dbordements) e e e e 2. le caract`re de n de ligne (newline) est eectivement stock dans le tampon. e e Exemple : comptage du nombre de caract`res et de lignes dun chier. Achage du rsultat sur la sortie e e standard.

6. Manipulation des chiers, oprations de haut niveau e

16

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

/* comptage.c */ #include <stdio.h> #include <stdlib.h> void compter(char nom[], FILE * f) { int c, cars = 0, lignes = 0; while ((c = fgetc(f)) != EOF) { cars++; if (c == \n) lignes++; }; printf("%s: %d cars, %d lignes\n", nom, cars, lignes); } int main(int argc, char *argv[]) { char *nomfichier, *prog = argv[0]; FILE *f; switch (argc) { case 1: compter("entre standard", stdin); e break; case 2: nomfichier = argv[1]; f = fopen(nomfichier, "r"); if (f == NULL) { fprintf(stderr, "%s: fichier %s absent ou illisible\n", prog, nomfichier); exit(EXIT_FAILURE); }; compter(nomfichier, f); fclose(f); break; default: fprintf(stderr, "Usage: %s [fichier]\n", prog); exit(EXIT_SUCCESS); }; exit(EXIT_SUCCESS); }

6.3

Positionnement
int feof(FILE *stream); long ftell(FILE *stream); int fseek(FILE *stream, long offset, int whence);

feof() indique si la n de chier est atteinte. ftell() indique la position courante dans le chier (0 = dbut). fseek() dplace la position courante : si whence est SEEK SET la position est donne par rapport au e e e dbut du chier, si cest SEEK CUR : dplacement par rapport ` la position courante, SEEK END : dplacement e e a e par rapport ` la n. a

7. Manipulation des chiers, oprations de bas niveau e

17

6.4

Divers
int ferror(FILE *stream); void clearerr(FILE *stream); int fileno(FILE *stream); FILE *fdopen(int fildes, char *mode);

La fonctionferror() indique si une erreur a eu lieu sur un ot, clearerr() eace lindicateur derreur et fileno() renvoie le numro de chier de bas niveau (descripteur de chier ) correspondant ` un ot. e a Inversement, fdopen() ouvre un chier de haut niveau ` partir dun chier de bas niveau dj` ouvert. a ea

7
7.1

Manipulation des chiers, oprations de bas niveau e


Ouverture, fermeture, lecture, criture e
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(const char *pathname, int flags, mode_t mode);

Ouverture dun chier nomm pathname. Les flags peuvent prendre lune des valeurs suivantes : O RDONLY e (lecture seulement), O WRONLY (criture seulement), O RDWR (lecture et criture). Cette valeur peut tre e e e combine ventuellement (par un ou logique) avec des options: O CREAT (cration du chier si il nexiste e e e pas dj`), O TRUNC (si le chier existe il sera tronqu), O APPEND (chaque criture se fera ` la n du chier), ea e e a etc. En cas de cration dun nouveau chier, le mode sert ` prciser les droits dacc`s. Lorsquun nouveau e a e e chier est cr, mode est combin avec le umask du processus pour former les droits dacc`s du chier. Les ee e e permissions eectives sont alors (mode & ~umask). Le param`tre mode doit tre prsent quand les flags contiennent O CREAT. e e e Lentier retourn par open() est un descripteur de chier (-1 en cas derreur), qui sert ` rfrencer le chier e a ee par la suite. Les descripteurs 0, 1 et 2 correspondent aux chiers standards stdin, stdout et stderr qui sont normalement dj` ouverts (0=entre, 1=sortie, 2=erreurs). ea e
#include <unistd.h> #include <sys/types.h> int close(int fd); int read(int fd, char *buf, size_t count); size_t write(int fd, const char *buf, size_t count);

close() ferme le chier indiqu par le descripteur fd. Retourne 0 en cas de succ`s, -1 en cas dchec. read() e e e demande ` lire au plus count octets sur fd, ` placer dans le tampon buf. Retourne le nombre doctets qui a a ont t eectivement lus, qui peut tre infrieur ` la limite donne pour cause de non-disponibilit (-1 en cas ee e e a e e derreur, 0 en n de chier). write() tente dcrire sur le chier les count premiers octets du tampon buf. e Retourne le nombre doctets qui ont t eectivement crits, -1 en cas derreur. ee e Exemple :

7. Manipulation des chiers, oprations de bas niveau e

18

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45

/* copie.c */ /* Entres-sorties de bas niveau e Usages: 1: copie (entre-standard -> sortie standard) e 2: copie fichier (fichier -> sortie standard) 3: copie source dest (source -> dest) */ #include #include #include #include #include #include <stdlib.h> <stdio.h> <sys/types.h> <sys/stat.h> <fcntl.h> <unistd.h>

#define ENTREE_STANDARD 0 #define SORTIE_STANDARD 1 #define TAILLE 4096 void erreur_fatale(char s[]) { fprintf(stderr, "Erreur fatale %s\n", s); exit(EXIT_FAILURE); } void transfert(int entree, int sortie) { int nb_lus, nb_ecrits; char tampon[TAILLE]; if (entree == -1) erreur_fatale("Fichier dentre introuvable"); e if (sortie == -1) erreur_fatale ("Ne peut pas ouvrir le fichier de sortie"); for (;;) { nb_lus = read(entree, tampon, TAILLE); if (nb_lus <= 0) break; nb_ecrits = write(sortie, tampon, nb_lus); if (nb_ecrits != nb_lus) erreur_fatale("Probleme decriture"); }; } int main(int argc, char *argv[]) { int entree, sortie; switch (argc) { case 1:

7. Manipulation des chiers, oprations de bas niveau e

19

46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65

transfert(ENTREE_STANDARD, SORTIE_STANDARD); break; case 2: entree = open(argv[1], O_RDONLY); transfert(entree, SORTIE_STANDARD); close(entree); break; case 3: entree = open(argv[1], O_RDONLY); sortie = open(argv[2], O_CREAT | O_WRONLY | O_TRUNC, 0666); transfert(entree, sortie); close(entree); close(sortie); break; default: erreur_fatale("Usage: copie [src [dest]]"); }; exit(EXIT_SUCCESS); }

Probl`me. Montrez que la taille du tampon inue sur les performances des oprations dentre-sortie. e e e Pour cela, modiez le programme prcdent pour quil accepte 3 param`tres : les noms des chiers source e e e et destination, et la taille du tampon (ce tampon sera allou dynamiquement). e

7.2

Suppression
#include <stdio.h> int remove(const char *pathname);

Cette fonction supprime le chier pathname, et retourne 0 en cas de succ`s (-1 sinon). e Exercice: crire un substitut pour la commande rm. e

7.3

Positionnement
#include <unistd.h> #include <sys/types.h> off_t lseek(int fildes, off_t offset, int whence);

lseek() repositionne le pointeur de lecture. Similaire ` fseek(). Pour conna la position courante faire a tre un appel ` stat(). a Exercice. Ecrire un programme pour manipuler un chier relatif denregistrements de taille xe.

7.4

Verrouillage
#include <sys/file.h> int flock(int fd, int operation)

7. Manipulation des chiers, oprations de bas niveau e

20

Lorsque operation est LOCK EX, il y a verrouillage du chier dsign par le descripteur fd. Le chier est e e dverrouill par loption LOCK UN. e e Probl`me. Ecrire une fonction mutex() qui permettra de dlimiter une section critique dans un programme e e C. Exemple dutilisation :
#include "mutex.h" ... mutex("/tmp/foobar",MUTEX_BEGIN); ... mutex("/tmp/foobar",MUTEX_END);

Le premier param`tre indique le nom du chier utilis comme verrou. Le second prcise si il sagit de e e e verrouiller ou dverrouiller. Faut-il prvoir des options MUTEX CREATE, MUTEX DELETE ? Quarrive-til si un e e programme se termine en oubliant de fermer des sections critiques ? Fournir le chier dinterface mutex.h, limplmentation mutex.c, et des programmes illustrant lutilisation e de cette fonction.

7.5

Informations sur les chiers/rpertoires/... e


#include <sys/stat.h> #include <unistd.h> int stat(const char *file_name, struct stat *buf); int fstat(int filedes, struct stat *buf);

Ces fonctions permettent de conna tre diverses informations sur un chier dsign par un chemin dacc`s e e e (stat()) ou par un descripteur (fstat()). Exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 /* taille.c */ /* indique la taille et la nature des "fichiers" cits en param`tres e e */ #include #include #include #include #include <stdlib.h> <stdio.h> <sys/stat.h> <errno.h> <string.h>

int main(int argc, char *argv[]) { int k; struct stat s; if (argc < 2) { fprintf(stderr, "Usage: %s fichier ... \n", argv[0]); exit(EXIT_FAILURE); }; for (k = 1; k < argc; k++) { printf("%20s:\t", argv[k]); if (stat(argv[k], &s) == 0) {

7. Manipulation des chiers, oprations de bas niveau e

21

22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51

printf("%8ld ", s.st_size); if (S_ISREG(s.st_mode)) printf("(fichier)"); else if (S_ISLNK(s.st_mode)) printf("(lien symbolique)"); else if (S_ISDIR(s.st_mode)) printf("(repertoire)"); else if (S_ISCHR(s.st_mode)) printf ("(peripherique mode caracteres)"); else if (S_ISBLK(s.st_mode)) printf("(peripherique mode bloc)"); else if (S_ISFIFO(s.st_mode)) printf("(fifo)"); else if (S_ISSOCK(s.st_mode)) printf("(socket)"); } else { switch (errno) { case ENOENT: printf("le fichier nexiste pas"); break; default: printf("erreur %s", strerror(errno)); break; }; }; printf("\n"); }; exit(EXIT_SUCCESS); }

7.6

Parcours de rpertoires e

Le parcours dun rpertoire, pour obtenir la liste des chiers et rpertoires quil contient, se fait grce aux e e a fonctions:
#include <sys/types.h> #include <dirent.h> DIR *opendir(const char *name); int closedir(DIR *dir); void rewinddir(DIR *dir); void seekdir(DIR *dir, off_t offset); off_t telldir(DIR *dir);

Voir la documentation pour des exemples. Exercice : crire une version simplie de la commande ls. e e Exercice : crire une commande qui fasse appara la structure dune arborescence. Exemple dachage e tre :
C++ | CompiSep | Fichiers

7. Manipulation des chiers, oprations de bas niveau e

22

Systeme | Semaphores | Pipes | Poly | SVGD | Essais | Fifos

Conseil: crire une fonction ` deux param`tres: le chemin dacc`s du rpertoire et le niveau de rcursion. e a e e e e

7.7

Duplication de descripteurs
int dup(int oldfd); int dup2(int oldfd, int newfd);

Ces deux fonctions crent une copie du descripteur oldfd. dup() utilise le plus petit numro de descripteur e e libre. dup2() rutilise le descripteur newfd, en fermant ventuellement le chier qui lui tait antrieurement e e e e associ. e La valeur retourne est celle du descripteur, ou -1 en cas derreur. e Exemple
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 /* redirection.c */ /* Le fichier cit en param`tre est pass ` travers e e e a la commande wc. */ #include #include #include #include #include #include <sys/types.h> <fcntl.h> <stdio.h> <stdlib.h> <unistd.h> <errno.h>

#define COMMANDE "wc" int main(int argc, char *argv[]) { int fd; if (argc != 2) { fprintf(stderr, "Usage: %s fichier\n", argv[0]); exit(EXIT_FAILURE); }; fd = open(argv[1], O_RDONLY); if (fd == -1) { perror("open"); exit(EXIT_FAILURE); }; /* transfert du descripteur dans celui de lentre standard */ e

7. Manipulation des chiers, oprations de bas niveau e

23

26 27 28 29 30 31 32 33 34 35 36 37

if (dup2(fd, 0) < 0) { perror("dup2"); exit(EXIT_FAILURE); }; close(fd); system(COMMANDE); if (errno != 0) { perror("system"); exit(EXIT_FAILURE); }; exit(EXIT_SUCCESS); }

Exercice : que se produit-il si on essaie de rediriger la sortie standard dune commande ` la mani`re de a e lexemple prcdent ? (essayer avec ls, ls -l). e e

7.8

mmap() : chiers mapps en mmoire e e

Un chier mapp en mmoire appara comme un tableau doctets, ce qui permet de le parcourir en tous e e t sens plus commodment quavec des seek(), read() et write(). e Cest technique beaucoup plus conomique que de copier le chier dans une zone alloue en mmoire : cest e e e le syst`me de mmoire virtuelle qui soccupe de lire et crire physiquement les pages du chier au moment e e e o` on tente dy accder, et g`re tous les tampons. u e e
#include <unistd.h> #include <sys/mman.h> void mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset); int munmap(void *start, size_t length); *

La fonction mmap() mappe en mmoire un morceau (de longueur length, en partant du offset-i`me octet) e e du chier dsign par le descripteur fd , et retourne un pointeur sur la zone de mmoire correspondante. e e e On peut dnir quelques options (protection en lecture seule, partage, etc) grce ` prot et flags. Voir e a a pages de manuel. munmap() lib`re la mmoire. e e
1 2 3 4 5 6 7 8 9 10 /* inverse.c Affichage des lignes dun fichier en partant de la fin Exemple dutilisation de mmap M. Billaud, Octobre 2002 */ #include #include #include #include <unistd.h> <stdio.h> <stdlib.h> <sys/types.h>

7. Manipulation des chiers, oprations de bas niveau e

24

11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57

#include <sys/stat.h> #include <sys/mman.h> #include <fcntl.h> /* affichage ` lenvers du contenu dun tampon de texte */ a void afficher(char tampon[], int taille) { char *c; int nb; /* longueur ligne courante, y compris \n nb = 1; for (c = tampon + taille - 1; c >= tampon; c--) { if (*c == \n) { write(1, c + 1, nb); nb = 0; } nb++; } write(1, c + 1, nb); } int main(int argc, char *argv[]) { int fd; struct stat s; char *t; if (argc != 2) { fprintf(stderr, "Usage: %s fichier\n", argv[0]); exit(EXIT_FAILURE); } if (stat(argv[1], &s) != 0) { perror("Contr^le du type"); o exit(EXIT_FAILURE); } if (!S_ISREG(s.st_mode)) { fprintf(stderr, "%s nest pas un fichier\n", argv[1]); exit(EXIT_FAILURE); } fd = open(argv[1], O_RDONLY); if (fd < 0) { perror("Ouverture"); exit(EXIT_FAILURE); } t = mmap(NULL, s.st_size, PROT_READ, MAP_PRIVATE, fd, 0); if (t == MAP_FAILED) { perror("Mise en mmoire"); e exit(EXIT_FAILURE); } afficher(t, s.st_size); if (munmap(t, s.st_size) < 0) {

*/

8. Pipes et FIFOs

25

58 59 60 61 62

perror("Dtachement"); e exit(EXIT_FAILURE); } close(fd); }

Pipes et FIFOs

Les pipes et les FIFOs permettent de faire communiquer des processus dune mme machine : ce quun (ou e plusieurs) processus crit peut tre lu par un autre. e e

8.1

FIFOs

Les FIFOs sont galement appels tuyaux nomms . Ce sont des objets visibles dans larborescence des e e e chiers et rpertoires. Ce quun processus crit dans une FIFO peut tre lu immdiatement par dautres e e e e processus.
#include <stdio.h> int mkfifo (const char *path, mode_t mode);

La fonction mkfifo() cre un FIFO ayant le chemin indiqu par path et les droits dacc`s donns par mode. e e e e Si la cration russit, la fonction renvoie 0, sinon -1. Exemple: e e
if(mkfifo("/tmp/fifo.courrier",0644) == -1) perror("mkfifo");

Les FIFOS sont ensuite utilisables par open(), read(), write(), close(), fdopen() etc. Exercice. Regarder ce qui se passe quand plusieurs processus crivent dans une mme FIFO (faire une boucle sleep-write). e e plusieurs processus lisent la mme FIFO. e Exercice. Ecrire une commande mutex qui permettra de dlimiter une section critique dans des shelle scripts. Exemple dutilisation :
mutex -b /tmp/foobar ... mutex -e /tmp/foobar

Le premier param`tre indique si il sagit de verrouiller (-b = begin) ou de dverrouiller (-e = end). Le e e second param`tre est le nom du verrou. e Conseil : la premi`re option peut sobtenir en tentant de lire un jeton dans une FIFO. Cest la seconde e option qui dpose le jeton. Prvoir une option pour crer une FIFO ? e e e

8.2

Pipes

On utilise un pipe (tuyau) pour faire communiquer un processus et un de ses descendants3 .


3

ou des descendants - au sens large - du processus qui ont cre le tuyau e

8. Pipes et FIFOs

26

#include <unistd.h> int pipe(int filedes[2]);

Lappel pipe() fabrique un tuyau de communication et renvoie dans un tableau une paire de descripteurs. On lit ` un bout du tuyau (sur le descripteur de sortie fildes[0]) ce quon a crit dans lautre (filedes[1]). a e Voir exemple dans 11.1 (fork). Les pipes ne sont pas visibles dans larborescence des chiers et rpertoires, par contre ils sont hrits lors e e e de la cration dun processus. e La fonction socketpair() (voir 10.5 (socketpair)) gnralise la fonction pipe. e e

8.3

Pipes depuis/vers une commande


#include <stdio.h> FILE *popen(const char *command, const char *type); int pclose(FILE *stream);

popen() lance la commande dcrite par la cha command et retourne un ot. e ne Si type est "r" le ot retourn est celui de la sortie standard de la commande (on peut y lire). Si type est e "w" cest son entre standard. e pclose() referme ce ot. Exemple : envoi dun ls -l par courrier
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 /* avis.c */ /* Illustration de popen() */ #include #include #include #include #include <sys/types.h> <fcntl.h> <stdlib.h> <unistd.h> <stdio.h>

#define TAILLE_MAX_COMMANDE 100 int main(int argc, char *argv[]) { char cmdmail[TAILLE_MAX_COMMANDE]; FILE *fls, *fmail; int c; if (argc != 2) { fprintf(stderr, "Usage: %s destinataire\n", argv[0]); exit(EXIT_FAILURE); }; snprintf(cmdmail, TAILLE_MAX_COMMANDE, "sendmail %s", argv[1]); fmail = popen(cmdmail, "w"); if (fmail == NULL) { perror("popen(sendmail)"); exit(EXIT_FAILURE);

9. select() : attente de donnes e

27

24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

}; fprintf(fmail, "Subject: ls -l\n\n"); fprintf(fmail, "Cher %s,\nvoici mon rpertoire:\n", argv[1]); e fls = popen("ls -l", "r"); if (fls == NULL) { perror("popen(ls)"); exit(EXIT_FAILURE); }; while ((c = fgetc(fls)) != EOF) fputc(c, fmail); pclose(fls); fprintf(fmail, "---\nLe Robot\n"); pclose(fmail); exit(EXIT_SUCCESS); }

select() : attente de donnes e

Il est assez courant de devoir attendre des donnes en provenance de plusieurs sources. On utilise pour cela e la fonction select() qui permet de surveiller plusieurs descripteurs simultanment. e
#include <sys/time.h> #include <sys/types.h> #include <unistd.h> int select(int n, fd_set fd_set fd_set struct *readfds, *writefds, *exceptfds, timeval *timeout);

FD_CLR(int fd, fd_set *set); FD_ISSET(int fd, fd_set *set); FD_SET(int fd, fd_set *set); FD_ZERO(fd_set *set);

Cette fonction attend que des donnes soient prtes ` tre lues sur un des descripteurs de lensemble readfs, e e ae ou que lun des descripteurs de writefds soit prt ` recevoir des critures, que des exceptions se produisent e a e (exceptfds), ou encore que le temps dattente timeout soit puis. e e Lorsque select() se termine, readfds, writefds et exceptfds contiennent les descripteurs qui ont chang e dtat. select() retourne le nombre de descripteurs qui ont chang dtat, ou -1 en cas de probl`me. e e e e Lentier n doit tre suprieur (strictement) au plus grand des descripteurs contenus dans les 3 ensembles e e (cest en fait le nombre de bits signicatifs du masque binaire qui reprsente les ensembles). On peut utiliser e la constante FD SETSIZE. Les pointeurs sur les ensembles (ou le dlai) peuvent tre NULL, ils reprsentent alors des ensembles vides e e e (ou une absence de limite de temps). Les macros FD CLR, FD ISSET, FD SET, FD ZERO permettent de manipuler les ensembles de descripteurs.

9. select() : attente de donnes e

28

9.1

Attente de donnes provenant de plusieurs sources e

Exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 /* mix.c */ /* affiche les donnes qui proviennent de 2 fifos e usage: mix f1 f2 */ #include #include #include #include #include #include #include <sys/time.h> <sys/types.h> <sys/stat.h> <unistd.h> <stdio.h> <stdlib.h> <fcntl.h>

#define TAILLE_TAMPON 128 /************************************************* Mixe les donnes en provenance de deux e descripteurs **************************************************/ void mixer(int fd1, int fd2, int sortie) { fd_set ouverts; /* les descripteurs ouverts */ int nbouverts; FD_ZERO(&ouverts); FD_SET(fd1, &ouverts); FD_SET(fd2, &ouverts); nbouverts = 2; while (nbouverts > 0) { /* tant quil reste des descripteurs ouverts.... */

fd_set prets; char tampon[TAILLE_TAMPON]; /* on attend quun descripteur soit pr^t ... */ e prets = ouverts; if (select(FD_SETSIZE, &prets, NULL, NULL, NULL) < 0) { perror("select"); exit(EXIT_FAILURE); } if (FD_ISSET(fd1, &prets)) { /* si fd1 est pr^t... */ e int n = read(fd1, tampon, TAILLE_TAMPON); if (n >= 0) { /* on copie ce quon a lu */ write(sortie, tampon, n); } else { /* fin de fd1 : on lenl`ve */ e

9. select() : attente de donnes e

29

43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81

close(fd1); nbouverts--; FD_CLR(fd1, &ouverts); } }; if (FD_ISSET(fd2, &prets)) { /* si fd2 est pr^t... */ e int n = read(fd2, tampon, TAILLE_TAMPON); if (n >= 0) { /* on copie ce quon a lu */ write(sortie, tampon, n); } else { /* fin de fd2 : on lenl`ve */ e close(fd2); nbouverts--; FD_CLR(fd2, &ouverts); } }; }; } int main(int argc, char *argv[]) { int fd1, fd2; if (argc != 3) { fprintf(stderr, "Usage : %s f1 f2\n", argv[0]); exit(EXIT_FAILURE); }; fd1 = open(argv[1], O_RDONLY); if (fd1 == -1) { fprintf(stderr, "Ouverture %s refuse\n", argv[1]); e exit(EXIT_FAILURE); }; fd2 = open(argv[2], O_RDONLY); if (fd2 == -1) { fprintf(stderr, "Ouverture %s refuse\n", argv[2]); e exit(EXIT_FAILURE); }; mixer(fd1, fd2, 1); exit(EXIT_SUCCESS); }

9.2

Attente de donnes avec dlai maximum e e

Lexemple suivant montre comment utiliser la limite de temps dans le cas (frquent) dattente sur un seul e descripteur. Une fonction utilitaire :
1 2 /* SelectFifo/AttenteDonnees.c

9. select() : attente de donnes e

30

3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34

attente de donnes provenant dun descripteur ouvert, e avec dlai maximum e */ #include #include #include #include #include #include #include /* fonction AttenteDonnees param`tres: e - un descripteur ouvert - une dure dattente en millisecondes e r^le: attend que des donnes arrivent sur le descripteur pendant o e un certain temps. retourne: 1 si des donnes sont arrives e e 0 si le dlai est dpass e e e -1 en cas derreur. Voir variable errno. */ int AttenteDonnees(int fd, int millisecondes) { fd_set set; struct timeval delai; FD_ZERO(&set); FD_SET(fd, &set); delai.tv_sec = millisecondes / 1000; delai.tv_usec = (millisecondes % 1000) * 1000; return select(FD_SETSIZE, &set, NULL, NULL, &delai); } <sys/time.h> <sys/types.h> <sys/stat.h> <unistd.h> <stdio.h> <stdlib.h> <fcntl.h>

Le programme principal :
1 2 3 4 5 6 7 8 9 /* SelectFifo/lecteur.c Exemple de lecture avec dlai (timeout). e M. Billaud, Septembre 2002 Ce programme attend des lignes de texte provenant dune fifo, et les affiche. En attendant de recevoir les lignes, il affiche une petite toile e tournante (par affichage successif des symboles - \ | et /). Exemple dutilisation:

9. select() : attente de donnes e

31

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 */

- crer une fifo : mkfifo /tmp/mafifo e - dans une fen^tre, lancer : lecteur /tmp/mafifo e le programme se met en attente - dans une autre fen^tre, faire e cat > /tmp/mafifo puis taper quelques lignes de texte.

#include #include #include #include #include #include #include

<sys/time.h> <sys/types.h> <sys/stat.h> <unistd.h> <stdio.h> <stdlib.h> <fcntl.h>

#define TAILLE_TAMPON 100 #define DELAI 500

/* en millisecondes */

extern int AttenteDonnees(int, int); int main(int argc, char *argv[]) { int fd; char symbole[] = "-\\|-"; int n = 0; int numero_ligne = 1; if (argc != 2) { fprintf(stderr, "Usage: %s fifo\n", argv[0]); exit(EXIT_FAILURE); } printf("> Ouverture fifo %s ...\n", argv[1]); fd = open(argv[1], O_RDONLY); if (fd == -1) { fprintf(stderr, "Ouverture refuse\n"); e exit(EXIT_FAILURE); }; printf("Ok\n"); for (;;) { int r; r = AttenteDonnees(fd, DELAI); if (r == 0) { /* dlai dpass, on affiche un caract`re suivi par backspace */ e e e e printf("%c\b", symbole[n++ % 4]); fflush(stdout); } else { /* on a reu quelque chose */ c char buffer[TAILLE_TAMPON]; int nb; nb = read(fd, buffer, TAILLE_TAMPON - 1); if (nb <= 0) break;

10. Communication interprocessus par sockets locaux

32

58 59 60 61 62 63 64

buffer[nb] = \0; printf("%4d %s", numero_ligne++, buffer); } } close(fd); exit(EXIT_SUCCESS); }

10

Communication interprocessus par sockets locaux

Les sockets (prises) sont un moyen gnrique de faire communiquer des processus entre eux. Cest le moyen e e standard utilis pour la communication sur le rseau Internet, mais on peut lemployer galement pour la e e e communication locale entre processus tournant sur une mme machine (commes les FIFOs et les pipes, et e les les de messages IPC que nous verrons plus loin).

10.1

Cration dun socket e


#include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol);

La fonction socket() cre une nouvelle prise et retourne un descripteur qui servira ensuite aux lectures e et critures. Le param`tre domain indique le domaine de communication utilis, qui est PF LOCAL ou e e e (synonyme) PF UNIX pour les communications locales.4 Le type indique le style de communication dsir entre les deux participants. Les deux styles principaux sont e e SOCK DGRAM : communication par messages (blocs contenant des octets) appels datagrammes e SOCK STREAM : la communication se fait par un ot (bidirectionnel) doctets une fois que la connection est tablie. e Fiabilit: la abilit des communication par datagrammes est garantie pour le domaine local, mais ce nest e e pas le cas pour les domaines rseau que nous verrons plus loin : les datagrammes peuvent tre perdus, e e dupliqus, arriver dans le dsordre etc. et cest au programmeur dapplication den tenir compte. Par contre e e la abilit des streams est assure par les couches basses du syst`me de communication, videmment au e e e e prix dun surcot (numrotation des paquets, accuss de rception, temporisations, retranmissions, etc). u e e e Enn, le param`tre protocol indique le protocole slectionn. La valeur 0 correspond au protocole par e e e dfaut pour le domaine et le type indiqu. e e

10.2

Adresses

La fonction socket() cre un socket anonyme. Pour quun autre processus puisse le dsigner, il faut lui e e associer un nom par lintermdiaire dune adresse contenue dans une structure sockaddr un : e
Le domaine dnit une famille de protocoles (protocol family) utilisables. Autres familles disponibles : PF INET e protocoles internet IPv4, PF INET6 protocoles internet IPv6, PF IPX protocoles Novel IPX, PF X25 protocoles X25 (ITU-T X.25 / ISO-8208), PF APPLETALKAppletalk, etc.
4

10. Communication interprocessus par sockets locaux

33

#include <sys/un.h> struct sockaddr_un { sa_family_t sun_family; /* AF_UNIX */ char sun_path[UNIX_PATH_MAX]; /* pathname */ };

Ces adresses sont des chemins dacc`s dans larborescence des chiers et rpertoires. e e Exemple :
#include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> socklen_t longueur_adresse; struct sockaddr_un adresse;

adresse.sun_family = AF_LOCAL; strcpy(adresse.sun_path,"/tmp/xyz"); longueur_adresse = SUN_LEN (&dresse);

Lassociation dune adresse ` un socket se fait par bind() (voir exemples plus loin). a

10.3

Communication par datagrammes

Dans lexemple dvelopp ici, un serveur ache les datagrammes mis par les clients. e e e 10.3.1 La rception de datagrammes e

#include <sys/types.h> #include <sys/socket.h> int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);

int

recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);

La fonction bind() permet de nommer le socket de rception. La fonction recvfrom() attend larrive dun e e datagramme qui est stock dans les len premiers octets du tampon buff. Si from nest pas NULL, ladresse e du socket metteur5 est place dans la structure pointe par from, dont la longueur maximale est contenue e e e dans lentier point par fromlen. e Si la lecture a russi, la fonction retourne le nombre doctets du message lu, et la longueur de ladresse est e mise ` jour. a Le param`tre flags permet de prciser des options. e e
1
5

/* serveur-dgram-local.c */

qui peut servir ` expdier une rponsee a e e

10. Communication interprocessus par sockets locaux

34

2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47

/* Usage: serveur-dgram-local chemin

Reoit des datagrammes par un socket du domain local, c et les affiche. Le param`tre indique le nom du socket. e Sarr^te quand la donne est "stop". e e */ #include #include #include #include #include #include <stdio.h> <unistd.h> <stdlib.h> <sys/types.h> <sys/socket.h> <sys/un.h>

#define TAILLE_MAX_DONNEE 1024 int main(int argc, char *argv[]) { socklen_t longueur_adresse; struct sockaddr_un adresse; int fd; if (argc != 2) { fprintf(stderr, "usage: %s chemin\n", argv[0]); exit(EXIT_FAILURE); }; adresse.sun_family = AF_LOCAL; strcpy(adresse.sun_path, argv[1]); longueur_adresse = SUN_LEN(&adresse); fd = socket(PF_LOCAL, SOCK_DGRAM, 0); if (fd < 0) { perror("Cration du socket serveur"); e exit(EXIT_FAILURE); } if (bind(fd, (struct sockaddr *) &adresse, longueur_adresse) < 0) { perror("Nommage du socket serveur"); exit(EXIT_FAILURE); } printf("> Serveur dmarr sur socket local \"%s\"\n", e e argv[1]); while (1) { int lg; /* un caract`re supplmentaire permet dajouter le e e terminateur de cha^ne, qui nest pas transmis */ char tampon[TAILLE_MAX_DONNEE + 1]; lg = recvfrom(fd, tampon, TAILLE_MAX_DONNEE, 0, NULL, NULL); if (lg <= 0) { perror("Rception datagramme"); e

10. Communication interprocessus par sockets locaux

35

48 49 50 51 52 53 54 55 56 57 58 59 60

exit(EXIT_FAILURE); }; tampon[lg] = \0; /* ajout terminateur */ printf("Reu : %s\n", tampon); c if (strcmp(tampon, "stop") == 0) { printf("> arr^t demand\n"); e e break; } } close(fd); unlink(argv[1]); exit(EXIT_SUCCESS); }

10.3.2

Emission de datagrammes

#include <sys/types.h> #include <sys/socket.h> int sendto(int s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);

sendto utilise le descripteur de socket s pour envoyer le message form des len premiers octets de msg ` e a ladresse de longueur tolen pointe par to. e Le mme descripteur peut tre utilis pour des envois ` des adresses direntes. e e e a e
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 /* client-dgram-local.c */ /* Usage: client-dgram-local chemin messages

Envoie des datagrammes ` un socket du domain local, a et les affiche. Le premier param`tre indique le nom du socket, e les autres des cha^nes de caract`res. e Exemple : client-dgram-local un deux "trente et un" stop */ #include #include #include #include #include #include <stdio.h> <unistd.h> <stdlib.h> <sys/types.h> <sys/socket.h> <sys/un.h>

#define TAILLE_MAX_DONNEE 1024 int main(int argc, char *argv[]) { socklen_t longueur_adresse; struct sockaddr_un adresse; int fd; int k;

10. Communication interprocessus par sockets locaux

36

22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54

if (argc <= 2) { fprintf(stderr, "usage: %s chemin message\n", argv[0]); exit(EXIT_FAILURE); } adresse.sun_family = AF_LOCAL; strcpy(adresse.sun_path, argv[1]); longueur_adresse = SUN_LEN(&adresse); fd = socket(PF_LOCAL, SOCK_DGRAM, 0); if (fd < 0) { perror("Cration du socket client"); e exit(EXIT_FAILURE); } for (k = 2; k < argc; k++) { int lg; lg = strlen(argv[k]); if (lg > TAILLE_MAX_DONNEE) lg = TAILLE_MAX_DONNEE; /* le message est envoy sans le terminateur \0 */ e if (sendto(fd, argv[k], lg, 0, (struct sockaddr *) &adresse, longueur_adresse) < 0) { perror("Expdition du message"); e exit(EXIT_FAILURE); } printf("."); fflush(stdout); sleep(1); } printf("Ok\n"); close(fd); exit(EXIT_SUCCESS); }

Exercice : modier les programmes prcdents pour que le serveur envoie une rponse qui sera ache par e e e e le client6 . 10.3.3 Emission et rception en mode connect e e

#include <sys/types.h> #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);

Penser ` attribuer un nom au socket du client pour que le serveur puisse lui rpondre, par exemple avec laide a e de la fonction tempnam().

10. Communication interprocessus par sockets locaux

37

int send(int s, const void *msg, size_t len, int flags); int recv(int s, void *buf, size_t len, int flags);

Un metteur qui va envoyer une srie de messages au mme destinataire par le mme socket peut faire e e e e pralablement un connect() pour indiquer une destination par dfaut, et employer ensuite send() ` la e e a place de sendto(). Le rcepteur qui ne sintresse pas ` ladresse de lenvoyeur peut utiliser recv(). e e a Exercice : modier les programmes prcdents pour utiliser recv() et send(). e e

10.4

Communication par ots

Dans ce type de communication, cest une suite doctets qui est transmises (et non pas une suite de messages comme dans la communication par datagrammes). Les sockets locaux de ce type sont cres par e
int fd = socket(PF_LOCAL,SOCK_STREAM,0);

10.4.1

Architecture client-serveur

La plupart des applications communicantes sont conues selon une structure dissymtrique : larchitecture c e client-serveur, dans laquelle un processus serveur est contact par plusieurs clients. e Le client cre un socket (socket()), quil met en relation (par connect()) avec celui du serveur. Les donnes e e sont changes par read(), write() ... et le socket est ferm par close(). e e e
#include <sys/types.h> #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);

Du ct serveur : un socket est cre et une adresse lui est associe (socket() + bind()). Un listen() oe e e prvient le syst`me que ce socket recevra des demandes de connexion, et prcise le nombre de connexions e e e que lon peut mettre en le dattente. Le serveur attend les demandes de connexion par la fonction accept() qui renvoit un descripteur, lequel permet la communication avec le client.
#include <sys/types.h> #include <sys/socket.h> int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen); listen(int s, int backlog); accept(int s, struct sockaddr socklen_t

int int

*addr, *addrlen);

10. Communication interprocessus par sockets locaux

38

Remarque : il est possible ne fermer quune moiti de socket : shutdown(1) met n aux missions (causant e e une n de chier chez le correspondant), shutdown(0) met n aux rceptions. e

#include <sys/socket.h> int shutdown(int s, int how);

Le client :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 /* client-stream Envoi/rception de donnes par un socket local (mode connect) e e e Exemple de client qui - ouvre un socket - envoie sur ce socket du texte lu sur lentree standard - attend et affiche une rponse e */ #include #include #include #include #include #include #include #include <unistd.h> <sys/types.h> <sys/socket.h> <sys/un.h> <signal.h> <stdio.h> <string.h> <stdlib.h>

#define TAILLE_TAMPON 1000 int main(int argc, char *argv[]) { char *chemin; /* chemin dacc`s du socket serveur */ e socklen_t longueur_adresse; struct sockaddr_un adresse; int fd; /* 1. rception des param`tres de la ligne de commande */ e e if (argc != 2) { printf("Usage: %s chemin\n", argv[0]); exit(EXIT_FAILURE); }; chemin = argv[1]; /* 2. Initialisation du socket */ /* 2.1 cration du socket e */ fd = socket(PF_LOCAL, SOCK_STREAM, 0); if (fd < 0) { perror("Cration du socket client"); e exit(EXIT_FAILURE); }

10. Communication interprocessus par sockets locaux

39

37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81

/* 2.2 Remplissage adresse serveur */ adresse.sun_family = AF_LOCAL; strcpy(adresse.sun_path, chemin); longueur_adresse = SUN_LEN(&adresse); /* 2.3 connexion au serveur */ if (connect(fd, (struct sockaddr *) &adresse, longueur_adresse) < 0) { perror("connect"); exit(EXIT_FAILURE); } printf("CLIENT> Connexion tablie\n"); e /* 3. Lecture et envoi des donnes */ e for (;;) { char tampon[TAILLE_TAMPON]; int nb_lus, nb_envoyes; nb_lus = read(0, tampon, TAILLE_TAMPON); if (nb_lus <= 0) break; nb_envoyes = write(fd, tampon, nb_lus); if (nb_envoyes != nb_lus) { perror("envoi donnes"); e fprintf(stderr, "seulement %d envoys sur %d", e nb_envoyes, nb_lus); exit(EXIT_FAILURE); } } /* 4. Fin de lenvoi */ shutdown(fd, 1); printf("CLIENT> Fin envoi, attente de la rponse.\n"); e /* 5. Rception et affichage de la rponse */ e e for (;;) { char tampon[TAILLE_TAMPON]; int nb_lus; nb_lus = read(fd, tampon, TAILLE_TAMPON - 1); if (nb_lus <= 0) break; tampon[nb_lus] = \0; /* ajout dun terminateur de cha^ne */ printf("%s", tampon); } /* et fin */ close(fd); printf("CLIENT> Fin.\n"); exit(EXIT_SUCCESS); }

Le serveur est programm ici de faon atypique, puisquil traite quil tra une seule communication ` la e c te a fois. Si le client fait tra ner les choses, les autres clients en attente resteront bloqus longtemps. e
1 2 /* serveur-stream

10. Communication interprocessus par sockets locaux

40

3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45

Envoi/rception de donnes par un socket local (mode connect) e e e Exemple de serveur qui - attend une connexion - lit du texte - envoie une rponse e Remarque: ce serveur ne traite quune connexion ` la fois. a */ #include #include #include #include #include #include #include #include <unistd.h> <sys/types.h> <sys/socket.h> <sys/un.h> <signal.h> <stdio.h> <stdlib.h> <ctype.h>

#define TAILLE_TAMPON 1000 #define CONNEXIONS_EN_ATTENTE 4 int main(int argc, char *argv[]) { int fd_serveur; struct sockaddr_un adresse; size_t taille_adresse; char *chemin; char tampon[TAILLE_TAMPON]; int nb_car; int numero_connexion = 0; /* 1. rception des param`tres de la ligne de commande */ e e if (argc != 2) { printf("usage: %s chemin\n", argv[0]); exit(1); }; chemin = argv[1]; /* 3. Initialisation du socket de rception */ e /* 3.1 cration du socket e */ fd_serveur = socket(PF_LOCAL, SOCK_STREAM, 0); if (fd_serveur < 0) { perror("Cration du socket serveur"); e exit(EXIT_FAILURE); } /* 3.2 Remplissage adresse serveur */ adresse.sun_family = AF_LOCAL; strcpy(adresse.sun_path, chemin); taille_adresse = SUN_LEN(&adresse);

10. Communication interprocessus par sockets locaux

41

46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94

/* 3.3 Association de ladresse au socket */ taille_adresse = sizeof adresse; if (bind(fd_serveur, (struct sockaddr *) &adresse, taille_adresse) < 0) { perror("bind"); exit(EXIT_FAILURE); } /* 3.4 Ce socket attend des connexions mises en file dattente */ listen(fd_serveur, CONNEXIONS_EN_ATTENTE); printf("SERVEUR> Le serveur coute le socket %s\n", chemin); e /* 4. boucle du serveur */ for (;;) { int fd_client; int compteur = 0; /* 4.1 attente dune connexion printf("SERVEUR> Attente dune fd_client = accept(fd_serveur, if (fd_client < 0) { perror("accept"); exit(EXIT_FAILURE); } numero_connexion++; printf("SERVEUR> Connexion #%d numero_connexion); */ connexion.\n"); NULL, NULL);

e tablie.\n",

/* 4.2 lecture et affichage des donnes envoyes par le client */ e e for (;;) { int nb_lus; nb_lus = read(fd_client, tampon, TAILLE_TAMPON - 1); if (nb_lus <= 0) break; compteur += nb_lus; tampon[nb_lus] = \0; printf("%s", tampon); }; /* 4.4 plus de donnes, on envoit une rponse */ e e printf("SERVEUR> envoi de la rponse.\n"); e nb_car = sprintf(tampon, "*** Fin de la connexion #%d\n", numero_connexion); write(fd_client, tampon, nb_car); nb_car = sprintf(tampon, "*** Vous mavez envoy %d caract`res\n", e e compteur); write(fd_client, tampon, nb_car); nb_car = sprintf(tampon, "*** Merci et ` bientot.\n"); a write(fd_client, tampon, nb_car);

10. Communication interprocessus par sockets locaux

42

95 96 97 98 99

/* 4.4 fermeture de la connexion */ close(fd_client); printf("SERVEUR> fin de connexion.\n"); } }

Dans une programmation plus classique, le serveur lance un processus (par fork(), voir plus loin) d`s quune e connexion est tablie, et dl`gue le traitement de la connexion ` ce processus. e ee a Une autre technique est envisageable pour traiter plusieurs connexions par un processus unique : le serveur maintient une liste de descripteurs ouverts, et fait une boucle autour dun select(), en attente de donnes e venant soit du descripteur principal ouvert par le serveur. Dans ce cas il eectue ensuite un accept(...) qui permettra dajouter un nouveau client ` la liste. a soit dun des descripteurs des clients, et il traite alors les donnes venant de ce client (il lenl`ve de la e e liste en n de communication). Cette technique conduit ` des performances nettement suprieures aux serveurs multiprocessus ou multia e threads (pas de temps perdu ` lancer des processus), au prix dune programmation qui oblige le programmeur a a e ` grer lui-mme le contexte de droulement de chaque processus. e e
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 /* serveur-stream-monotache Envoi/rception de donnes par un socket local (mode connect) e e e Exemple de serveur monot^che qui g`re plusieurs connexions a e - attend une connexion - lit du texte - envoie une rponse e */ #include #include #include #include #include #include #include #include #include <unistd.h> <sys/types.h> <sys/socket.h> <sys/un.h> <signal.h> <stdio.h> <stdlib.h> <ctype.h> <assert.h>

#define TAILLE_TAMPON 1000 #define CONNEXIONS_EN_ATTENTE 4 #define NBMAXCLIENTS 10 /* les donnes propres ` chaque client */ e a #define INACTIF -1 struct {

10. Communication interprocessus par sockets locaux

43

24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68

int fd; int numero_connexion; int compteur; } client[NBMAXCLIENTS]; int main(int argc, char *argv[]) { int fd_serveur; struct sockaddr_un adresse; size_t taille_adresse; char *chemin; int nb_connexions = 0; int i; int nbfd; /* 1. rception des param`tres de la ligne de commande */ e e if (argc != 2) { printf("usage: %s chemin\n", argv[0]); exit(1); }; chemin = argv[1]; /* 3. Initialisation du socket de rception */ e /* 3.1 cration du socket e */ fd_serveur = socket(PF_LOCAL, SOCK_STREAM, 0); if (fd_serveur < 0) { perror("Cration du socket serveur"); e exit(EXIT_FAILURE); } /* 3.2 Remplissage adresse serveur */ adresse.sun_family = AF_LOCAL; strcpy(adresse.sun_path, chemin); taille_adresse = SUN_LEN(&adresse); /* 3.3 Association de ladresse au socket */ taille_adresse = sizeof adresse; if (bind(fd_serveur, (struct sockaddr *) &adresse, taille_adresse) < 0) { perror("bind"); exit(EXIT_FAILURE); } /* 3.4 Ce socket attend des connexions mises en file dattente */ listen(fd_serveur, CONNEXIONS_EN_ATTENTE); printf("SERVEUR> Le serveur coute le socket %s\n", chemin); e /* 3.5 initialisation du tableau des clients */ for (i = 0; i < NBMAXCLIENTS; i++) { client[i].fd = INACTIF; } /* 4. boucle du serveur */

10. Communication interprocessus par sockets locaux

44

69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119

for (;;) { fd_set lectures; /* 4.1 remplissage des masques du select */ FD_ZERO(&lectures); FD_SET(fd_serveur, &lectures); for (i = 0; i < NBMAXCLIENTS; i++) { if (client[i].fd != INACTIF) FD_SET(client[i].fd, &lectures); } /* 4.2 attente dun vnement (ou plusieurs) */ e e nbfd = select(FD_SETSIZE, &lectures, NULL, NULL, NULL); assert(nbfd >= 0); /* 4.3 en cas de nouvelle connexion : */ if (FD_ISSET(fd_serveur, &lectures)) { /* si il y a de la place dans la table des clients, on y ajoute la nouvelle connexion */ nbfd--; for (i = 0; i < NBMAXCLIENTS; i++) { if (client[i].fd == INACTIF) { int fd_client; fd_client = accept(fd_serveur, NULL, NULL); if (fd_client < 0) { perror("accept"); exit(EXIT_FAILURE); } client[i].fd = fd_client; client[i].numero_connexion = ++nb_connexions; client[i].compteur = 0; printf ("SERVEUR> arrive de connexion #%d (fd %d)\n" e client[i]. numero_connexion, fd_client); break; } if (i >= NBMAXCLIENTS) { printf ("SERVEUR> trop de connexions !\n"); } } }; /* 4.4 traitement des clients actifs qui ont reu des donnes */ c e for (i = 0; (i < NBMAXCLIENTS) && (nbfd > 0); i++) { if ((client[i].fd != INACTIF) && FD_ISSET(client[i].fd, &lectures)) { int nb_lus; char tampon[TAILLE_TAMPON];

10. Communication interprocessus par sockets locaux

45

120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163

nbfd--; nb_lus = read(client[i].fd, tampon, TAILLE_TAMPON - 1); printf ("SERVEUR> donnes reues de #%d (%d octets)\n", e c client[i].numero_connexion, nb_lus); if (nb_lus > 0) client[i].compteur += nb_lus; else { int nb_car; printf ("SERVEUR> envoi de la rponse au client #%d.\ e client[i]. numero_connexion); nb_car = sprintf(tampon, "*** Fin de la connexion #%d\n", client[i]. numero_connexion); write(client[i].fd, tampon, nb_car); nb_car = sprintf(tampon, "*** Vous mavez envoy %d caract`res\n", e e client[i]. compteur); write(client[i].fd, tampon, nb_car); nb_car = sprintf(tampon, "*** Merci et ` bient^t.\n"); a o write(client[i].fd, tampon, nb_car); close(client[i].fd); /* enl`vement de la liste des clients */ e client[i].fd = INACTIF; } } } assert(nbfd == 0); } }

/* tous les descripteurs ont t traits */ e e e

10.5

socketpair()

La fonction socketpair() construit une paire de sockets locaux, bi-directionnels, relis lun ` lautre. e a
#include <sys/types.h> #include <sys/socket.h> int socketpair(int d, int type, int protocol, int sv[2]);

10. Communication interprocessus par sockets locaux

46

Dans ltat actuel des implmentations, le param`tre d (domaine) doit tre gal ` AF LOCAL, et type ` e e e e e a a SOCK DGRAM ou SOCK STREAM, avec le protocole par dfaut (valeur 0). e Cette fonction remplit le tableau sv[] avec les descripteurs de deux sockets du type indiqu. Ces deux e sockets sont relis entre eux et bidirectionnels : ce quon crit sur le descripteur sv[0] peut tre lu sur e e e sv[1], et rciproquement. e On utilise socketpair() comme pipe(), pour la communication entre descendants dun mme processus e . socketpair() poss`de deux avantages sur pipe() : la possibilit de transmettre des datagrammes, et la e e bidirectionnalit. e
7

Exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
7

/* paire-send.c e change de donnes ` travers des sockets locaux crs par e a ee socketpair() */ #include #include #include #include #include <unistd.h> <stdio.h> <stdlib.h> <sys/socket.h> <sys/types.h>

struct paquet { int type; int valeur; }; /* les diffrents types de paquets */ e #define DONNEE 0 #define RESULTAT 1 #define FIN 2 void additionneur(int fd) { /* calcule la somme des entiers qui arrivent sur le descripteur, renvoie le rsultat e */ int somme = 0; struct paquet p; int n; for (;;) { n = recv(fd, &p, sizeof p, 0); if (n < 0) { perror("additionneur : rception"); e exit(EXIT_FAILURE); }; printf

au sens large, ce qui inclue la communication dun processus avec un de ses ls

10. Communication interprocessus par sockets locaux

47

35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86

("additionneur: rception dun paquet contenant "); e if (p.type == FIN) { printf("la marque de fin\n"); break; } printf("la donne %d\n", p.valeur); e somme += p.valeur; }; /* envoi reponse */ p.type = RESULTAT; p.valeur = somme; printf("additionneur: envoi du total %d\n", somme); n = send(fd, &p, sizeof p, 0); if (n < 0) { perror("additionneur : envoi"); exit(EXIT_FAILURE); }; exit(EXIT_SUCCESS); }; void generateur(int fd) { /* envoie une suite dentiers, rcup`re et affiche le rsultat. e e e */ struct paquet p; int i, n, resultat; for (i = 1; i <= 5; i++) { p.type = DONNEE; p.valeur = i; printf("gnerateur: envoi de la donne %d\n", i); e e n = send(fd, &p, sizeof p, 0); if (n < 0) { perror("generateur: envoi donne"); e exit(EXIT_FAILURE); }; sleep(1); }; p.type = FIN; printf("gnerateur: envoi de la marque de fin\n"); e n = send(fd, &p, sizeof p, 0); if (n < 0) { perror("generateur: envoi fin"); exit(EXIT_FAILURE); }; printf("generateur: lecture du rsultat\n"); e n = recv(fd, &p, sizeof p, 0); if (n < 0) { perror("generateur: rception rponse"); e e exit(EXIT_FAILURE); }; resultat = p.valeur; printf("generateur: resultat reu = %d\n", resultat); c

11. Processus

48

87 88 89 90 91 92 93 94 95 96 97 98 99 100

} int main() { int paire[2]; socketpair(AF_LOCAL, SOCK_DGRAM, 0, paire); if (fork() == 0) { close(paire[0]); additionneur(paire[1]); exit(EXIT_SUCCESS); }; close(paire[1]); generateur(paire[0]); exit(EXIT_SUCCESS); }

11
11.1

Processus
fork(), wait()
#include <unistd.h> pid_t fork(void); pid_t wait(int *status)

La fonction fork() cre un nouveau processus (ls) semblable au processus courant (p`re). La valeur e e renvoye nest pas la mme pour le ls (0) et pour le p`re (numro de processus du ls). -1 indique un chec. e e e e e La fonction wait() attend quun des processus ls soit termin. Elle renvoie le numro du ls, et son status e e (voir exit()) en param`tre pass par adresse. e e Attention. Le processus ls hrite des descripteurs ouverts de son p`re. Il convient que chacun des e e processus ferme les descripteurs qui ne le concernent pas. Exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 /* biproc.c */ /* * Illustration de fork() et pipe(); * * Exemple ` deux processus relis par un tuyau a e - lun envoie abcdef...z 10 fois dans le tuyau - lautre crit ce qui lui arrive du tuyau sur la e sortie standard, en le formattant. */ #include #include #include #include #include <unistd.h> <stdlib.h> <stdio.h> <sys/types.h> <sys/wait.h>

11. Processus

49

15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60

#define MAXLIGNE 30 void genere(int sortie) { char alphabet[26]; int k; for (k = 0; k < 26; k++) alphabet[k] = a + k; for (k = 0; k < 10; k++) if (write(sortie, alphabet, 26) != 26) { perror("write"); exit(EXIT_FAILURE); }; close(sortie); } int lire(int fd, char *buf, size_t count) { /* lecture, en insistant pour remplir le buffer */ int deja_lus = 0, n; while (deja_lus < count) { n = read(fd, buf + deja_lus, count - deja_lus); if (n == -1) return (-1); if (n == 0) break; /* plus rien ` lire */ a deja_lus += n; }; return (deja_lus); } void affiche(int entree) { char ligne[MAXLIGNE + 1]; int nb, numligne = 1; while ((nb = lire(entree, ligne, MAXLIGNE)) > 0) { ligne[nb] = \0; printf("%3d %s\n", numligne++, ligne); }; if (nb < 0) { perror("read"); exit(EXIT_FAILURE); } close(entree); } int main(void) { int fd[2], status; pid_t fils;

11. Processus

50

61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82

if (pipe(fd) != 0) { perror("pipe"); exit(EXIT_FAILURE); } if ((fils = fork()) < 0) { perror("fork"); exit(EXIT_FAILURE); } if (fils == 0) { /* le processus fils */ close(fd[0]); close(1); genere(fd[1]); exit(EXIT_SUCCESS); }; /* le processus p`re continue ici */ e close(0); close(fd[1]); affiche(fd[0]); wait(&status); printf("status fils = %d\n", status); exit(EXIT_SUCCESS); }

Exercice : Observez ce qui se passe si, dans la fonction affiche(), on remplace lappel ` lire() par un a read() ? Et si on ne fait pas le wait() ?

11.2

waitpid()

La fonction waitpid() permet dattendre larrt dun des processus ls dsign par son pid (nimporte lequel e e e si pid=-1 ), et de rcuprer ventuellement son code de retour. Elle retourne le numro du processus ls. e e e e Loption WNOHANG rend waitpid non bloquant (qui retourne alors -1 si le processus attendu nest pas termin). e
#include <sys/types.h> #include <sys/wait.h> pid_t waitpid (pid_t pid, int *status, int options);

Exemple :
int pid_fils; int status; if( (pid_fils = fork()) != 0) { code_processus_fils(); exit(EXIT_SUCCESS); }; ... if (waitpid(pid_fils,NULL,WMNOHANG) == -1) printf("Le processus fils nest pas encore termin\n"); e ...

11. Processus

51

11.3

exec()
#include <unistd.h> int execv (const char int execl (const char int execve(const char int execle(const char int execvp(const char int execlp(const char *FILENAME, *FILENAME, *FILENAME, *FILENAME, *FILENAME, *FILENAME, char *const ARGV[]) const char *ARG0,...) char *const ARGV[], char *const ENV[]) const char *ARG0,...char *const ENV[]) char *const ARGV[]) const char *ARG0, ...)

Ces fonctions font toutes la mme chose : activer un excutable ` la place du processus courant. Elles e e a di`rent par la mani`re dindiquer les param`tres. e e e execv() : les param`tres de la commande sont transmis sous forme dun tableau de pointeurs sur des e cha nes de caract`res (le dernier tant NULL). Exemple: e e
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 /* execv.c */ #include <unistd.h> #include <stdio.h> #include <stdlib.h> #define TAILLE_MAX_PREFIXE 10 #define TAILLE_MAX_NOMFICHIER 100 int main(void) { char prefixe[TAILLE_MAX_PREFIXE]; char nomfichier[TAILLE_MAX_NOMFICHIER]; char *args[] = { "gcc", NULL, "-o", NULL, NULL };

/* le nom du fichier source */ /* le nom de lexcutable e /* fin des param`tres e */ */

printf("prfixe du fichier ` compiler : "); e a scanf("%s", prefixe); /* dangereux */ snprintf(nomfichier, TAILLE_MAX_NOMFICHIER, "%s.c", prefixe); args[1] = nomfichier; args[3] = prefixe; execv("/usr/bin/gcc", args); perror("execv"); /* on ne passe pas ici */ exit(EXIT_FAILURE); }

execl() reoit un nombre variable de param`tres. Le dernier est NULL). Exemple: c e


1 /* execl.c */

11. Processus

52

2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

#include <unistd.h> #include <stdio.h> #include <stdlib.h> #define TAILLE_MAX_PREFIXE 10 #define TAILLE_MAX_NOMFICHIER 100 int main(void) { char prefixe[TAILLE_MAX_PREFIXE]; char nomfichier[TAILLE_MAX_NOMFICHIER]; printf("Prfixe du fichier ` compiler : "); e a scanf("%s", prefixe); /* dangereux */ snprintf(nomfichier, TAILLE_MAX_NOMFICHIER, "%s.c", prefixe); execl("/usr/bin/gcc", "gcc", nomfichier, "-o", prefixe, NULL); perror("execl"); exit(EXIT_FAILURE); } /* on ne passe jamais ici */

execve() et execle() ont un param`tre supplmentaire pour prciser lenvironnement. e e e execvp() et execlp() utilisent la variable denvironnement PATH pour localiser lexcutable ` lancer. e a On pourrait donc crire simplement: e
execlp("gcc","gcc",fichier,"-o",prefixe,NULL);

11.4

Numros de processus : getpid(), getppid() e


#include <unistd.h> pid_t getpid(void); pid_t getppid(void);

getpid() permet ` un processus de conna son propre numro, et getppid() celui de son p`re. a tre e e

11.5

Programmation dun dmon e

Les dmons 8 sont des processus qui tournent normalement en arri`re-plan pour assurer un service. Pour e e programmer correctement un dmon, il ne sut pas de faire un fork(), il faut aussi sassurer que le processus e restant ne bloque pas de ressources. Par exemple il doit librer le terminal de contrle du processus, revenir e o a ` la racine, faute de quoi il empchera le dmontage ventuel du syst`me de chiers ` partir duquel il a t e e e e a ee lanc. e
1 2 3 4
8

/* demon.c */ int devenir_demon(void) { int fd;

Traduction de langlais daemon, acronyme de Disk And Extension MONitor , qui dsignait une des parties e rsidentes dun des premiers syst`mes dexploitation. e e

12. Signaux

53

5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

/* Le processus se ddouble, et le p`re se termine */ e e if (fork() != 0) exit(EXIT_SUCCESS); /* le processus fils devient le leader dun nouveau groupe de processus */ setsid(); /* le processus fils cre le processus dmon, et e e se termine */ if (fork() != 0) exit(EXIT_SUCCESS); /* le dmon dmnage vers la racine */ e e e chdir("/"); /* lentre standard est redirige vers /dev/null */ e e fd = open("/dev/null", O_RDWR); dup2(fd, 0); close(fd); /* et les sorties vers /dev/console */ fd = open("/dev/console", O_WRONLY); dup2(fd, 1); dup2(fd, 2); close(fd); }

Voir FAQ Unix : 1.7 How do I get my program to act like a daemon

12
12.1

Signaux
signal()
#include <stdio.h> void (*signal(int signum, void (*handler)(int)))(int);

La fonction signal() demande au syst`me de lancer la fonction handler lorsque le signal signum est reu e c par le processus courant. La fonction signal() renvoie la fonction qui tait prcdemment associe au mme e e e e e signal. Il y a une trentaine de signaux dirents, e
9

parmi lesquels

SIGINT (program interrupt, mis par Ctrl-C), e SIGTST (terminal stop, mis par Ctrl-Z) e SIGTERM (demande de n de processus) SIGKILL (arrt immdiat de processus) e e
La liste compl`te des signaux, leur signication et leur comportement sont dcrits dans la page de manuel signal e e (chapitre 7 pour Linux).
9

12. Signaux

54

SIGFPE (erreur arithmtique), e SIGALRM (n de dlai, voir fonction alarm()), etc. e La fonction handler() prend en param`tre le numro du signal reu, et ne renvoie rien. e e c Exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 /* sig.c */ #include #include #include #include #include <stdlib.h> <signal.h> <errno.h> <unistd.h> <stdio.h>

void traitement(int signum) { printf("Signal %d => ", signum); switch (signum) { case SIGTSTP: printf("Je mendors....\n"); kill(getpid(), SIGSTOP); printf("Je me rveille !\n"); e signal(SIGTSTP, traitement); break; case SIGINT: case SIGTERM: printf("Fin du programme.\n"); exit(EXIT_SUCCESS); break; }; } int main(void) { signal(SIGTSTP, traitement); signal(SIGINT, traitement); signal(SIGTERM, traitement); while (1) { sleep(1); printf("."); fflush(stdout); }; printf("fin\n"); exit(EXIT_SUCCESS); }

/* auto-endormissement */ /* repositionnement */

/* si on reoit contr^le-Z */ c o /* si contr^le-C */ o /* si kill processus */

12.2

kill()
#include <unistd.h> int kill(pid_t pid, int sig);

La fonction kill() envoie un signal ` un processus. a

13. Les signaux Posix

55

12.3

alarm()
#include <unistd.h> long alarm(long delai);

La fonction alarm() demande au syst`me denvoyer un signal SIGALRM au processus dans un dlai x (en e e e secondes). Si une alarme tait dj` positionne, elle est remplace. Un dlai nul supprime lalarme existante. e ea e e e

12.4

pause()

La fonction pause() bloque le processus courant jusqu` ce quil reoive un signal. a c


#include<unistd.h> int pause(void);

Exercice : Ecrire une fonction quivalente ` sleep(). e a

13

Les signaux Posix

Le comportement des signaux classiques dUNIX est malheureusement dirent dune version ` lautre. On e a emploie donc de prfrence les mcanismes dnis par la norme POSIX, qui orent de plus la possibilit de ee e e e masquer des signaux.

13.1

Manipulation des ensembles de signaux

e Le type sigset t reprsente les ensembles de signaux.


#include <signal.h> int int int int int sigemptyset(sigset_t *set); sigfillset(sigset_t *set); sigaddset(sigset_t *set, int signum); sigdelset(sigset_t *set, int signum); sigismember(const sigset_t *set, int signum);

La fonction sigemptyset() cre un ensemble vide, sigaddset() ajoute un lment, etc. e ee

13.2

sigaction()
#include <signal.h> int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

La fonction sigaction() change laction qui sera excute lors de la rception dun signal. Cette action est e e e dcrite par une structure struct sigaction e

13. Les signaux Posix

56

struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); /* non utilis */ e }

sa handler indique laction associe au signal signum. Il peut valoir SIG DFL (action par dfaut), e e SIG IGN (ignorer), ou un pointeur vers une fonction de traitement de lu signal. le masque sa mask indique lensemble de signaux qui seront bloqus pendant lexcution de ce signal. e e Le signal lui-mme sera bloqu, sauf si SA NODEFER ou SA NOMASK gurent parmi les ags. e e Le champ sa flags contient une combinaison dindicateurs, parmi lesquels SA NOCLDSTOP pour le signal SIGCHLD, ne pas recevoir la notication darrt des processus ls (quand e les processus ls reoivent SIGSTOP, SIGTSTP, SIGTTIN ou SIGTTOU). c SA ONESHOT ou SA RESETHAND remet laction par dfaut quand le handler a t appel (cest le come ee e portement par dfaut du signal() classique). e SA SIGINFO indique quil faut utiliser la fonction sa sigaction() ` trois param`tres ` la place de a e a sa handler(). Exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 /* sig-posix.c */ #include #include #include #include #include <stdlib.h> <signal.h> <errno.h> <unistd.h> <stdio.h>

#define DELAI 1 #define NBTOURS 60 void traitement(int signum) { struct sigaction rien, ancien; printf("Signal %d => ", signum); switch (signum) { case SIGTSTP: printf("Jai reu un SIGTSTP.\n"); c /* on dsarme le signal SIGTSTP */ e rien.sa_handler = SIG_DFL; rien.sa_flags = 0; sigemptyset(&rien.sa_mask); /* rien ` masquer */ a sigaction(SIGTSTP, &rien, &ancien); printf("Alors je mendors....\n"); kill(getpid(), SIGSTOP); /* auto-endormissement */ printf("On me rveille ?\n"); e

14. Les processus lgers (Posix 1003.1c) e

57

24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51

/* remise en route */ sigaction(SIGTSTP, &ancien, NULL); printf("Cest reparti !\n"); break; case SIGINT: case SIGTERM: printf("On ma demand darr^ter le programme.\n"); e e exit(EXIT_SUCCESS); break; }; } int main(void) { struct sigaction a; int i; a.sa_handler = traitement; sigemptyset(&a.sa_mask); /* fonction ` lancer */ a /* rien ` masquer */ a /* pause contr^le-Z */ o /* fin contr^le-C */ o /* arr^t */ e

sigaction(SIGTSTP, &a, NULL); sigaction(SIGINT, &a, NULL); sigaction(SIGTERM, &a, NULL); for (i = 1; i < NBTOURS; i++) { sleep(DELAI); printf("%d", i % 10); fflush(stdout); }; printf("Fin\n"); exit(EXIT_SUCCESS); }

14

Les processus lgers (Posix 1003.1c) e

Les processus classiques dUNIX poss`dent des ressources spares (espace mmoire, table des chiers oue e e e verts...). Lorsquun nouveau l dexcution (processus ls) est cr par fork(), il se voit attribuer une copie e ee des ressources du processus p`re. e Il sensuit deux probl`mes : e probl`me de performances, puisque la duplication est un mcanisme coteux e e u probl`me de communication entre les processus, qui ont des variables spares. e e e Il existe des moyens dattnuer ces probl`mes : technique du copy-on-write dans le noyau pour ne dupliquer e e les pages mmoires que lorsque cest strictement ncessaire), utilisation de segments de mmoire partage e e e e (IPC) pour mettre des donnes en commun. Il est cependant apparu utile de dnir un mcanisme permettant e e e davoir plusieurs ls dexcution (threads) dans un mme espace de ressources non dupliqu : cest ce quon e e e appelle les processus lgers. Ces processus lgers peuvent se voir aecter des priorits. e e e

14. Les processus lgers (Posix 1003.1c) e

58

On remarquera que la commutation entre deux threads dun mme groupe est une opration conomique, e e e puisquil nest pas utile de recharger enti`rement la table des pages de la MMU. e Ces processus lgers ayant vocation ` communiquer entre eux, la norme Posix 1003.1c dnit galement des e a e e mcanismes de synchronisation : exclusion mutuelle (mutex ), smaphores, et conditions. e e Remarque : les smaphores ne sont pas dnis dans les biblioth`ques de AIX 4.2 et SVR4 dATT/Motorola. e e e Ils existent dans Solaris et les biblioth`ques pour Linux. e

14.1

Threads
#include <pthread.h> int pthread_create(pthread_t *thread, pthread_attr_t *attr, void * (*start_routine)(void *), void * arg); void pthread_exit(void *retval); int pthread_join(pthread_t th, void **thread_return);

La fonction pthread create demande le lancement dun nouveau processus lger, avec les attributs ine diqus par la structure pointe par attr (NULL = attributs par dfaut). Ce processus excutera la fonction e e e e start routine, en lui donnant le pointeur arg en param`tre. Lidentiant du processus lger est rang ` e e ea lendoit point par thread. e Ce processus lger se termine (avec un code de retour) lorsque la fonction qui lui est associe se termine par e e return retcode, ou lorsque le processus lger excute un pthread exit (retcode). e e La fonction pthread join permet au processus p`re dattendre la n dun processus lger, et de rcuprer e e e e ventuellement son code de retour. e Priorits : e Le fonctionnement des processus lgers peut tre modi (priorits, algoe e e e rithme dordonnancement, etc.) en manipulant les attributs qui lui sont associs. e Voir les fonctions pthread attr init, pthread attr destroy, pthread attr set-detachstate, pthread attr getdetachstate, pthread attr setschedparam, pthread attr getschedparam, pthread attr setschedpolicy, pthread attr getschedpolicy, pthread attr setinheritsched, pthread attr getinheritsched, pthread attr setscope, pthread attr getscope.

14.2

Verrous dexclusion mutuelle (mutex)


#include <pthread.h> int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr); int pthread_mutex_destroy(pthread_mutex_t *mutex); int pthread_mutex_lock(pthread_mutex_t *mutex)); int pthread_mutex_unlock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex);

Les verrous dexclusion mutuelle (mutex) sont crs par pthread mutex init. Il en est de dirents types ee e (rapides, rcursifs, etc.), selon les attributs points par le param`tre mutexattr. La valeur par dfaut e e e e (mutexattr=NULL) fait gnralement laaire. Lidenticateur du verrou est plac dans la variable pointe e e e e par mutex.

14. Les processus lgers (Posix 1003.1c) e

59

pthread mutex destroy dtruit le verrou. pthread mutex lock tente de le bloquer (et met le thread en e attente si le verrou est dj` bloqu), pthread mutex unlock le dbloque. pthread mutex trylock tente de ea e e bloquer le verrou, et choue si le verrou est dj` bloqu. e ea e

14.3

Exemple

Source :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 /* leger_mutex.c */ #include <pthread.h> #include <stdio.h> #include <unistd.h> struct Donnees { char *chaine; int nombre; int delai; }; pthread_mutex_t verrou; int numero = 0; void *ecriture(void *data) { int k; struct Donnees *d = data; for (k = 0; k < d->nombre; k++) { pthread_mutex_lock(&verrou); /* DEBUT SECTION CRITIQUE */ numero++; printf("[%d] %s\n", numero, d->chaine); pthread_mutex_unlock(&verrou); /* FIN SECTION CRITIQUE */ sleep(d->delai); }; return NULL; } int main(void) { pthread_t t1, t2; struct Donnees d1, d2; d1.nombre = 3; d1.chaine = "Hello"; d1.delai = 1; d2.nombre = 2; d2.chaine = "World"; d2.delai = 2; pthread_mutex_init(&verrou, NULL); pthread_create(&t1, NULL, ecriture, (void *) &d1); pthread_create(&t2, NULL, ecriture, (void *) &d2);

/* chaine ` crire a e */ /* nombre de rptitions */ e e /* dlai entre critures */ e e

14. Les processus lgers (Posix 1003.1c) e

60

38 39 40 41 42 43

pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_mutex_destroy(&verrou); printf(" %d lignes.\n", numero); exit(0); }

Compilation: Sous Linux, avec la biblioth`que linuxthreads de Xavier Leroy (INRIA), ce programme doit tre compil e e e avec loption -D REENTRANT et la biblioth`que libpthread: e
gcc -g -Wall -pedantic -D_REENTRANT leger_mutex.c -o leger_mutex -lpthread

Excution: e
% leger_mutex [1] Hello [2] World [3] Hello [4] Hello [5] World 5 lignes. %

14.4

Smaphores e

Les smaphores, qui font partie de la norme POSIX, ne sont pas implments dans toutes les biblioth`ques e e e e de threads.
#include <semaphore.h> int sem_init(sem_t *sem, int pshared, unsigned int value); int sem_destroy(sem_t * sem); int sem_wait(sem_t * sem); int sem_post(sem_t * sem); int sem_trywait(sem_t * sem); int sem_getvalue(sem_t * sem, int * sval);

Les smaphores sont crs par sem init, qui place lidenticateur du smaphore ` lendroit point par sem. e ee e a e La valeur initiale du smaphore est dans value. Si pshared est nul, le smaphore est local au processus lourd e e (le partage de smaphores entre plusieurs processus lourds nest pas implment dans la version courante de e e e linuxthreads.). sem wait et sem post sont les quivalents respectifs des primitives P et V de Dijkstra. La fonction e sem trywait choue (au lieu de bloquer) si la valeur du smaphore est nulle. Enn, sem getvalue cone e sulte la valeur courante du smaphore. e Exercice: Utiliser un smaphore au lieu dun mutex pour scuriser lexemple. e e

15. Communication entre processus (IPC System V)

61

14.5

Conditions

Les conditions servent ` mettre en attente des processus lgers derri`re un mutex. Une primitive permet de a e e dbloquer dun seul coup tous les threads bloqus par un mme condition. e e e
#include <pthread.h> int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr); int pthread_cond_destroy(pthread_cond_t *cond); int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond);

Les conditions sont cres par phtread cond init, et dtruites par phtread cond destroy. ee e Un processus se met en attente en eectuant un phtread cond wait (ce qui bloque au passage un mutex). La primitive phtread cond broadcast dbloque tous les processus qui attendent sur une condie tion, phtread cond signal en dbloque un seul. e

15

Communication entre processus (IPC System V)

Les mcanismes de communication entre processus (InterProcess Communication, ou IPC ) dUnix System e V ont t repris dans de nombreuses variantes dUnix. Il y a 3 mcanismes : ee e les segments de mmoire partage, e e les smaphores, e les les de messages. Ces trois types dobjets sont identis par des cls. e e

15.1

ftok() constitution dune cl e


# include <sys/types.h> # include <sys/ipc.h> key_t ftok ( char *pathname, char project )

La fonction ftok() constitue une cl ` partir dun chemin dacc`s et dun caract`re indiquant un projet ea e e . Plutt que de risquer une explication abstraite, tudions deux cas frquents : o e e On dispose dun logiciel commun dans /opt/jeux/OuiOui. Ce logiciel utilise deux objets partags. On e pourra utiliser les cls ftok("/opt/jeux/OuiOui",A) et ftok("/opt/jeux/OuiOui",B). Ainsi e tous les processus de ce logiciel se rf`reront aux mmes objets qui seront partags entre tous les ee e e utilisateurs.

15. Communication entre processus (IPC System V)

62

On distribue un exemple aux tudiants, qui le recopient chez eux et le font tourner. On souhaite que e les processus dun mme tudiant communiquent entre eux, mais quils ninterf`rent pas avec dautres. e e e On basera donc la cl sur une donne personnelle, par exemple le rpertoire daccueil, avec les cls e e e e ftok(getenv("HOME"),A) et ftok(getenv("HOME"),B).

15.2

Mmoires partages e e

Ce mcanisme permet ` plusieurs programmes de partager des segments mmoire. Chaque segment mmoire e a e e est identi, au niveau du syst`me, par une cl ` laquelle correspond un identiant. Lorsquun segment e e e a est attach ` un programme, les donnes quil contient sont accessibles en mmoire par lintermdiaire dun e a e e e pointeur.
#include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, int size, int shmflg); char *shmat (int shmid, char *shmaddr, int shmflg ) int shmdt (char *shmaddr) int shmctl(int shmid, int cmd, struct shmid_ds *buf);

La fonction shmget() donne lidentiant du segment ayant la cl key. Un nouveau segment (de taille size) e est cr si key est IPC PRIVATE, ou bien si les indicateurs de shmflg contiennent IPC CREAT. Combines, ee e les options IPC EXCL | IPC CREAT indiquent que le segment ne doit pas exister pralablement. Les bits de e poids faible de shmflg indiquent les droits dacc`s. e shmat() attache le segment shmid en mmoire, avec les droits spcis dans shmflag (SHM R, SHM W, e e e SHM RDONLY). shmaddr prcise o` ce segment doit tre situ dans lespace mmoire (la valeur NULL demande e u e e e un placement automatique). shmat() renvoie ladresse o` le segment a t plac. u ee e shmdt() lib`re le segment. shmctl() permet diverses oprations, dont la destruction dune mmoire e e e partage (voir exemple). e Exemple (deux programmes): Le producteur :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 /* prod.c */ /* Ce programme lit une suite de nombres, et effectue le cumul dans une variable en mmoire partage. */ e e #include #include #include #include #include #include <sys/ipc.h> <sys/shm.h> <sys/types.h> <stdlib.h> <stdio.h> <errno.h>

struct donnees { int nb; int total; }; int main(void)

15. Communication entre processus (IPC System V)

63

16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64

{ key_t cle; int id; struct donnees *commun; int reponse; cle = ftok(getenv("HOME"), A); if (cle == -1) { perror("ftok"); exit(EXIT_FAILURE); } id = shmget(cle, sizeof(struct donnees), IPC_CREAT | IPC_EXCL | 0666); if (id == -1) { switch (errno) { case EEXIST: fprintf(stderr, "Le segment existe dj`\n"); e a break; default: perror("shmget"); break; } exit(EXIT_FAILURE); } commun = (struct donnees *) shmat(id, NULL, SHM_R | SHM_W); if (commun == NULL) { perror("shmat"); exit(EXIT_FAILURE); } commun->nb = 0; commun->total = 0; while (1) { printf("+ "); if (scanf("%d", &reponse) != 1) break; commun->nb++; commun->total += reponse; printf("sous-total %d= %d\n", commun->nb, commun->total); }; printf("---\n"); if (shmdt((char *) commun) == -1) { perror("shmdt"); exit(EXIT_FAILURE); } /* suppression segment */ if (shmctl(id, IPC_RMID, NULL) == -1) { perror("shmctl(remove)"); exit(EXIT_FAILURE); };

15. Communication entre processus (IPC System V)

64

65 66

exit(EXIT_SUCCESS); }

Le consommateur :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 /* cons.c */ /* Ce programme affiche priodiquement le contenu de la e mmoire partage. Arr^t par Contr^le-C e e e o */ #include #include #include #include #include #include #include #include <sys/ipc.h> <sys/shm.h> <sys/types.h> <unistd.h> <stdlib.h> <stdio.h> <errno.h> <signal.h>

#define DELAI 2 struct donnees { int nb; int total; }; int encore; void arret(int signal) { encore = 0; } int main(void) { key_t cle; int id; struct donnees *commun; struct sigaction a; cle = ftok(getenv("HOME"), A); if (cle == -1) { perror("ftok"); exit(EXIT_FAILURE); } id = shmget(cle, sizeof(struct donnees), 0); if (id == -1) { switch (errno) { case ENOENT: printf("pas de segment\n"); exit(EXIT_SUCCESS); default:

15. Communication entre processus (IPC System V)

65

42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67

perror("shmget"); exit(EXIT_FAILURE); } } commun = (struct donnees *) shmat(id, NULL, SHM_R); if (commun == NULL) { perror("shmat"); exit(EXIT_FAILURE); } encore = 1; a.sa_handler = arret; sigemptyset(&a.sa_mask); a.sa_flags = 0; sigaction(SIGINT, &a, NULL); while (encore) { sleep(DELAI); printf("sous-total %d= %d\n", commun->nb, commun->total); } printf("---\n"); if (shmdt((char *) commun) == -1) { perror("shmdt"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); }

Question : le second programme nache pas forcment des informations cohrentes. Pourquoi ? Quy e e faire ? Probl`me : crire deux programmes qui partagent deux variables i, j. Voici le pseudo-code: e e
processus P1 | i=0 j=0 | repeter indefiniment | i++ j++ fin processus P2 | tant que i==j | faire rien | ecrire i fin

Au bout de combien de temps le processus P2 sarrte-t-il ? Faire plusieurs essais. e Exercice : la commande ipcs ache des informations sur les segments qui existent. Ecrire une commande qui permet dacher le contenu dun segment (on donne le shmid et la longueur en param`tres). e

15.3

Smaphores e
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semget(key_t key, int nsems, int semflg )

15. Communication entre processus (IPC System V)

66

int semop(int semid, struct sembuf *sops, unsigned nsops) int semctl(int semid, int semnum, int cmd, union semun arg )

Les oprations System V travaillent en fait sur des tableaux de smaphores gnraliss (pouvant voluer par e e e e e e une valeur enti`re quelconque). e La fonction semget() demande ` travailler sur le smaphore gnralis qui est identi par la cl key (mme a e e e e e e e notion que pour les cls des segments partags) et qui contient nsems smaphores individuels. Un nouveau e e e smaphore est cr, avec les droits donns par les 9 bits de poids faible de semflg, si key est IPC PRIVATE, e ee e ou si semflg contient IPC CREAT. semop() agit sur le smaphore semid en appliquant simultanment ` plusieurs smaphores individuels les e e a e actions dcrites dans les nsops premiers lments du tableau sops. Chaque sembuf est une structure de la e ee forme:
struct sembuf { ... short sem_num; short sem_op; short sem_flg; ... }

/* semaphore number: 0 = first */ /* semaphore operation */ /* operation flags */

sem flg est une combinaison dindicateur qui peut contenir IPC NOWAIT et SEM UNDO (voir manuel). Ici nous supposons que sem flg est 0. sem num indique le numro du smaphore individuel sur lequel porte lopration. sem op est un entier destin e e e e (sauf si il est nul) ` tre ajout ` la valeur courante semval du smaphore. Lopration se bloque si sem op ae ea e e + semval < 0. Cas particulier : si sem op est 0, lopration est bloque tant que semval est non nul. e e Les valeurs des smaphores ne sont mises ` jour que lorsque aucun deux nest bloqu. e a e semctl permet de raliser diverses oprations sur les smaphores, selon la commande demande. En partice e e e ulier, on peut xer le n-i`me smaphore ` la valeur val en faisant : e e a
semctl(sem,n,SETVAL,val);

Exemple: primitives sur les smaphores traditionnels. e


1 2 3 4 5 6 7 8 9 10 11 /* sem.c */ /* Oprations sur des smaphores e e */ #include #include #include #include #include #include #include <sys/types.h> <sys/ipc.h> <sys/sem.h> <unistd.h> <stdio.h> <stdlib.h> <ctype.h>

15. Communication entre processus (IPC System V)

67

12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58

typedef int SEMAPHORE; void detruire_sem(SEMAPHORE sem) { if (semctl(sem, 0, IPC_RMID, 0) != 0) { perror("detruire_sem"); exit(EXIT_FAILURE); }; } void changer_sem(SEMAPHORE sem, int val) { struct sembuf sb[1]; sb[0].sem_num = 0; sb[0].sem_op = val; sb[0].sem_flg = 0; if (semop(sem, sb, 1) != 0) { perror("changer_sem"); exit(EXIT_FAILURE); }; } SEMAPHORE creer_sem(key_t key) { SEMAPHORE sem; int r; sem = semget(key, 1, IPC_CREAT | 0666); if (sem < 0) { perror("creer_sem"); exit(EXIT_FAILURE); }; r = semctl(sem, 0, SETVAL, 0); /* valeur initiale = 0 */ if (r < 0) { perror("initialisation smaphore"); e exit(EXIT_FAILURE); }; return sem; } void P(SEMAPHORE sem) { changer_sem(sem, -1); } void V(SEMAPHORE sem) { changer_sem(sem, 1); } /* --------------------------------------------- */ int main(int argc, char *argv[]) { SEMAPHORE sem;

15. Communication entre processus (IPC System V)

68

59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95

key_t key; int encore = 1; if (argc != 2) { fprintf(stderr, "Usage: %s cle\n", argv[0]); exit(EXIT_FAILURE); }; key = atoi(argv[1]); sem = creer_sem(key); while (encore) { char reponse; printf("p,v,x,q ? "); if (scanf("%c", &reponse) != 1) break; switch (toupper(reponse)) { case P: P(sem); printf("Ok.\n"); break; case V: V(sem); printf("Ok.\n"); break; case X: detruire_sem(sem); printf("Smaphore dtruit\n"); e e encore = 0; break; case Q: encore = 0; break; default: printf("?\n"); }; }; printf("Bye.\n"); exit(EXIT_SUCCESS); }

Exercice : que se passe-t-il si on essaie dinterrompre semop() ? Exercice : utilisez les smaphores pour scuriser lexemple prsent sur les mmoires partages. e e e e e e

15.4

Files de messages

Ce mcanisme permet lchange de messages par des processus. Chaque message poss`de un corps de e e e longueur variable, et un type (entier strictement positif) qui peut servir ` prciser la nature des informations a e contenues dans le corps. Au moment de la rception, on peut choisir de slectionner les messages dun type donn. e e e
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h>

15. Communication entre processus (IPC System V)

69

int msgget (key_t key, int msgflg) int msgsnd (int msqid, struct msgbuf *msgp, int msgsz, int msgflg) int msgrcv (int msqid, struct msgbuf *msgp, int msgsz, long msgtyp, int msgflg) int msgctl ( int msqid, int cmd, struct msqid_ds *buf )

msgget() demande lacc`s ` (ou la cration de) la le de message avec la cl key. msgget() retourne la e a e e valeur de lidenticateur de le. msgsnd() envoie un message dans la le msqid. Le corps de ce message contient msgsz octets, il est plac, e prcd par le type dans le tampon point par msgp. Ce tampon de la forme: e e e e
struct msgbuf { long mtype; /* message type, must be > 0 */ char mtext[...] /* message data */ };

msgrcv() lit dans la le un message dun type donn (si type > 0) ou indirent (si type==0), et le place e e dans le tampon point par msgp. La taille du corps ne pourra excder msgsz octets, sinon il sera tronqu. e e e msgrcv() renvoie la taille du corps du message. Exemple. Deux programmes, lun pour envoyer des messages (lignes de texte) sur une le avec un type donn, lautre pour acher les messages reus. e c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 /* snd.c */ /* envoi des messages dans une file (IPC System V) */ #include #include #include #include <errno.h> <stdio.h> <stdlib.h> <stdio.h>

#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #define MAXTEXTE 1000 struct tampon { long mtype; char mtext[MAXTEXTE]; }; int main(int argc, char *argv[]) { int cle, id, mtype; if (argc != 3) { fprintf(stderr, "Usage: %s cl type\n", argv[0]); e exit(1); }; cle = atoi(argv[1]); mtype = atoi(argv[2]);

15. Communication entre processus (IPC System V)

70

26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

id = msgget(cle, 0666); if (id == -1) { perror("msgget"); exit(EXIT_FAILURE); }; while (1) { int l; struct tampon msg; printf("> "); fgets(msg.mtext, MAXTEXTE, stdin); l = strlen(msg.mtext); msg.mtype = mtype; if (msgsnd(id, (struct msgbuf *) &msg, l + 1, 0) == -1) { perror("msgsnd"); exit(EXIT_FAILURE); }; }; }

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

/* rcv.c */ /* affiche les messages qui proviennent dune file (IPC System V) */ #include #include #include #include <errno.h> <stdio.h> <stdlib.h> <stdio.h>

#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #define MAXTEXTE 1000 struct tampon { long mtype; char mtext[MAXTEXTE]; }; int encore = 1; int main(int argc, char *argv[]) { int cle, id; if (argc != 2) { fprintf(stderr, "Usage: %s cle\n", argv[0]); exit(1); }; cle = atoi(argv[1]); id = msgget(cle, IPC_CREAT | 0666);

16. Communication par le rseau TCP-IP e

71

28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

if (id == -1) { perror("msgget"); exit(EXIT_FAILURE); }; while (encore) { int l; struct tampon msg; l = msgrcv(id, (struct msgbuf *) &msg, MAXTEXTE, 0L, 0); if (l == -1) { perror("msgrcv"); exit(EXIT_FAILURE); }; printf("(type=%ld) %s\n", msg.mtype, msg.mtext); }; exit(EXIT_SUCCESS); }

16

Communication par le rseau TCP-IP e

Les concepts fondamentaux (sockets, adresses, communication par datagrammes et ots, client-serveur etc.) sont les mmes que pour les sockets locaux (voir 10 (sockets)). Les particularits viennent des adresses : e e comment fabriquer une adresse ` partir dun nom de machine (rsolution) et dun numro de port, comment a e e retrouver le nom dune machine ` partir dune adresse (rsolution inverse) etc. a e

16.1

Sockets, addresses
#include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol);

Pour communiquer, les applications doivent crer des sockets (prises bidirectionnelles) par la fonction e socket() et les relier entre elles. On peut ensuite utiliser ces sockets comme des chiers ordinaires (par read, write, ...) ou par des oprations spciques ( send, sendto, recv, recvfrom, ...). e e Pour dsigner un socket sur une machine il faut une adresse de socket. Comme il existe dirents types de e e sockets, les oprations sur les adresses concerne un type dadresse gnral abstrait (struct sockaddr) qui e e e recouvre tous les types concrets particuliers. Pour TCP-IP (Inet), les adresses de sockets sont dtermines par un numro IP, et un numro de port. Pour e e e e IPv4, on utilise des struct sockaddr in, qui poss`dent 3 champs importants : e sin family, la famille dadresses, valant AF INET sin addr, pour ladresse IP. sin port, pour le numro de port e Attention, les octets de ladresse IP et le numro de port sont stocks dans lordre rseau (big-endian), qui e e e nest pas forcment celui de la machine hte sur laquelle sexcute le programme. Voir plus loin les fonctions e o e de conversion hte/rseau. o e

16. Communication par le rseau TCP-IP e

72

Pour IPv6, on utilise des struct sockaddr in6, avec sin6 family, valant AF INET6 sin6 addr, ladresse IP sur 6 octets sin6 port, pour le numro de port. e

16.2

Remplissage dune adresse

Comme pour les sockets locaux, on cre les sockets par socket() et on nomme la prise par bind(). e 16.2.1 Prparation dune adresse distante e

Dans le cas le plus frquent, on dispose du nom de la machine destinataire (par exemple la cha de e ne caract`res "www.elysee.fr"), et du numro de port (un entier). e e
#include <netdb.h> struct hostent *gethostbyname(const char *name);

gethostbyname() retourne un pointeur vers une structure hostent qui contient diverses informations sur la machine en question, en particulier une adresse h addr 10 que lon mettra dans sin addr. Le champ sin port est un entier court en ordre rseau, pour y mettre un entier ordinaire il faut le convertir e par htons()11 . Voir exemple client-echo.c. 16.2.2 Prparation dune adresse locale e

Pour une adresse sur la machine locale12 , on utilise dans le cas le plus frquent, ladresse INADDR ANY (0.0.0.0). Le socket est alors ouvert (avec le mme e e numro de port) sur toutes les adresses IP de toutes les interfaces de la machine. e ladresse INADDR LOOPBACK correspondant ` ladresse locale 127.0.0.1 (alias localhost). Le socket a nest alors accessible que depuis la machine elle-mme. e une des adresses IP de la la machine. ladresse de diusion gnrale (broadcast) INADDR BROADCAST (255.255.255.255)13 e e Voir exemple serveur-echo.c. Pour IPV6 Famille dadresses AF INET6, famille de protocoles PF INET6 adresses prdnies IN6ADD LOOPBACK INIT (::1) IN6ADD ANY INIT e e
Une machine peut avoir plusieurs adresses Host TO Network Short 12 ne pas confondre avec la notion de socket du domaine local vue en 10 (sockets) 13 Lutilisation de ladresse de diusion est soumise ` restrictions, voir manuel a
11 10

16. Communication par le rseau TCP-IP e

73

16.2.3

Examen dune adresse

La fonction getsockname() permet de retrouver ladresse associe ` un socket. e a


#include <sys/socket.h> int getsockname(int s , struct sockaddr *name socklen_t * namelen )

Le numro de port est dans le champ sin port, pour le convertir en entier normal, utiliser ntohs() (Network e TO Host short). Ladresse IP peut tre convertie en cha e ne de caract`res en notation dcimale pointe (exemple e e e "147.210.94.194") par inet ntoa() (network to ascii),
#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> char *inet_ntoa(struct in_addr in);

Cest ce qui est utilis dans serveur-echo.c pour dterminer et acher la provenance des requtes. e e e On peut galement tenter une rsolution inverse, cest-`-dire de retrouver le nom ` partir de ladresse, en e e a a passant par gethostbyaddr, qui retourne un pointeur vers une structure hostent, dont le champ h name dsigne le nom ociel de la machine. e Cette rsolution naboutit pas toujours, parce que tous les numros IP ne correspondent pas ` des machines e e a dclares. e e
#include <netdb.h> extern int h_errno; struct hostent *gethostbyaddr(const char *addr, int len, int type); struct hostent { char *h_name; /* official name of host */ char **h_aliases; /* alias list */ int h_addrtype; /* host address type */ int h_length; /* length of address */ char **h_addr_list; /* list of addresses */ } #define h_addr h_addr_list[0] /* for backward compatibility */

16.3

Fermeture dun socket

Un socket peut tre ferm par close() ou par shutdown(). e e


int shutdown(int fd, int how);

Un socket est bidirectionnel, le param`tre how indique quelle(s) moiti(s) on ferme : 0 pour la sortie, 1 pour e e lentre, 2 pour les deux (quivaut ` close()). e e a

16. Communication par le rseau TCP-IP e

74

16.4
16.4.1

Communication par datagrammes (UDP)


Cration dun socket e

#include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol);

Cette fonction construit un socket et retourne un numro de descripteur. Pour une liaison par datagrammes e sur IPv4, utilisez la famille AF INET, le type SOCK DGRAM et le protocole par dfaut 0. e Retourne -1 en cas dchec. e 16.4.2 Connexion de sockets

La fonction connect met en relation un socket (de cette machine) avec un autre socket dsign, qui sera le e e correspondant par dfaut pour la suite des oprations. e e
#include <sys/types.h> #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);

16.4.3

Envoi de datagrammes

Sur un socket connect (voir ci-dessus), on peut expdier des datagrammes (contenus dans un tampon t de e e longueur n) par write(sockfd,t,n). La fonction send()
int send(int s, const void *msg, size_t len, int flags);

permet dindiquer des ags, par exemple MSG DONTWAIT pour une criture non bloquante. e Enn, sendto() envoie un datagramme ` une adresse spcie, sur un socket connect ou non. a e e e
int sendto(int s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);

16.4.4

Rception de datagrammes e

Inversement, la rception peut se faire par un simple read(), par un recv() (avec des ags), ou par un e recvfrom, qui permet de rcuprer ladresse from de lmetteur. e e e
int recv(int s, void *buf, size_t len, int flags); int recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);

16. Communication par le rseau TCP-IP e

75

16.4.5 Principe:

Exemple UDP : serveur dcho e

Le client envoie une cha de caract`res au serveur. ne e Le serveur lache, la convertit en minuscules, et la rexpdie. e e Le client ache la rponse. e Usage: sur le serveur : serveur-echo numro-de-port e pour chaque client : client-echo nom-serveur numro-de-port message ` expdier e a e Le client
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 /* client-echo.c Envoi de datagrammes Exemple de client qui - ouvre un socket - envoie des datagrammes sur ce socket (lignes de textes de lentre standard) e - attend une rponse e - affiche la rponse e */ #include #include #include #include #include #include #include #include #include <unistd.h> <sys/types.h> <sys/socket.h> <netinet/in.h> <signal.h> <stdio.h> <netdb.h> <string.h> <stdlib.h>

#define TAILLE_TAMPON 1000 static int fd; void ErreurFatale(char message[]) { printf("CLIENT> Erreur fatale\n"); perror(message); exit(EXIT_FAILURE); } int main(int argc, char *argv[]) { struct sockaddr_in adresse_socket_serveur; struct hostent *hote;

16. Communication par le rseau TCP-IP e

76

32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73

int taille_adresse_socket_serveur; char *nom_serveur; int numero_port_serveur; char *requete, reponse[TAILLE_TAMPON]; int longueur_requete, longueur_reponse; /* 1. rception des param`tres de la ligne de commande */ e e if (argc != 4) { printf("Usage: %s hote port message\n", argv[0]); exit(EXIT_FAILURE); }; nom_serveur = argv[1]; numero_port_serveur = atoi(argv[2]); requete = argv[3]; /* 2. Initialisation du socket */ /* 2.1 cration du socket en mode datagramme */ e fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) ErreurFatale("Creation socket"); /* 2.2 recherche de la machine serveur */ hote = gethostbyname(nom_serveur); if (hote == NULL) ErreurFatale("Recherche serveur"); /* 2.3 Remplissage adresse serveur */ adresse_socket_serveur.sin_family = AF_INET; adresse_socket_serveur.sin_port = htons(numero_port_serveur); adresse_socket_serveur.sin_addr = *(struct in_addr *) hote->h_addr; taille_adresse_socket_serveur = sizeof adresse_socket_serveur; longueur_requete = strlen(requete) + 1; /* 3. Envoi de la requ^te */ e printf("REQUETE> %s\n", requete); if (sendto(fd, requete, longueur_requete, 0, /* flags */ (struct sockaddr *) &adresse_socket_serveur, taille_adresse_socket_serveur) < 0) ErreurFatale("Envoi requete"); /* 4. Lecture de la rponse */ e longueur_reponse = recvfrom(fd, reponse, TAILLE_TAMPON, 0, NULL, 0); if (longueur_reponse < 0) ErreurFatale("Attente rponse"); e

16. Communication par le rseau TCP-IP e

77

74 75 76 77 78

printf("REPONSE> %s\n", reponse); close(fd); printf("CLIENT> Fin.\n"); exit(0); }

Exercice : faire en sorte que le client rexpdie sa requte si il ne reoit pas la rponse dans un dlai x. e e e c e e e Fixer une limite au nombre de tentatives. Le serveur
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 /* serveur-echo.c -

Rception de datagrammes e

Exemple de serveur qui - ouvre un socket sur un port en mode non-connect e - affiche les messages (cha^nes de caract`res) e quil reoit par ce socket. c - envoie une rponse e */ #include #include #include #include #include #include #include #include #include <unistd.h> <sys/types.h> <sys/socket.h> <netinet/in.h> <signal.h> <stdio.h> <stdlib.h> <arpa/inet.h> <ctype.h>

#define TAILLE_TAMPON 1000 static int fd; void ErreurFatale(char message[]) { printf("SERVEUR> Erreur fatale\n"); perror(message); exit(EXIT_FAILURE); } void ArretServeur(int signal) { close(fd); printf("SERVEUR> Arr^t du serveur (signal %d)\n", signal); e exit(EXIT_SUCCESS); } int main(int argc, char *argv[]) { struct sockaddr_in adresse_serveur; size_t taille_adresse_serveur; int numero_port_serveur;

16. Communication par le rseau TCP-IP e

78

37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77

char *src, *dst; struct sigaction a; /* 1. rception des param`tres de la ligne de commande */ e e if (argc != 2) { printf("usage: %s port\n", argv[0]); exit(1); }; numero_port_serveur = atoi(argv[1]); /* 2. Si une interruption se produit, arr^t du serveur */ e /* signal(SIGINT,ArretServeur); */ a.sa_handler = ArretServeur; sigemptyset(&a.sa_mask); a.sa_flags = 0; sigaction(SIGINT, &a, NULL); /* 3. Initialisation du socket de rception */ e /* 3.1 Cration du socket en mode non-connect e e (datagrammes) */ fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) ErreurFatale("socket"); /* 3.2 Remplissage de ladresse de rception e (protocole Internet TCP-IP, rception accepte sur toutes e e les adresses IP du serveur, numro de port indiqu) e e */ adresse_serveur.sin_family = AF_INET; adresse_serveur.sin_addr.s_addr = INADDR_ANY; adresse_serveur.sin_port = htons(numero_port_serveur); /* 3.3 Association du socket au port de rception */ e taille_adresse_serveur = sizeof adresse_serveur; if (bind(fd, (struct sockaddr *) &adresse_serveur, taille_adresse_serveur) < 0) ErreurFatale("bind"); printf("SERVEUR> Le serveur coute le port %d\n", e numero_port_serveur); while (1) { struct sockaddr_in adresse_client; int taille_adresse_client; char tampon_requete[TAILLE_TAMPON], tampon_reponse[TAILLE_TAMPON]; int lg_requete, lg_reponse;

16. Communication par le rseau TCP-IP e

79

78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107

/* 4. Attente dun datagramme (requ^te) */ e taille_adresse_client = sizeof(adresse_client); lg_requete = recvfrom(fd, tampon_requete, TAILLE_TAMPON, 0, (struct sockaddr *) &adresse_client, (socklen_t *) & taille_adresse_client); if (lg_requete < 0) ErreurFatale("recfrom"); /* 5. Affichage message avec sa provenance et sa longueur */ printf("%s:%d [%d]\t: %s\n", inet_ntoa(adresse_client.sin_addr), ntohs(adresse_client.sin_port), lg_requete, tampon_requete); /* 6. Fabrication dune rponse */ e src = tampon_requete; dst = tampon_reponse; while ((*dst++ = toupper(*src++)) != \0); lg_reponse = strlen(tampon_reponse) + 1; /* 7. Envoi de la rponse */ e if (sendto(fd, tampon_reponse, lg_reponse, 0, (struct sockaddr *) &adresse_client, taille_adresse_client) < 0) ErreurFatale("Envoi de la rponse"); e }; }

/* flags */

16.5

Communication par ots de donnes (TCP) e

La cration dun socket pour TCP se fait ainsi e


int fd; .. fd=socket(AF_INET,SOCK_STREAM,0);

16.5.1

Programmation des clients TCP

Le socket dun client TCP doit tre reli (par connect()) ` celui du serveur, et il est utilis ensuite par des e e a e read() et des write(), ou des entres-sorties de haut niveau fprintf(), fscanf(), etc. si on a dni des e e ots par fdopen().

16. Communication par le rseau TCP-IP e

80

16.5.2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

Exemple : client web


/* client-web.c */ /* Interrogation dun serveur web Usage: client-web serveur port adresse-document retourne le contenu du document dadresse http://serveur:port/adresse-document Exemple: client-web www.info.prive 80 /index.html Fonctionnement: - ouverture dune connexion TCP vers serveur:port - envoi de la requ^te e GET adresse-document HTTP/1.0[cr][lf][cr][lf] - affichage de la rponse e */ #include #include #include #include #include #include #include #include #include <unistd.h> <sys/types.h> <sys/socket.h> <netinet/in.h> <signal.h> <stdio.h> <netdb.h> <string.h> <stdlib.h>

#define CRLF "\r\n" #define TAILLETAMPON 1000 /* -- connexion vers un serveur TCP --------------------------- */ int connexion_tcp(char nom_serveur[], int port_serveur) { struct sockaddr_in addr_serveur; struct hostent *serveur; int fd; fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) { perror("socket"); exit(EXIT_FAILURE); } serveur = gethostbyname(nom_serveur); if (serveur == NULL) { perror("gethostbyname"); exit(EXIT_FAILURE); } addr_serveur.sin_family = AF_INET; /* cration prise */ e

/* recherche adresse serveur */

16. Communication par le rseau TCP-IP e

81

43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88

addr_serveur.sin_port = htons(port_serveur); addr_serveur.sin_addr = *(struct in_addr *) serveur->h_addr; if (connect(fd, /* connexion au serveur */ (struct sockaddr *) &addr_serveur, sizeof addr_serveur) < 0) { perror("connect"); exit(EXIT_FAILURE); } return (fd); } /* ------------------------------------------------------ */ void demander_document(int fd, char adresse_document[]) { char requete[TAILLETAMPON]; int longueur; /* constitution de la requ^te, suivie dune ligne vide */ e longueur = snprintf(requete, TAILLETAMPON, "GET %s HTTP/1.0" CRLF CRLF, adresse_document); write(fd, requete, longueur); /* envoi */ } /* ------------------------------------------------------ */ void afficher_reponse(int fd) { char tampon[TAILLETAMPON]; int longueur; while (1) { longueur = read(fd, tampon, TAILLETAMPON); /* lecture par bloc */ if (longueur <= 0) break; write(1, tampon, longueur); /* copie sur sortie standard */ }; } /* -- main --------------------------------------------------- */ int main(int argc, char *argv[]) { char *nom_serveur, *adresse_document; int port_serveur; int fd; if (argc != 4) { printf("Usage: %s serveur port adresse-document\n", argv[0]); exit(EXIT_FAILURE); }; nom_serveur = argv[1];

16. Communication par le rseau TCP-IP e

82

89 90 91 92 93 94 95 96

port_serveur = atoi(argv[2]); adresse_document = argv[3]; fd = connexion_tcp(nom_serveur, port_serveur); demander_document(fd, adresse_document); afficher_reponse(fd); close(fd); exit(EXIT_SUCCESS); }

Remarque: souvent il est plus commode de crer des ots de haut niveau au dessus du socket (voir fdopen()) e que de manipuler des read et des write. Voir dans lexemple suivant. 16.5.3 Raliser un serveur TCP e

Un serveur TCP doit traiter des connexions venant de plusieurs clients. Apr`s avoir cr et nomm le socket, le serveur spcie quil accepte les communications entrantes par e ee e e listen(), et se met eectivement en attente dune connexion de client par accept().
#include <sys/types.h> #include <sys/socket.h> int listen(int s, int backlog); int accept(int s, struct sockaddr *addr, socklen_t *addrlen);

Le param`tre backlog indique la taille maximale de la le des connexions en attente. Sous Linux la limite e est donne par la constante SOMAXCONN (qui vaut 128), sur dautres syst`mes elle est limite ` 5. e e e a La fonction accept() renvoie un autre socket, qui servira ` la communication avec le client. Ladresse du a client peut tre obtenue par les param`tres addr et addrlen. e e En gnral les serveurs TCP doivent traiter plusieurs connexions simultanment. La solution habituelle est e e e de lancer, apr`s lappel ` accept() un processus ls (par fork())qui traite la communication avec un seul e a client. Ceci induit une gestion des processus, donc des signaux lis ` la terminaison des processus ls. e a

16.6

Exemple TCP : un serveur Web

Dans ce qui suit nous prsentons un serveur Web rudimentaire, capable de fournir des pages Web construites e a ` partir des chiers dun rpertoire. Nous donnons deux implmentations possibles, ` laide de processus e e a lourds et lgers. e 16.6.1 Serveur Web (avec processus)

Cette version suit lapproche traditionnelle. Pseudo-code:


ouvrir socket serveur (socket/bind/listen) rpter indfiniment e e e | attendre larrive dun client (accept) e | crer un processus (fork) et lui dlguer e e e

16. Communication par le rseau TCP-IP e

83

| la communication avec le client fin-rpeter e 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 /* -----------------------------------------------Serveur TCP serveur web, qui renvoie les fichiers (textes) dun rpertoire sous forme de pages HTML e usage : serveur-web port repertoire exemple: serveur-web 8000 /usr/doc/exemples --------------------------------------------------*/ #include #include #include #include #include #include #include #include #include #include #include <unistd.h> <sys/types.h> <sys/errno.h> <sys/socket.h> <sys/wait.h> <sys/stat.h> <netinet/in.h> <arpa/inet.h> <signal.h> <stdio.h> <stdlib.h>

#include "declarations.h" void fin_serveur(int sig); void fin_fils(int sig); int fdServeur; /* variable globale, pour partager avec traitement signal fin_serveur */

void demarrer_serveur(int numero_port, char repertoire[]) { int numero_client = 0; int fdClient; struct sigaction action_fin, action_fin_fils; fdServeur = serveur_tcp(numero_port); /* positionnement des signaux SIGINT et SIGCHLD */ action_fin.sa_handler = fin_serveur; sigemptyset(&action_fin.sa_mask); action_fin.sa_flags = 0; sigaction(SIGINT, &action_fin, NULL); action_fin_fils.sa_handler = fin_fils; sigemptyset(&action_fin_fils.sa_mask); action_fin_fils.sa_flags = SA_NOCLDSTOP; sigaction(SIGCHLD, &action_fin_fils, NULL); printf("> Serveur " VERSION " (port=%d, rpertoire de documents=\"%s\")\n", e

16. Communication par le rseau TCP-IP e

84

41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88

numero_port, repertoire); while (1) { struct sockaddr_in a; size_t l = sizeof a; fdClient = attendre_client(fdServeur); getsockname(fdClient, (struct sockaddr *) &a, &l); numero_client++; printf("> client %d [%s]\n", numero_client, inet_ntoa(a.sin_addr)); if (fork() == 0) { /* le processus fils ferme le socket serveur et soccupe du client */ close(0); close(1); close(2); close(fdServeur); dialogue_client(fdClient, repertoire); close(fdClient); exit(EXIT_SUCCESS); } /* le processus p`re na plus besoin du socket client. e Il le ferme et repart dans la boucle */ close(fdClient); } } /* ------------------------------------------------------------Traitement des signaux --------------------------------------------------------------- */ void fin_serveur(int sig) { printf("=> fin du serveur\n"); shutdown(fdServeur, 2); /* utile ? */ close(fdServeur); exit(EXIT_SUCCESS); } void fin_fils(int sig) { /* cette fonction est appele chaque fois quun signal SIGCHLD e indique la fin dun processus fils _au moins_. */ while (waitpid(-1, NULL, WNOHANG) > 0) { /* attente des fils arr^ts, tant quil y en a */ e e }; } /* -------------------------------------------------------------*/ void usage(char prog[]) { printf("Usage : %s [options\n\n", prog); printf("Options :"

16. Communication par le rseau TCP-IP e

85

89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121

"-h\tcemessage\n" "-p port\tport du serveur [%d]\n" "-d dir \trpertoire des documents [%s]\n", e PORT_PAR_DEFAUT, REPERTOIRE_PAR_DEFAUT); } /* -------------------------------------------------------------*/ int main(int argc, char *argv[]) { int port = PORT_PAR_DEFAUT; char *repertoire = REPERTOIRE_PAR_DEFAUT; /* la racine des documents */ char c; while ((c = getopt(argc, argv, "hp:d:")) != -1) switch (c) { case h: usage(argv[0]); exit(EXIT_SUCCESS); break; case p: port = atoi(optarg); break; case d: repertoire = optarg; break; case ?: fprintf(stderr, "Option inconnue -%c. -h pour aide.\n", optopt); break; }; demarrer_serveur(port, repertoire); exit(EXIT_SUCCESS); }

16.6.2

Serveur Web (avec threads)

Les processus lgers permettent une autre approche : on cre pralablement un pool de processus que e e e lon bloque. Lorsquun client se prsente, on cone la communication ` un processus inoccup. e a e
ouvrir socket serveur (socket/bind/listen) crer un pool de processus e rpter indfiniment e e e | attendre larrive dun client (accept) e | trouver un processus libre, et lui | confier la communication avec le client fin-rpeter e

1 2 3

/* serveur-web.c */ /* -----------------------------------------------Serveur TCP

16. Communication par le rseau TCP-IP e

86

4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48

serveur web, qui renvoie les fichiers (textes) dun rpertoire sous forme de pages HTML e usage : serveur-web port repertoire exemple: serveur-web 8000 /usr/doc/exemples Version base sur les threads. Au lieu de crer e e un processus par connexion, on g`re un pool de t^ches e a - au dmarrage du serveur les t^ches sont crees, e a e et bloques par un verrou e - quand un client se connecte, la connexion est confie ` une t^che inactive, qui est dbloque e a a e e pour loccasion. --------------------------------------------------*/ #include #include #include #include #include #include #include #include #include #include #include #include <pthread.h> <unistd.h> <sys/types.h> <sys/errno.h> <sys/socket.h> <sys/wait.h> <sys/stat.h> <netinet/in.h> <arpa/inet.h> <signal.h> <stdio.h> <stdlib.h>

#include "declarations.h" void fin_serveur(int sig); #define NB_TACHES 5 struct tache { pthread_t id; /* identificateur de thread */ pthread_mutex_t verrou; int actif; /* 1 => le client est occupe */ int fd; /* socket du client */ char *repertoire; }; struct tache taches[NB_TACHES]; int fdServeur; /* variable globale, pour partager avec traitement signal fin_serveur */ /* ------------------------------------------------------travail_tache: contient le code de laction lance pour chaque t^che e a ------------------------------------------------------- */ void *travail_tache(void *data) { struct tache *t = data; /* les donnes spcifiques e e de la t^che */ a

16. Communication par le rseau TCP-IP e

87

49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94

while (1) { pthread_mutex_lock(&t->verrou); dialogue_client(t->fd, t->repertoire); close(t->fd); t->actif = 0; }; return NULL; } /* ------------------------------------------------------- */ void creer_taches(char repertoire[]) { int k; for (k = 0; k < NB_TACHES; k++) { struct tache *t = taches + k; t->actif = 0; t->repertoire = repertoire; pthread_mutex_init(&t->verrou, NULL); pthread_mutex_lock(&t->verrou); pthread_create(&t->id, NULL, travail_tache, (void *) t); } } /* ----------------------------------------------------demarrer_serveur: cre le socket serveur e et lance des processus pour chaque client ----------------------------------------------------- */ int demarrer_serveur(int numero_port, char repertoire[]) { int numero_client = 0; int fdClient; struct sigaction action_fin; printf("> Serveur " VERSION "+threads " "(port=%d, rpertoire de documents=\"%s\")\n", e numero_port, repertoire); /* positionnement du signaux SIGINT */ action_fin.sa_handler = fin_serveur; sigemptyset(&action_fin.sa_mask); action_fin.sa_flags = 0; sigaction(SIGINT, &action_fin, NULL); /* cration du socket serveur et du pool de t^ches */ e a fdServeur = serveur_tcp(numero_port); creer_taches(repertoire); /* boucle du serveur */ while (1) { struct sockaddr_in a; size_t l = sizeof a;

16. Communication par le rseau TCP-IP e

88

95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143

int k; fdClient = attendre_client(fdServeur); getsockname(fdClient, (struct sockaddr *) &a, &l); numero_client++; /* recherche dune t^che inoccupe */ a e for (k = 0; k < NB_TACHES; k++) if (taches[k].actif == 0) break; if (k == NB_TACHES) { /* pas de t^che libre ? */ a printf ("> client %d [%s] rejet (surcharge)\n", e numero_client, inet_ntoa(a.sin_addr)); close(fdClient); } else { /* affectation du travail et dblocage de la t^che */ e a printf ("> client %d [%s] trait par t^che %d\n", e a numero_client, inet_ntoa(a.sin_addr), k); taches[k].fd = fdClient; taches[k].actif = 1; pthread_mutex_unlock(&taches[k].verrou); } } } /* ------------------------------------------------------------Traitement des signaux --------------------------------------------------------------- */ void fin_serveur(int sig) { printf("=> fin du serveur\n"); shutdown(fdServeur, 2); /* utile ? */ close(fdServeur); exit(EXIT_SUCCESS); } /* -------------------------------------------------------------*/ void usage(char prog[]) { printf("Usage : %s [options\n\n", prog); printf("Options :" "-h\tcemessage\n" "-p port\tport du serveur [%d]\n" "-d dir \trpertoire des documents [%s]\n", e PORT_PAR_DEFAUT, REPERTOIRE_PAR_DEFAUT); } /* -------------------------------------------------------------*/ int main(int argc, char *argv[]) { int port = PORT_PAR_DEFAUT; char *repertoire = REPERTOIRE_PAR_DEFAUT; /* la racine

16. Communication par le rseau TCP-IP e

89

144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166

des documents */ char c; while ((c = getopt(argc, argv, "hp:d:")) != -1) switch (c) { case h: usage(argv[0]); exit(EXIT_SUCCESS); break; case p: port = atoi(optarg); break; case d: repertoire = optarg; break; case ?: fprintf(stderr, "Option inconnue -%c. -h pour aide.\n", optopt); break; }; demarrer_serveur(port, repertoire); exit(EXIT_SUCCESS); }

16.6.3

Parties communes aux deux serveurs

Les dclarations de constantes et les enttes des fonctions communes : e e


1 2 3 4 5 6 7 8 9 10 11 12 13 14 /* Serveur Web - declarations.h */ #define CRLF "\r\n" #define VERSION "MegaSoft 0.0.7 pour Unix" #define PORT_PAR_DEFAUT 8000 #define REPERTOIRE_PAR_DEFAUT "/tmp" #define FATAL(err) { perror((char *) err); exit(1);} /* -Prototypes des fonctions ----------------------- */

extern void dialogue_client(int fdClient, char repertoire[]); extern void envoyer_document(FILE * out, char nom_document[], char repertoire[]); extern void document_non_trouve(FILE * out, char nom_document[]); extern void requete_invalide(FILE * out); extern int serveur_tcp(int numero_port); extern int attendre_client(int fdServeur);

les fonctions rseau : e serveur tcp() : cration du socket du serveur TCP. e attendre client()

16. Communication par le rseau TCP-IP e

90

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

/* Projet serveur Web - reseau.c Fonctions rseau e */ #include #include #include #include #include #include #include #include #include <sys/types.h> <sys/errno.h> <sys/socket.h> <sys/wait.h> <sys/stat.h> <netinet/in.h> <signal.h> <stdio.h> <stdlib.h>

#include "declarations.h" /* -----------------------------------------------------------Fonctions rseau e ------------------------------------------------------------ */ int serveur_tcp(int numero_port) { int fd; /* dmarre un service TCP sur le port indiqu */ e e struct sockaddr_in addr_serveur; size_t lg_addr_serveur = sizeof addr_serveur; /* cration de la prise */ e fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) FATAL("socket"); /* nommage de la prise */ addr_serveur.sin_family = AF_INET; addr_serveur.sin_addr.s_addr = INADDR_ANY; addr_serveur.sin_port = htons(numero_port); if (bind (fd, (struct sockaddr *) &addr_serveur, lg_addr_serveur) < 0) FATAL("bind"); /* ouverture du service */ listen(fd, 4); return (fd); } int attendre_client(int fdServeur) { int fdClient; /* A cause des signaux SIGCHLD, la fonction accept() peut etre interrompue quand un fils se termine.

16. Communication par le rseau TCP-IP e

91

45 46 47 48 49 50 51 52

Dans ce cas, on relance accept(). */ while ((fdClient = accept(fdServeur, NULL, NULL)) < 0) { if (errno != EINTR) FATAL("Fin anormale de accept()."); }; return (fdClient); }

les fonction de dialogue avec le client: dialogue client() : lecture et traitement de la requte dun client e envoyer document(), document non trouve(), requete invalide().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 /* traitement-client.c projet serveur WEB Communication avec un client */ #include #include #include #include <stdio.h> <stdlib.h> <string.h> <unistd.h>

#include "declarations.h" void dialogue_client(int fdClient, char repertoire[]) { FILE *in, *out; char verbe[100], nom_document[100]; int fd2; /* Ouverture de fichiers de haut niveau */ in = fdopen(fdClient, "r"); /* note : si on attache out au m^me descripteur que in, e la fermeture de lun entraine la fermeture de lautre */ fd2 = dup(fdClient); out = fdopen(fd2, "w"); /* lecture de la requ^te, du genre e "GET quelquechose ..." */ fscanf(in, "%100s %100s", verbe, nom_document); fclose(in); if (strcmp(verbe, "GET") == 0) envoyer_document(out, nom_document, repertoire); else requete_invalide(out);

16. Communication par le rseau TCP-IP e

92

30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79

fflush(out); fclose(out); }

/* utile ? */

void envoyer_document(FILE * out, char nom_document[], char repertoire[]) { char nom_fichier[100]; FILE *fichier; char ligne[100]; sprintf(nom_fichier, "%s%s", repertoire, nom_document); if (strstr(nom_fichier, "/../") != NULL) { /* tentative dacc`s hors du rpertoire ! */ e e document_non_trouve(out, nom_document); return; }; fichier = fopen(nom_fichier, "r"); if (fichier == NULL) { document_non_trouve(out, nom_document); return; }; fprintf(out, "HTTP/1.1 200 OK" CRLF "Server: " VERSION CRLF "Content-Type: text/html; charset=iso-8859-1" CRLF CRLF); fprintf(out, "<html><head><title>Fichier %s</title></head>" "<body bgcolor=\"white\"><h1>Fichier %s</h1>" CRLF "<center><table><tr><td bgcolor=\"yellow\"><listin CRLF, nom_document, nom_fichier); /* le corps du fichier */ while (fgets(ligne, 100, fichier) > 0) { char *p; for (p = ligne; *p != \0; p++) { switch (*p) { case <: fputs("&lt;", out); break; case >: fputs("&gt;", out); break; case &: fputs("&amp;", out); break; case \n: fputs(CRLF, out); break; default: fputc(*p, out); };

17. Documentation

93

80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116

}; }; /* balises de fin */ fputs("</listing></table></center></body></html>" CRLF, } void document_non_trouve(FILE * out, char nom_document[]) { /* envoi de la rponse : ent^te */ e e fprintf(out, "HTTP/1.1 404 Not Found" CRLF "Server: MegaSoft 0.0.7 (CP/M)" CRLF "Content-Type: text/html; charset=iso-8859-1" CRLF CRLF); /* corps de la rponse */ e fprintf(out, "<HTML><HEAD>" CRLF "<TITLE>404 Not Found</TITLE>" CRLF "</HEAD><BODY BGCOLOR=\"yellow\">" CRLF "<H1>Pas trouv !</H1>" CRLF e "Le document <font color=\"red\"><tt>%s</tt></font> " "demand<br>nest pas disponible.<P>" CRLF e "<hr> Le webmaster" "</BODY></HTML>" CRLF, nom_document); fflush(out); } void requete_invalide(FILE * out) { fprintf(out, "<HTML><HEAD>" CRLF "<TITLE>400 Bad Request</TITLE>" CRLF "</HEAD><BODY BGCOLOR=\"yellow\">" CRLF "<H1>Bad Request</H1>" "Vous avez envoy une requ^te que " e e "ce serveur ne comprend pas." CRLF "<hr> Le webmaster" "</BODY></HTML>" CRLF); fflush(out); }

Exercice : modier traitement-client pour quil traite le cas des rpertoires. Il devra alors acher le e nom des objets de ce rpertoire, leur type, leur taille et un lien. e

17

Documentation

Les documents suivants ont t tr`s utiles pour la rdaction de ce texte et la programmation des exemples : ee e e Unix Frequently Asked Questions <http://www.erlenstar.daemon.co.uk/unix/faq_toc.html>

17. Documentation

94

HP-UX Reference, volume 2 (section 2 and 3, C programming routines). HP-UX release 8.0, HewlettPackard. (Janvier 1991). Advanced Unix Programming, Marc J. Rochkind, Prentice-Hall Software Series (1985). ISBN 0-13011800-1. Linux online man pages (Distribution Slackware 3.1). The GNU C Library Reference Manual , Sandra Loosemore, Richard M. Stallman, Roland McGrath, and Andrew Oram. Edition 0.06, 23-Dec-1994, pour version 1.09beta. Free Software Foundation, ISBN 1-882114-51-1. What is multithreading ? , Martin McCarthy, Linux Journal 34, Fvrier 1997, pages 31 ` 40. e a Syst`mes dexploitation distribus, Andrew Tanenbaum, InterEditions 1994, ISBN 2-7296-0706-4. e e Page Web de Xavier Leroy sur les threads : <http://pauillac.inria.fr/~xleroy/linuxthreads> Sources :
$Source: /home/billaud/COURS/POLYSYSRESEAU-2002-octobre/RCS/sysreseau.sgml,v $ $Date: 2002/10/18 18:17:36 $