Vous êtes sur la page 1sur 86

Outils de Communication Rseau sous Unix BSD 4.

X e
Philippe Durif 1999

Contents
1 Le 1.1 1.2 1.3 1.4 concept dInternet Internet : un rseau de rseaux (format des adresses e e IP : protocole de la couche rseau . . . . . . . . . . e UDP et TCP : protocoles de la couche transport . . Internet et Unix BSD 4.X (i.e. les Suns) . . . . . . 1.4.1 La table des machines (hosts) . . . . . . . . 1.4.2 La table des rseaux (networks) . . . . . . . e 1.4.3 La table des services (/etc/services) . . . 1.4.4 Manipulation des adresses Internet . . . . . 1.4.5 Conversions machine/rseau . . . . . . . . . e . . . . . . . . . . . . . . . . . . . . . 1 1 2 2 2 3 4 5 5 6 7 7 8 8 9 9 10 12 13 13 13 13 14 14 14 15 15 15 15 16 17 17 18 18 18

Internet) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

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

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

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

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

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

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

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

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

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

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

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

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

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

2 Adresses de la couche transport 2.1 Format gnral des adresses . . . . . . . . . . e e 2.2 Format des adresses du domaine Unix . . . . . 2.3 Format des adresses du domaine Internet . . . 2.4 Gestion des numros de port . . . . . . . . . . e 2.4.1 La table des services (/etc/services) 2.5 Fabriquer des adresses Internet . . . . . . . . 2.5.1 Utilitaires BSTRING(3) . . . . . . . .

3 Les sockets 3.1 Caractristiques dune socket . . . . . . . . . . . . . . . . . e 3.1.1 Domaines de communication dune socket . . . . . . 3.1.2 Types de communication dune socket . . . . . . . . 3.1.3 Protocole dune socket . . . . . . . . . . . . . . . . . 3.2 Primitives gnrales sur les sockets . . . . . . . . . . . . . . e e 3.2.1 Crer une socket : socket() . . . . . . . . . . . . . . e 3.2.2 Dtruire une socket : close() . . . . . . . . . . . . . e 3.2.3 Rduire les fonctionnalits dune socket : shutdown() e e 3.2.4 Associer une adresse a une socket : bind() . . . . . . ` 3.2.5 Consulter ladresse dune socket : getsockname() . . 3.2.6 Exemples dutilisations de bind() . . . . . . . . . . . 3.2.7 Connexion de socket : connect() . . . . . . . . . . . 3.2.8 Rception de message : recv() . . . . . . . . . . . . e 3.2.9 Emission de message : send() . . . . . . . . . . . . . 3.2.10 Lecture et criture : read(), write() . . . . . . . . . e 3.2.11 Attente slective : select() . . . . . . . . . . . . . . e 3

CONTENTS 3.3 Notion de client et de serveur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.4 Autres aspects des sockets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 22

4 Les 4.1 4.2 4.3

sockets de type datagram (SOCK DGRAM) 25 R`gles gnrales dutilisation des primitives . . . . . . . . . . . . . . . . . . . . . . . . 25 e e e Exemples dutilisation des sockets SOCK DGRAM . . . . . . . . . . . . . . . . . . . 26 Comment concevoir un nouveau service . . . . . . . . . . . . . . . . . . . . . . . . . . 29 33 34 34 34 34 36 38 38 41 41 41 41 42 42 42 43 43 43 43 43 44 44 44 45 45 45 45 46 47 49 49 49 50 50

5 Les sockets de type circuit virtuel (SOCK STREAM) 5.1 Les primitives spciques aux serveurs en circuits virtuels e 5.1.1 listen() . . . . . . . . . . . . . . . . . . . . . . 5.1.2 accept() . . . . . . . . . . . . . . . . . . . . . . 5.2 Un premier exemple . . . . . . . . . . . . . . . . . . . . 5.3 Utilisation des sockets SOCK STREAM . . . . . . . . . 5.4 Autres exemples . . . . . . . . . . . . . . . . . . . . . . . 5.4.1 Un serveur de piles parall`le et multi-processus . e

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

6 Le mcanisme XDR (eXternal Data Representation) e 6.1 Gnralits sur le mcanisme XDR . . . . . . . . . . . . . . . . . . . . e e e e 6.2 Fonctions de gestion des ots XDR . . . . . . . . . . . . . . . . . . . . 6.2.1 fdopen() et fflush() . . . . . . . . . . . . . . . . . . . . . . . 6.2.2 xdrstdio create() . . . . . . . . . . . . . . . . . . . . . . . . 6.2.3 xdr destroy() . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.3 Fonctions de transmission XDR . . . . . . . . . . . . . . . . . . . . . . 6.3.1 Fonctions de transmission sans allocation dynamique . . . . . . 6.3.1.1 xdr void() . . . . . . . . . . . . . . . . . . . . . . . . 6.3.1.2 xdr int() . . . . . . . . . . . . . . . . . . . . . . . . . 6.3.1.3 xdr opaque() . . . . . . . . . . . . . . . . . . . . . . . 6.3.1.4 xdr union() . . . . . . . . . . . . . . . . . . . . . . . 6.3.2 Fonctions de transmission avec allocation dynamique ventuelle e 6.3.2.1 xdr string() . . . . . . . . . . . . . . . . . . . . . . . 6.3.2.2 xdr wrapstring() . . . . . . . . . . . . . . . . . . . . 6.3.2.3 xdr array() . . . . . . . . . . . . . . . . . . . . . . . 6.3.2.4 xdr bytes() . . . . . . . . . . . . . . . . . . . . . . . 6.3.2.5 xdr reference() . . . . . . . . . . . . . . . . . . . . . 6.3.2.6 xdr pointer() . . . . . . . . . . . . . . . . . . . . . . 6.3.3 Libration de zone xdr free() . . . . . . . . . . . . . . . . . . e 6.4 Un exemple complet . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.5 Construction de nouvelles fonctions XDR . . . . . . . . . . . . . . . . . 6.5.1 XDR de structures . . . . . . . . . . . . . . . . . . . . . . . . . 6.5.2 XDR de tableau de taille variable . . . . . . . . . . . . . . . . . 6.5.3 XDR de structures contenant un tableau . . . . . . . . . . . . . 6.5.4 XDR de liste cha ee . . . . . . . . . . . . . . . . . . . . . . . . n

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

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

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

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

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

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

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

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

CONTENTS 7 Appel de procdure loigne SunOS 4.x (RPC) e e e 7.1 Rappel . . . . . . . . . . . . . . . . . . . . . . . . 7.2 Architecture des RPC de SunOS 4.x . . . . . . . 7.2.1 Situation des RPC . . . . . . . . . . . . . 7.2.2 Nommage des services RPC . . . . . . . . 7.2.3 Le processus portmap . . . . . . . . . . . 7.2.4 Lenregistrement dun serveur . . . . . . . 7.2.5 Le mcanisme dappel vu du client . . . . e 7.2.6 Smantique des appels . . . . . . . . . . . e 7.3 Primitives RPC de haut niveau . . . . . . . . . . 7.3.1 Le client . . . . . . . . . . . . . . . . . . . 7.3.1.1 callrpc() . . . . . . . . . . . . 7.3.1.2 clnt perrno() . . . . . . . . . . 7.3.2 Le serveur . . . . . . . . . . . . . . . . . . 7.3.2.1 registerrpc() . . . . . . . . . . 7.3.2.2 svc run() . . . . . . . . . . . . . 7.3.3 Exemple : un compte bancaire . . . . . . . 7.4 Primitives RPC de bas niveau . . . . . . . . . . . 7.4.1 Le client . . . . . . . . . . . . . . . . . . . 7.4.1.1 clnt create () . . . . . . . . . 7.4.1.2 clnt control () . . . . . . . . . 7.4.1.3 clnt pcreateerror () . . . . . 7.4.1.4 clnt destroy() . . . . . . . . . 7.4.1.5 clnt call() . . . . . . . . . . . 7.4.1.6 clnt freeres() . . . . . . . . . 7.4.1.7 clnt perror() . . . . . . . . . . 7.4.2 Le serveur . . . . . . . . . . . . . . . . . . 7.4.2.1 svctcp create() . . . . . . . . . 7.4.2.2 svc register() . . . . . . . . . 7.4.2.3 Le dispatcher . . . . . . . . . . . 7.4.3 Exemple : le compte bancaire . . . . . . . 7.5 RPC non bloquant . . . . . . . . . . . . . . . . . 7.6 Diusion dappel . . . . . . . . . . . . . . . . . . 7.7 Rtro appel . . . . . . . . . . . . . . . . . . . . . e 7.8 RPC scuriss . . . . . . . . . . . . . . . . . . . . e e 7.8.1 Exemple : le compte bancaire scuris . . . e e 8 Le 8.1 8.2 8.3

5 53 53 53 53 53 54 55 55 55 55 56 56 56 56 56 57 57 58 58 59 59 60 60 60 60 60 60 61 61 61 62 63 64 64 64 64

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

gnrateur RPCGEN e e 67 Ecriture de linterface en rpcgen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 Ecriture des services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 Ecriture dun client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 71 71 72 72

9 Linterface TLI 9.1 Architecture des TLI . . . . . . . . . . . . . . . . . . . . . . . 9.1.1 Fournisseurs de transport ou TP (Transport Provider) 9.1.2 Ouverture dun tep : t open() . . . . . . . . . . . . . . 9.1.3 Publication dun tep : t bind() . . . . . . . . . . . . .

6 9.1.4 Les vnements : t look() . . . . . . . e e 9.1.5 Les tats . . . . . . . . . . . . . . . . . e 9.1.6 Les structures de donnes : t alloc() e 9.2 Environnement de dveloppement . . . . . . . e 9.3 Un exemple de communication en T COTS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

CONTENTS . . . . . . . . . . . . . . . . . . . . . . . . . 72 73 76 77 77

Chapter 1 Le concept dInternet


1.1 Internet : un rseau de rseaux (format des adresses e e Internet)

Lide de lInternet (projet DARPA) est de fdrer dirents rseaux htrog`nes dans un seul e e e e e ee e super-rseau ; en particulier, la technologie Internet ne fait pas dispara les rseaux existant, elle e tre e sappuie sur eux. Internet propose dune part une technique dadressage de rseaux et de machines, e dautre part une suite de protocoles. Gnralement les adresses rseaux ne dsignent pas des machines mais plutt des points dacc`s e e e e o e au rseau, un point dacc`s est, par exemple, une carte (carte Ethernet, carte Token Ring). Ainsi, e e une machine quipe de plusieurs cartes une passerelle par exemple aura plusieurs adresses. e e Par abus de langage, on parle souvent dadresse de machine (host). Lors de sa fabrication, chaque carte Ethernet est marque dun numro unique de 48 bits. Lunicit e e e de ce numro fait quil peut reprsenter ladresse de la carte, quel que soit lenvironnement dans lequel e e elle est plonge. e En revanche, une carte Token Ring porte un numro logiciel aect par ladministrateur local du e e rseau. Une telle adresse nest valable que dans ce rseau. e e Dans Internet, une adresse de point dacc`s est code sur 32 bits et poss`de deux niveaux, les bits e e e de poids forts permettent didentier le rseau parmi lensemble des rseaux connus par Internet, les e e bits de poids faible permettent de dsigner un point dacc`s particulier (une machine) dans ce rseau. e e e La gestion des adresses Internet est hirarchique : le NIC (Network Information Control Center) e distribue des fourchettes dadresses rseau a des organismes ociels (pour la France cest lINRIA) e ` qui se chargent dallouer des adresses aux organismes demandeurs. La partie machine dune adresse est gre localement. Ce fonctionnement assure lunicit dune adresse Internet. ee e Internet propose trois formats dadresse ce qui autorise une certaine souplesse vis a vis de la taille ` des rseaux physiques (voir gure 1.1). e Les protocoles de lInternet sont nombreux, dont : IP qui est le protocole rseau de base, e UDP et TCP sont les protocoles de transport construits sur IP, ICMP protocole de contrle (message derreur, rgulation de ux, ...), o e ARP (resp. RARP) spciques au rseau Ethernet et qui permet, connaissant ladresse Internet e e (resp. Ethernet) dune machine, dobtenir son adresse Ethernet (resp. Internet). 1

2 Classe dadresses A B C format

CHAPTER 1. LE CONCEPT DINTERNET nombre de rseaux de points dacc`s e e 128 16M 16K 64K 2M 256

0rrrrrrr mmmmmmmm mmmmmmmm mmmmmmmm 10rrrrrr rrrrrrrr mmmmmmmm mmmmmmmm 110rrrrr rrrrrrrr rrrrrrrr mmmmmmmm

Figure 1.1: Les trois classes dadresses Internet, les r dsignent les bits dadresse de rseau, les m ceux e e de point dacc`s. La classe A correspond aux rares tr`s grands rseaux, le rseau de Lille 1 est de e e e e classe B.

1.2

IP : protocole de la couche rseau e

IP Internet Protocol est un protocole de commutation de paquet de point dacc`s a point e ` dacc`s (couche rseau de lISO). Ses caractristiques sont son manque de abilit (pas daccus de e e e e e rception), lordre darrive nest pas forcment le mme que lordre de dpart, mais il y a prservation e e e e e e des fronti`res denregistrement. Typiquement un paquet IP poss`de une borne suprieure pour sa e e e taille, son format est indiqu a la gure 1.2. e` adresse Internet metteur e adresse Internet destinataire information utile

Figure 1.2: Format simpli dun paquet IP e

1.3

UDP et TCP : protocoles de la couche transport

Les protocoles UDP User Datagram Protocol et TCP Transport Control Protocol se situent au dessus dIP ; ils assurent tous deux la transmission dinformation dapplication ` a application et non plus de point dacc`s a point dacc`s ; plutt que dapplication on parlera de e ` e o service ou encore de SAP1 . Plusieurs services pouvant tourner sur une mme machine, on les distingue e grce a un numro de port (sur Sun cest un u short). Ladresse dun service est donc un couple a ` e adresse Internet, numro de port. e UDP est une simple surcouche de IP (commutation de paquet) ; TCP assure une commutation de circuit virtuel avec les qualits qui en dcoulent (abilit, respect de lordre dmission) mais il ne e e e e prserve pas les fronti`res denregistrement. e e Les services ociels (well-known), comme FTP ou TELNET, portent des numros de port ale lous par un organisme centralisateur et sont infrieurs a IPPORT_RESERVED, constante dnie dans e e ` e <netinet/in.h>. Les ports au del` de IPPORT_USERRESERVED sont en principe utilisables librement. a

1.4

Internet et Unix BSD 4.X (i.e. les Suns)

Le syst`me Unix BSD 4.X des Suns int`gre le concept Internet, le rseau physique tant la e e e e plupart du temps Ethernet. Chaque Sun poss`dent donc deux adresses par point dacc`s : une adresse e e Ethernet (pour les communications de la couche liaison de donnes : 802.3) et une adresse Internet. e
1

selon la terminologie ISO : Service Access Point.

1.4. INTERNET ET UNIX BSD 4.X (I.E. LES SUNS)

Les outils de dveloppement rseau BSD 4.X proposent donc une approche Internet qui se matrialise e e e dans la notion de socket donnant acc`s aux protocoles UDP et TCP. e Dautre part, les Macs du labo sont sur un rseau AppleTalk qui ne conna pas Internet, il est e t cependant interconnect avec Ethernet par une passerelle FastPath qui simule un adressage Internet e des dirents Macs. e Pour ladministration du rseau on dispose de plusieurs tables qui mettent en relation des noms e externes symboliques (comme homel, ftpd, ...) et des noms internes (adresses Internet, numro de e port, ...). Les tables qui nous intressent sont celles des machines, des rseaux et des services. Pour e e chacune de ces tables on dispose dun interface shell et dun interface programme.

1.4.1

La table des machines (hosts)

Elle met en relation les noms symboliques des machines et leurs adresses Internet. linterface shell Il propose le chier /etc/hosts et la commande ypcat hosts qui utilise le NIS 2 ; cette derni`re forme est plus s re, en eet le chier /etc/hosts local nest pas forcment a jour. e u e ` Chaque ligne correspond a une machine et contient de gauche a droite ladresse Internet compl`te (4 ` ` e champs dcimaux spars par des points), le nom symbolique ociel puis une liste ventuellement e e e e vide des noms alias. Voici les formats dadresses correspondant aux trois classes A, B et C : Classe A B C avec ri et si compris entre 0 et 255. linterface programme Il propose les primitives de GETHOSTENT(3N) qui utilise la structure struct hostent { char *h name ; /* official name of host */ char **h aliases ; /* alias list, terminee par NULL */ int h addrtype ; /* address type i.e. AF INET */ int h length ; /* length of address : 4 octets pour Internet */ char **h addr list ; /* liste des addresses, pour Internet la premiere */ /* est la bonne et est en format reseau. */ /* ATTENTION ! ! ! il faut y acceder avec : */ /* (unsigned long **) p->h addr list */ #define h addr h addr list[0] /* address, for backward compatiblity */ };
2

Format r1 .s3 .s2 .s1 r2 .r1 .s2 .s1 r3 .r2 .r1 .s1

Network Information System : base de donne accessible par toutes les machines du rseau e e

4 Les primitives sont :

CHAPTER 1. LE CONCEPT DINTERNET

#include <sys/types.h> #include <sys/socket.h> #include <netdb.h> struct hostent *gethostent (void), *gethostbyname (char *i name) ; Elles renvoient chacune un pointeur sur une structure hostent qui contient les champs dune ligne du chier /etc/hosts. gethostent() permet un parcours squentiel des entres et renvoie NULL e e apr`s la derni`re. e e Attention : le pointeur renvoy rep`re une zone statique dont le contenu est cras a chaque appel. e e e e` Les adresses (32 bits) sont fournies dans le format du rseau (cf BYTEORDER), pas dans celui e de la machine. Exemple tir de la commande ypcat hosts e 127.0.0.1 134.206.1.1 ... 134.206.10.65 134.206.10.66 ... 134.206.11.247 ... 134.206.12.1 134.206.12.10 localhost citil # CYBER 962-32 brigant gordon mac27 homel ms_mailhost ms9

localhost reprsente la machine sur laquelle tourne le processus (` cause de 127 qui est un numro e a e spcial). e homel est le nom ociel, il a un alias : ms_mailhost. 134 (1000 0110b) correspond a des adresses ` de classe B, ladresse rseau de homel est donc 134.206, cest en fait ladresse, tout a fait ocielle, e ` du rseau lilnet (le rseau de LILLE 1). Il est possible de grer localement des sous-rseaux, par e e e e exemple le premier octet de ladresse machine correspond a ladresse de sous-rseau : 10 dsigne le ` e e rseau Ethernet du laboratoire, 11 est le rseau Appletalk des Macs, 12 est le rseau Ethernet de e e e lenseignement.

1.4.2

La table des rseaux (networks) e

Elle met en relation les noms symboliques de rseau et leurs adresses Internet. e linterface shell Il propose le chier /etc/networks et la commande ypcat networks. Chaque ligne correspond a un rseau et contient de gauche a droite le nom symbolique ociel, la partie ` e ` rseau de ladresse Internet (au plus 3 champs dcimaux spars par des points), puis une liste e e e e eventuellement vide des noms alias. linterface programme Il propose les primitives de GETNETENT(3N) qui utilisent la structure :

1.4. INTERNET ET UNIX BSD 4.X (I.E. LES SUNS) struct netent { char *n name ; /* official name of net */ char **n aliases ; /* alias list, terminee par zero */ int n addrtype ; /* net number type i.e. AF INET */ long n net ; /* net number en format interne et cadre */ /* dans les octets de poids faibles */ }; Les primitives sont : #include <netdb.h> struct netent *getnetent (void), *getnetbyname (char *name) ;

qui renvoient un pointeur sur une structure statique correspondant a une ligne de la table des ` rseaux. e Exemple tir de ypcat networks e arpanet ucb-ether loopback reunir lilnet irisanet 10 arpa 46 ucbether 127 128.201 134.206 localnet 131.254

Moralit : les stations du M3 sont bien des sites du rseau lilnet ! e e

1.4.3

La table des services (/etc/services)

Elle est prsente dans le chapitre suivant. e e

1.4.4

Manipulation des adresses Internet

La manipulation des adresses Internet est facilite par les macros suivantes dnies dans <netinet/in.h> : e e

CHAPTER 1. LE CONCEPT DINTERNET /* Definitions of bits in internet address integers. */ #define #define #define #define #define #define #define #define #define #define #define #define IN IN IN IN IN IN IN IN IN IN IN IN CLASSA(i) (((long)(i) & 0x80000000) == 0) CLASSA NET 0xff000000 CLASSA NSHIFT 24 CLASSA HOST 0x00ffffff CLASSB(i) (((long)(i) & 0xc0000000) == 0x80000000) CLASSB NET 0xffff0000 CLASSB NSHIFT 16 CLASSB HOST 0x0000ffff CLASSC(i) (((long)(i) & 0xe0000000) == 0xc0000000) CLASSC NET 0xffffff00 CLASSC NSHIFT 8 CLASSC HOST 0x000000ff

1.4.5

Conversions machine/rseau e

Internet dnit un ordre rseau standard des octets constituant les entiers 32 et 16 bits dune e e adresse Internet ou dun numro de port. Cet ordre nest pas forcment identique a celui de la machine. e e ` Les primitives suivantes eectuent les conversions ncessaires et sont a utiliser avec gethostent et e ` getservent. #include <sys/types.h> #include <netinet/in.h> netlong = htonl(hostlong) ; u long netlong, hostlong ; netshort = htons(hostshort) ; u short netshort, hostshort ; hostlong = ntohl(netlong) ; u long hostlong, netlong ; hostshort = ntohs(netshort) ; u short hostshort, netshort ; Exercice : crire une commande lh (listhost) qui tant donn un nom de rseau ache la liste e e e e des machines de ce rseau (ide, consulter la table des rseaux puis celle des machines). e e e

Chapter 2 Adresses de la couche transport


On trouvera des informations utiles dans le manuel en ligne (man 4 avec intro, unix, inet, ip, tcp, udp) ainsi que dans les chiers dinclusions mentionns. e Un point dacc`s (SAP) de la couche transport est ce qui permet a une application dutiliser le e ` rseau pour envoyer ou recevoir des donnes. e e Pour que deux applications puissent changer des donnes via la couche transport, chacune delle e e doit avoir acc`s a un SAP et tre en mesure dadresser le SAP de lautre. e ` e Le format dune adresse de SAP dpend de la famille de protocole utilise. Par exemple, pour les e e communications intra-Unix, une adresse de SAP est implante comme une entre dans le syst`me de e e e chiers, par contre, dans Internet, une adresse de SAP est un couple (adresse Internet, numro de e port). Un numro de port est un entier qui identie un SAP sur une machine pour un protocole partie culier : udp et tcp ont chacun leur propre espace de numros de port. e Le format dadresse est orthogonal a lAPI (Application Programming Interface) utilise pour ` e accder a la couche transport. Par exemple, les adresses de SAP Internet sont implantes par la e ` e mme structure C, quon utilise lAPI des sockets (Unix BSD) ou bien lAPI tli (Unix Syst`me V). e e Un point de transport (socket ou tli) nest accessible de lextrieur que si on lui a associ une e e adresse explicitement (bind() pour lAPI socket) ou implicitement (dans le domaine internet et pour lAPI socket, cette association est faite automatiquement lors dun connect(), dun send(), ...).

2.1

Format gnral des adresses e e

Le format gnral des adresses est dcrit par la structure sockaddr 1 qui a pour seul rle de e e e o permettre lcriture des prototypes des primitives ayant une adresse en param`tre : e e struct sockaddr { short sa family ; /* famille dadresse (AF INET, AF UNIX,...) */ char sa data [14] ; /* contient ladresse effective */ };
1 Le nom sockaddr semble indiquer quil sagit dadresse de socket. Il nen est rien puisque ces mme formats e dadresse sont aussi utilisables pour lAPI tli. Cette ambigu e est probablement due a des raisons historiques. t `

CHAPTER 2. ADRESSES DE LA COUCHE TRANSPORT

En fait, chaque famille de protocoles poss`de son propre format dadresse. Le premier champs de e la structure sockaddr permet justement de distinguer entre ces dirents formats : e

AF INET pour la famille Internet, AF UNIX pour la famille Unix.

2.2

Format des adresses du domaine Unix

Dans le domaine Unix, une adresse est une entre2 dans le syst`me de chiers cre lors de e e ee lexcution du bind(). Par exemple, le chemin /users/graphix/durif/ma_socket. e Dans <sys/un.h> on trouve le format de ladresse Unix struct sockaddr un { short sun family ; /* AF UNIX ou AF UNSPEC pour casser */ /* une pseudo-connexion datagram */ char sun path [108] ; /* chemin */ };

2.3

Format des adresses du domaine Internet

Dans le domaine Internet, une adresse est un couple form de ladresse Internet de la machine, et e du numro de port. Un numro de port est un identiant unique dans le syst`me et pour un protocole e e e donn (un mme numro de port peut donc dsigner une socket TCP et une socket UDP, ceci est le e e e e cas des services prdnis qui fonctionnent dans les deux protocoles comme sunrpc). e e Dans <netinet/in.h> on trouve le format des adresses Internet.

le type dune entre socket est s, celui des rpertoires est d, celui des chiers est -, ... e e

2.4. GESTION DES NUMEROS DE PORT /* Internet address */ struct in addr { union { struct { u char s b1,s b2,s b3,s b4 ; } S un b ; struct { u short s w1,s w2 ; } S un w ; u long S addr ; } S un ; #define s addr S un.S addr /* should be used for all code */ }; struct sockaddr in { short sin family ; /* AF INET ou AF UNSPEC pour casser */ /* une pseudo-connexion datagram */ u short sin port ; /* port de la socket en format reseau */ struct in addr sin addr ; /* adresse internet de la machine en */ /* format reseau (sin addr.s addr est u long) */ char sin zero [8] ; };

2.4

Gestion des numros de port e

La famille de protocole Internet dnit un certain nombre de protocoles (ou applications) stane dards comme ftp, telnet et dautres. Dautres protocoles (comme sunrpc) ne font pas partie de la famille Internet bien qutant largement diuss. e e Chacun de ces protocoles utilise un (ou plusieurs) SAP qui doivent tre adressables. Pour cela, e les SAP de ces protocoles sont aects a des numros de port bien connus qui doivent tre identiques e ` e e quel que soit le site qui les implmente. e La table des services rpertorie lensemble de ces protocoles et les numros de port qui leur ont e e ociellement t allous. ee e

2.4.1

La table des services (/etc/services)

Elle met en relation les noms des services ociels (well-known) avec les couples (numros de port, e protocole de transport) (principalement UDP et TCP). Les services sont soit spciques Unix comme e rlogin, rsh, rcp, soit simplement Internet comme ftp, telnet ... linterface shell Il propose le chier /etc/services ou ypcat services. Sur chaque ligne, on trouve le nom symbolique de service, son port ociel et le protocole dans lequel il est disponible. linterface programme Il propose les primitives de GETSERVENT(3N), la structure utilise est e la suivante

10

CHAPTER 2. ADRESSES DE LA COUCHE TRANSPORT struct servent { char *s name ; /* official name of service */ char **s aliases ; /* alias list terminee par NULL */ int s port ; /* port service resides at : format reseau */ char *s proto ; /* protocol to use "udp", "tcp" */ }; les primitives sont #include <netdb.h> struct servent *getservent (void), *getservbyname (char *i name, char *i proto) ;

Elles sont similaires a celles de GETHOSTENT mais pour le chier /etc/services. ` Si i_proto est NULL il nest pas pris en compte dans la recherche, sinon cest le nom du protocole souhait (udp, tcp, ...). e Attention : le pointeur renvoy rep`re une zone statique dont le contenu est cras a chaque appel. e e e e` Les numros de port (32 bits) sont fournis dans le format du rseau (cf ntoh et hton), pas dans e e celui de la machine. Exemple tir de ypcat services e ftp telnet sunrpc sunrpc 21/tcp 23/tcp 111/udp 111/tcp

ftp (File Transfer Protocol) est disponible sur le port 21 uniquement avec le protocole TCP. sunrpc (Remote Procedure Call) est disponible sur le port 111 dans les deux protocoles UDP et TCP : chaque protocole poss`de son propre ensemble de numros de port. e e

2.5

Fabriquer des adresses Internet

La fabrication dadresse Internet dpend du contexte : e soit on veut associer une adresse a un SAP local pour le rendre accessible de lextrieur, ` e soit on veut fabriquer ladresse dun SAP loign pour dialoguer avec une application distante. e e Dans ces deux cas il faut garnir les champs de la structure sockaddr_in, cest a dire le numro ` e de port sin_port et ladresse Internet sin_addr. Voici un tableau rsumant la mani`re de garnir le numro de port sin_port. e e e

2.5. FABRIQUER DES ADRESSES INTERNET localisation du SAP local loign e e ociel 21 getservbyname ("ftp") ocieux statique constante > 5000 dynamique 0 serveur de nom

11

nature du service

Dans le cas ociel, on utilisera de prfrence le nom symbolique du service ("ftp" dans le tableau). ee Dans le cas local et ocieux, on utilisera de prfrence lallocation dynamique du numro de port ee e par bind() en donnant 0 dans sin_port ce qui a lavantage dviter tout conit. Linconvnient est e e quil faudra publier ce numro. e Voici un tableau rsumant la mani`re de garnir ladresse internet sin_addr. e e localisation du SAP local loign e e gethostname (moi) gethostbyname(moi) gethostbyname(nom serveur) INADDR ANY Il est videmment prfrable dinterroger symboliquement la table des hosts pour obtenir ladresse e ee Internet dune machine. Dans le cas o` un serveur doit tre accessible par toutes ses interfaces rseau, u e e qui est probablement le plus frquent, on donne la constante INADDR_ANY comme adresse machine. e Voici un utilitaire pour fabriquer une adresse du domaine Internet. void makeAddress (struct sockaddr_in *o_pa, const char *host, int port) { struct hostent *h ; bzero ((char *) o_pa, sizeof (struct sockaddr_in)) ; o_pa -> sin_family = AF_INET ; o_pa -> sin_port = htons (port) ; if ((h = gethostbyname (host)) == 0) { fprintf (stderr, "%s: machine inconnue\n", host) ; exit (1) ; } ; bcopy ((char *) h->h_addr, (char *) &o_pa->sin_addr, h->h_length) ; } Le param`tre port est suppos tre en format machine. Remarquez lutilisation de la macro htons() e ee qui traduit la reprsentation machine de port en reprsentation rseau (Host TO Network Short). e e e Voici un utilitaire pour fabriquer ladresse dun SAP bien connu du domaine Internet. void wellKnwonAddress (struct sockaddr_in *o_pa, const char *host, const char *serv, const char *proto) { struct servtent *s ; if ((s = getservbyname (serv, proto)) == 0) { fprintf (stderr, "%s/%s: service inconnu\n", serv, proto) ;

12 exit (1) ;

CHAPTER 2. ADRESSES DE LA COUCHE TRANSPORT

} ; makeAddress (o_pa, host, ntohs (s->s_port)) ; } Exercice Ecrire un utilitaire similaire pour le domaine Unix.

2.5.1

Utilitaires BSTRING(3)
bcopy (const char *source, char *dest, int length) ; bzero (char *zone, int length) ;

Deux utilitaires pour recopier et mettre a zro des cha ` e nes doctets.

Chapter 3 Les sockets


On trouvera des informations utiles dans le manuel en ligne (man 4 avec intro, unix, inet, ip, tcp, udp) ainsi que dans les chiers dinclusions mentionns. e Les sockets (prise ou SAP) sont lincarnation Unix BSD 4.2 des couches rseau et transport e de lOSI. Elles autorisent lutilisation de direntes familles de protocoles et en particulier ceux e de lInternet. Elles permettent une communication inter-processus bidirectionnelle soit sur une mme machine soit a travers le rseau. e ` e Toute communication met en jeu deux sockets qui doivent possder les mmes caractristiques, e e e en particulier elle doivent utiliser la mme famille de protocole et le mme protocole. e e Un processus dsigne par un descripteur (un simple emtier) la socket locale quil a lui-mme e e cre ou dont il a hrit lors dun fork(). En revanche, pour dsigner une socket loigne, il doit ee e e e e e spcier son adresse. e

3.1

Caractristiques dune socket e

Les trois caractristiques dune socket sont son domaine, son type et son protocole. Des sockets e destines a communiquer doivent possder les mmes caractristiques. e ` e e e

3.1.1

Domaines de communication dune socket

Une socket peut tre destine a communiquer avec des sockets qui sont sur la mme machine ou e e ` e sur dautres machines. Domaine Unix (PF UNIX) la socket ne peut communiquer quavec une socket situe sur la mme e e machine, on ne passe pas par le rseau (tout se passe en mmoire centrale). e e Domaine Internet (PF INET) la socket peut communiquer sur la mme machine ou sur dautres e machines par lintermdiaire du rseau. e e A chaque domaine correspond, dune part, un format dadresse (voir chapitre prcdent) et, e e dautre part, une famille de protocoles, do` le nom des constantes (PF comme Protocol Family). En u particulier le domaine PF_INET correspond a la suite des protocoles de lInternet (i.e. TCP/IP). `

3.1.2

Types de communication dune socket

Le type de communication xe les caractristiques logiques de la transmission. e 13

14

CHAPTER 3. LES SOCKETS

datagram (SOCK DGRAM) il ny a pas de circuit virtuel entre les deux sockets et chaque paquet est envoy indpendamment des autres, cest a dire non able, non squenc mais prservation e e ` e e e des fronti`res de paquet. e circuit virtuel (SOCK STREAM) il y a dabord tablissement dune connexion xe, la commue nication fonctionne ensuite comme un pipe Unix, cest a dire able, la squence est prserve, ` e e e pas de doublons mais il ny a pas prservation des fronti`res denregistrement (par exemple, le e e processus metteur peut envoyer 2 octets par 2 octets, et le rcepteur peut lire 3 par 3). e e e e` raw (SOCK RAW) utilise directement le protocole IP (rserv a root). sequenced paquet (SOCK SEQPACKET) cumule les avantages des circuits virtuels et la prservation e des fronti`res. e Le type de communication permet de savoir quels protocoles du domaine sont utilisables pour implanter la communication dsire. e e

3.1.3

Protocole dune socket

Le protocole permet de choisir le protocole dans lensemble des protocoles dnis par le domaine e et le type de communication. Les dirents protocoles existant sont rpertoris dans la table des protocoles (voir GETPROe e e TOENT(3N)). En gnral il nexiste quun protocole par domaine et par type ; dautre part il existe un protocole e e par dfaut dtermin par le domaine et le type de la socket (voir table 3.1). e e e protocole par dfaut SOCK DGRAM SOCK STREAM e PF INET UDP TCP SOCK SEQPACKET ? SOCK RAW IP

Table 3.1: les protocoles par dfaut. e On remarque quil ny a pas de ligne pour le domaine PF UNIX, en eet, la notion de protocole na pas de sens pour les sockets du domaine Unix.

3.2

Primitives gnrales sur les sockets e e

Les primitives qui suivent sont disponibles a la fois pour les sockets SOCK DGRAM et ` e e e e SOCK STREAM. En gnral, leur smantique di`re suivant le type de la socket. En gnral, une fonction renvoie une valeur positive ou nulle en cas de succ`s, une valeur ngative e e e e en cas dchec, dans ce cas on utilise perror(char *) pour imprimer un message appropri. e e Sauf option particuli`re de socket, les primitives connect(), recv(), send(), read(), write() e et accept() sont bloquantes. Lusage des primitives dcrites ci-apr`s est illustr dans les deux chapitres qui suivent. e e e

3.2.1

Crer une socket : socket() e

Cest a la cration dune socket quon xe ses trois caractristiques. ` e e

3.2. PRIMITIVES GENERALES SUR LES SOCKETS #include <sys/types.h> #include <sys/socket.h> int socket (int domain, int type, int protocol) ;

15

cre une socket (i.e. alloue les ressources syst`mes ncessaires) et renvoie son descripteur, ses e e e caractristiques sont xes par les param`tres, en particulier on prcisera 0 pour protocol an e e e e dobtenir le protocole par dfaut. Exemple : e s = socket (PF_UNIX, SOCK_DGRAM, 0) ;

3.2.2

Dtruire une socket : close() e


close (int s) ;

ferme la socket dsigne par s et restitue les ressources associes au syst`me. On voit ici que la e e e e socket se comporte comme un simple descripteur de chier ; ceci sera vrai dans certains cas pour dautres primitives et permettra de confondre la notion de socket et celle de chier.

3.2.3

Rduire les fonctionnalits dune socket : shutdown() e e


shutdown (int s, int how) ;

Rduit les fonctionnalits de s suivant la valeur de how. e e how fonction 0 criture seule e 1 lecture seule 2 plus rien

3.2.4

Associer une adresse ` une socket : bind() a


#include <sys/types.h> #include <sys/socket.h> bind (int s, struct sockaddr *i saddr, int i saddrlen) ;

Associe ladresse i_saddr a la socket s. i_saddrlen est la taille de la structure *i_saddr eec` tivement passe. Contre toute apparence, tous les param`tres sont en entre 1 . e e e

3.2.5

Consulter ladresse dune socket : getsockname()


getsockname (int s, struct sockaddr *o saddr, int *io saddrlen) ;

qui range dans o_addr ladresse associe a s. e `


1 en cas dambigu e possible, les noms de param`tres formels sont prcds de i, io ou o suivant quils sont de mode t e e e e in, in out ou out. Par exemple pour la primitive socket il ny a pas dambigu e : tous ses param`tres sont en entre t e e puisque le C ne poss`de que le passage par valeur. Une ambigu e appara lorsquun param`tre est ladresse dune e t t e variable.

16

CHAPTER 3. LES SOCKETS

3.2.6

Exemples dutilisations de bind()

La fabrication dadresse du domaine Unix ne pose aucun probl`me et nest pas illustre. e e Le code suivant pourrait tre le dbut du dmon telnet ; le bind() na aucune initiative a prendre, e e e ` ladresse etant totalement spcie. e e {int s ; struct sockaddr_in snom ; s = socket (PF_INET, SOCK_STREAM, 0) ; { char hostname [MAXHOSTNAMELEN + 1] ; struct servent *serv = getservbyname ("telnet", "tcp") ; gethostname (hostname, MAXHOSTNAMELEN) ; /* nom machine locale */ makeAddress (&snom, hostname, ntohs (serv->s_port)) ; } bind (s, (struct sockaddr *) &snom, sizeof snom) ; ... Dans ce second exemple, cest bind() qui alloue un numro de port. Il faut ensuite le publier e avec printf(). {int s ; struct sockaddr_in snom ; int snom_taille = sizeof snom ; s = socket (PF_INET, SOCK_STREAM, 0) ; { char hostname [MAXHOSTNAMELEN + 1] ; gethostname (hostname, MAXHOSTNAMELEN) ; /* nom machine locale */ makeAddress (&snom, hostname, 0) ; } bind (s, (struct sockaddr *) &snom, sizeof snom) ; getsockname (s, (struct sockaddr *) &snom, &snom_taille) ; printf ("port alloue : %d\n", ntohs (snom.sin_port)) ; ... Linconvnient majeur de cette technique est que le numro de port publi est dirent a chaque e e e e ` excution, il faut alors donner le bon numro aux futurs clients qui voudront se connecter a cette e e ` adresse. Lintroduction dun serveur de noms qui g`re une table de correspondances nom symbolique, e numro de port, permet de supprimer ce probl`me : les dirents intervenants de lapplication nont e e e plus qu` conna le nom symbolique invariable du port avec lequel ils veulent communiquer. a tre La carte hosts du NIS est un exemple de serveur de nom ; malheureusement, si vous ntes pas e super utilisateur (root) vous ne pouvez pas ajouter de nouveaux services dans /etc/services. Dans ce cas, on peut programmer soit mme son propre serveur de numro de port. Supposons e e que ce serveur existe, appelons le snp (comme Serveur de Numro de Port). Voici une partie de e linterface quil propose : /* interface destine aux clients */

3.2. PRIMITIVES GENERALES SUR LES SOCKETS unsigned int snp_consulter (char *nom, char *host) ; /* renvoie le numero de port correspondant a nom sur host */ /* interface destine aux serveurs */ void snp_enregistrer (char *nom, unsigned int port) ; /* enregistre la correspondance (nom.host_local, port) dans la table */ void snp_supprimer (char *nom) ; /* supprime la correspondance (nom.host_local, port) de la table */

17

Lorsquun serveur alloue un numro de port dynamiquement, il le publie aupr`s de snp en lui associant e e un nom symbolique. snp enregistre ce couple (nom symbolique, numro de port) dans sa table. e Lorsquun client dsire contacter un serveur dont il conna le nom symbolique, il interroge snp pour e t rcuprer le numro de port correspondant. On se servira de ces primitives dans la suite. e e e

3.2.7

Connexion de socket : connect()

La connexion permet dindiquer avec quel partenaire unique (socket loigne) les changes ultrieurs e e e e auront lieu ; les primitives send() et write() pourront d`s lors tre utilises et cest le partenaire e e e qui en sera la cible. #include <sys/types.h> #include <sys/socket.h> connect (int s, struct sockaddr *i peer, int peerlen) ; s est la socket locale, i_peer et peerlen forment ladresse dune socket loigne. e e Si s est de type SOCK STREAM la connexion est obligatoire pour quune communication puisse avoir lieu. Le processus qui excute ce connect() est considr comme un client de la socket loigne e ee e e qui appartient au processus serveur. La demande de connexion dun client naboutit que quand le serveur lhonore avec la primitive accept(), ceci correspond tr`s prcisment a ltablissement dun e e e ` e circuit virtuel. Une fois cr, ce circuit ne peut tre modi, la seule possibilit est de dtruire la ee e e e e socket ainsi connecte (close()). e Si s est de type SOCK DGRAM il ne sagit pas dune connexion mais dune facilit qui permet e dallger les communications ultrieures ; les envois se feront sur i_peer et les rceptions seulement e e e depuis i_peer ; s peut par la suite tre connecte avec un autre partenaire par un nouvel appel a e e ` connect(), ou bien, simplement dconnecte si on spcie AF UNSPEC dans i_peer->sa_family. e e e

3.2.8

Rception de message : recv() e


#include <sys/types.h> #include <sys/socket.h> int recv (int s, char *o buf, int lbuf, int flags) ; int recvfrom (int s, char *o buf, int lbuf, int flags, struct sockaddr *o from, int *io fromlen) ;

18

CHAPTER 3. LES SOCKETS

Ces primitives reoivent sur s un message dune autre socket. recv() ne peut fonctionner que si c s est connecte (connect()). Le message est stock dans le tampon o_buf de taille lbuf, la longueur e e eective du message est renvoye par la primitive. e flags est 0 ou un ou bits a bits dune ou plusieurs des valeurs suivantes ` MSG OOB qui spcie quun message urgent doit tre reu (out-of-band data), seules les sockets e e c SOCK STREAM dans INERNET supportent cette option, MSG PEEK qui spcie une rception non destructive. e e Si o_from nest pas le pointeur NULL, o_from et io_fromlen permettent de rcuprer ladresse e e de lmetteur. e

3.2.9

Emission de message : send()


#include <sys/types.h> #include <sys/socket.h> int send (int s, char *i msg, int lmsg, int flags) ; int sendto (int s, char *i msg, int lmsg, int flags, struct sockaddr *i to, int tolen) ;

Ces primitives envoient le message i_msg, lmsg sur la socket s. Pour send(), s doit tre connecte e e (connect()). i_to et tolen reprsentent ladresse de la socket cible. e flags est 0 ou vaut MSG OOB : le message est urgent (out-of-band), cette derni`re possibilit e e nexiste que sur les sockets SOCK STREAM et PF INET.

3.2.10

Lecture et criture : read(), write() e

Le descripteur dun ordre read() ou write() peut tre une socket a condition quelle soit e ` connecte. e Attention, read() renvoie le nombre doctets lus qui peut tre infrieur au nombre doctets quon e e a demand a lire, pour mener la lecture a son terme il est alors sens de mettre le read() dans une e` ` e boucle qui sarrte d`s quon a lu le nombre doctets souhait. e e e

3.2.11

Attente slective : select() e

Cette primitive est gnrale aux descripteurs dentre sortie dUnix : descripteurs de chier (obe e e tenus par open()), descripteurs de tube (obtenus par pipe()) et descripteurs de socket (obtenus par socket() et accept()). Cette primitive est particuli`rement utile lorsquon a plusieurs descripteurs ouverts et quon ne e sait pas a priori sur lequel une lecture ou une criture sera possible (on ne veut pas xer un ordre a e priori sur les ordres de lecture et/ou dcriture). e Le select() et les macros associes permettent de conna e tre les descripteurs sur lesquels une opration dentre/sortie non bloquante est possible. Cette primitive est bloquante avec la possibilit e e e de xer un dlai de garde (timeout). e

3.2. PRIMITIVES GENERALES SUR LES SOCKETS #include <sys/types.h> #include <sys/time.h> int select (int width, fd set *io readfds, fd set *io writefds, fd set *io exceptfds, struct timeval *io timeout) ; FD SET(fd,&fdset) /*positionne le descripteur fd dans le masque fdset*/ FD CLR (fd, &fdset) /* raz du descripteur fd dans le masque fdset */ FD ISSET (fd, &fdset) /* test du descripteur fd dans le masque fdset */ FD ZERO (&fdset) /* raz du masque fdset */ int fd ; fd set fdset ;

19

Un masque de type fd_set reprsente un ensemble de descripteurs, les oprations sur un tel e e ensemble sont lajout et la suppression dun descripteur (macros FD SET et FD CLR), le test dappartenance (FD ISSET) et linitialisation a vide de lensemble (macro FD ZERO). ` En entre, le masque io_readfds (resp. io_writefds et io_exceptfds), contient les descripe teurs pour lesquels select() doit tester la possibilit dune lecture non bloquante (resp. criture et e e vnement exceptionnel). e e En sortie, le mme masque io_readfds (resp. io_writefds et io_exceptfds), contient les dese cripteurs pour lesquels une lecture non bloquante est possible (resp. criture et vnement excepe e e tionnel). Un des trois masques peut tre le pointeur NULL sil est sans intrt. e ee width indique le nombre de bits signicatifs de chaque masque, sa valeur est donne par getdtae blesize(2). io_timeout donne un dlai de garde au select(), si cest le pointeur NULL, lattente nest pas e limite dans le temps. e select() renvoie le nombre total de descripteurs prts, en particulier 0 si le dlai de garde e e (io_timeout) a expir. select() renvoie -1 en cas derreur, en particulier sil a t interrompu par e ee un signal. Dans lexemple suivant, on veut accder au descripteur (fd1 ou fd2) qui, le premier, dispose dune e donne a lire : e ` ... while (1) { fd_set lecture ; FD_ZERO (&lecture) ; FD_SET (fd1, &lecture) ; FD_SET (fd2, &lecture) ; if (select (getdtablesize (), &lecture, NULL, NULL, NULL) >= 0) { if (FD_ISSET (fd1, &lecture)) traiter_1 (fd1) ; else if (FD_ISSET (fd2, &lecture)) traiter_2 (fd2) ; else /* theoriquement impossible puisque timeout infini */

20

CHAPTER 3. LES SOCKETS } else if (errno == EINTR) { /* select a ete interrompu par un signal : reprendre la boucle */ } } Exercice : On remarque que fd1 est favoris par rapport a fd2, comment viter ce favoritisme ? e ` e

3.3

Notion de client et de serveur

Conceptuellement, un client est une entit active qui prend des initiatives de son propre chef, e par exemple il demande a un serveur de lui rendre un service ; en revanche, un serveur est une entit ` e passive qui attend quon lui demande un service. Techniquement, le serveur est le processus qui eectue un bind(), le client est celui qui excute un e connect(). Un serveur est souvent implant comme un processus bouclant indniment et pouvant e e rpondre a de multiples clients. Un client conna ncessairement le serveur quil dsire utiliser, alors e ` t e e quun serveur ne peut prvoir a priori a quels clients il aura a faire. e ` ` En principe, le processus serveur doit tre lanc avant quun client ne lui fasse une demande. e e Un processus peut tre a la fois serveur et client. e ` Voici un module utilInet qui permet la cration de sockets destines a des clients ou des serveurs e e ` dans le domaine Internet : tout dabord utilInet.h #include #include #include #include #include #include <stdio.h> <sys/types.h> <sys/socket.h> <netdb.h> <sys/un.h> <netinet/in.h>

typedef int SOCKETI ; /* socket Internet */ SOCKETI mkClient (int type, char *rhost, char *nom) ; SOCKETI mkServeur (int type, char *nom) ; void delServeur (SOCKETI s, char *nom) ; Limplmentation du module est dans utilInet.c : e #include "utilInet.h" #include "Iaddress.h" #include "snp.h" static void test (int diag, char *mess) { if (diag < 0) { perror (mess) ; exit (1) ; } } static void rendrePublicInet (SOCKETI s, char *nom) { struct sockaddr_in nom_public ; int lg = sizeof nom_public ;

3.3. NOTION DE CLIENT ET DE SERVEUR { char hostname [MAXHOSTNAMELEN + 1] ; gethostname (hostname, MAXHOSTNAMELEN) ; /* nom machine locale */ makeAddress (&nom_public, hostname, 0) ; } test (bind (s, (struct sockaddr *) & nom_public, sizeof nom_public), "rendrePublicInet:bind") ; /* publier le port alloue par bind() */ test (getsockname (s, (struct sockaddr *) &nom_public, &lg), "rendrePublicInet:getsockname") ; snp_enregistrer (nom, nom_public.sinport) ; /* ou, si pas de snp : * printf ("Numero de port alloue: %d\n", ntohs (nom_public.sin_port)) ; */ } static void connecterInet (SOCKETI s, char *rhost, char *nom) { struct sockaddr_in nom_public ; struct hostent *he ; /* creation du nom public de la socket partenaire */ makeAddress (&nom_public, rhost, ntohs (snp_consulter (nom, rhost))) ; test (connect (s,(struct sockaddr*) &nom_public, sizeof nom_public), "connecterInet:connect") ; } SOCKETI mkClient { SOCKETI s ; (int type, char *rhost, char *nom)

21

tester (s = socket (PF_INET, type, 0), "socket()") ; connecterInet (s, rhost, nom) ; return s ; } SOCKETI mkServeur (int type, char *nom) { SOCKETI s ; tester (s = socket (PF_INET, type, 0), "socket()") ; rendrePublicInet (s, nom) ; return s ; }

22

CHAPTER 3. LES SOCKETS

void delServeur (SOCKETI s, char *nom) { snp_supprimer (nom) ; close (s) ; }

3.4

Autres aspects des sockets

fermeture en douceur On peut demander a ce que le close dune socket soit retard (linger) jusqu` ` e a ce que tous les messages en mission aient eectivement t envoys (ou bien jusqu` ce quun e ee e a dlai de garde soit atteint). e struct linger { int l_onoff; /* Linger active */ int l_linger; /* How long to linger for (seconds) */ }; struct linger ling = {1, 10} ; setsockopt (s, SOL_SOCKET, SO_LINGER, &ling, sizeof ling) ; Le verbre anglais linger signie sattarder. donnes urgentes (OOB ou Out Of Band) Possibles avec le send() et le recv() ; avec le ag e ` e MSG_OOB et seulement dans PF INET et en SOCK STREAM (cest a dire avec tcp). Ce mcanisme permet de faire raliser au rcepteur des traitements asynchrones par rapport au ux squentiel e e e des donnes transmises. Le rcepteur est averti de larrive dune donn urgente (en fait un e e e e seul caract`re) par le signal SIGURG. Pour recevoir ce signal le rcepteur doit armer le signal e e (signal()) et demander a la socket de le dclencher lors de larrive dune donne urgente. ` e e e Voici le code de lmetteur : e send (sock, "%", 1, MSG_OOB) ; et le code du rcepteur : e #include <signal.h> #include <unistd.h> #include <fcntl.h> static int socket_a_tester1, socket_a_tester2 ; static void handler_urgent (int sig) { fd_set evenements_urgents ; char buf ; /* rearmer le signal */ signal (sig, handler_urgent) ; FD_ZERO (&evenements_urgents) ; FD_SET (socket_a_tester1, &evenements_urgents) ;

3.4. AUTRES ASPECTS DES SOCKETS FD_SET (socket_a_tester2, &evenements_urgents) ; /* determiner la socket */ select (getdtablesize(), 0, 0, &evenements_urgents, 0) ; if (FD_ISSET (socket_a_tester1, &evenements_urgents)) { recv (socket_a_tester1, &buf, 1, MSG_OOB) ; ... } if (FD_ISSET (socket_a_tester2, &evenements_urgents)) { recv (socket_a_tester2, &buf, 1, MSG_OOB) ; ... } } void main() { /* armer le signal */ signal (SIGURG, handler_urgent) ; ... /* pour recevoir le SIGURG */ fcntl (socket_a_tester1, F_SETOWN, getpid()) ; fcntl (socket_a_tester2, F_SETOWN, getpid()) ; ... }

23

sockets non bloquantes il sagit dune proprit gnrale aux descripteurs dE/S, et donc dispoee e e nible sur les sockets. On lindique avec fcntl (s, F_SETFL, FNDELAY | fcntl (s, F_GETFL, 0)) ; les primitives read(), write(), recv(), send() et accept() ne sont plus bloquantes sur la socket s et renvoient une valeur particuli`re si lopration tait impossible. e e e diusion (broadcasting) La diusion nest possible quen communication datagramme (SOCK DGRAM) et si le processus appartient au super utilisateur, on rend une socket s diusante avec : { int on = 1 ; setsockopt (s, SOL_SOCKET, SO_BROADCAST, &on, sizeof on) ;

24

CHAPTER 3. LES SOCKETS

Chapter 4 Les sockets de type datagram (SOCK DGRAM)


En type datagram les primitives ne supportent pas la distinction client/serveur (alors quen circuit virtuel cette asymtrie est tout a fait explicite). En fait la distinction ventuelle entre client et serveur e ` e est purement du domaine de lapplication.

4.1

R`gles gnrales dutilisation des primitives e e e

Une socket peut tre dans un des trois tats suivant : e e anonyme cest son tat initial apr`s sa cration par socket(), e e e publique si elle est associe a une adresse mais nest pas connecte, e ` e connecte elle poss`de ncessairement une adresse et sa socket partenaire est connue du syst`me. e e e e Dans le domaine Internet exclusivement, les primitives connect() et sendto() ont pour eet de bord dassocier une adresse a la socket locale. ` Les r`gles dutilisations sont relativement intuitives : une socket peut recevoir si elle est publique e ou connecte ; elle peut mettre si elle est connecte ou bien si elle utilise sendto(). Voici un e e e diagramme de transition dcrivant lvolution de ltat dune socket. Une primitive est autorise e e e e dans un certain tat si le nouvel tat est dni. e e e bind() connect() sendto() recvfrom() recv() read() send() write() anonyme publique connecte e publique publique connecte e publique publique publique connecte e connecte e connecte e connecte e connecte e connecte e connecte e connecte e

Par exemple, le recv() nopre que sur une socket connecte et la socket ne change pas dtat et le e e e read() ne peut se faire que sur une socket publique ou connecte, toute chose logique. e Attention : dans le domaine Unix, le bind() est obligatoire pour recevoir. 25

26

CHAPTER 4. LES SOCKETS DE TYPE DATAGRAM (SOCK DGRAM)

4.2

Exemples dutilisation des sockets SOCK DGRAM

Pour arer les sources prsents ci-dessous, aucun traitement derreur sur le rsultat des primitives e e e e nest eectu. Il est cependant clair quun programme rel doit dtecter et traiter (perror()) toute e e e erreur due a lexcution dune primitive, ceci vite bien des ennuis en cas de bug. ` e e Les deux premiers exemples prsentent les sources dun client qui envoie un message a un serveur, e ` et du serveur correspondant qui reoit ce message et lenvoie sur sa sortie standard, dabord dans le c domaine Unix puis dans le domaine Internet. Le troisi`me exemple propose un service de question/rponse o` le client envoie une question e e u puis reoit la rponse ; le serveur reoit la question, la pose a loprateur puis renvoie la rponse de c e c ` e e celui-ci. Exemple 1 mission/ rception en datagramme dans le domaine Unix. e e Texte du processus metteur : 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 /* * emet.c */ #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <stdio.h> main (void) { int sock ; struct sockaddr_un nom_public ; char * buf = "un message vers le recepteur ....." ; sock = socket (PF_UNIX, SOCK_DGRAM, 0) ; /* creation du nom de la socket eloignee, puis connexion */ nom_public.sun_family = AF_UNIX ; strcpy (nom_public.sun_path, "socket") ; connect (sock, (struct sockaddr*) &nom_public, sizeof nom_public) ; /* communication : strlen (buf) + 1 pour envoyer le \0 terminateur */ write (sock, buf, strlen (buf) + 1) ; close (sock) ; }

On peut viter la connexion en remplaant les lignes 20 a 23 par : e c ` sendto (sock, buf, strlen (buf) + 1, 0, (struct sockaddr *)&nom_public, sizeof nom_public) ; Texte du processus rcepteur : e /* * recep.c */

4.2. EXEMPLES DUTILISATION DES SOCKETS SOCK DGRAM #include #include #include #include <sys/types.h> <sys/socket.h> <sys/un.h> <stdio.h>

27

main (void) { int sock ; struct sockaddr_un nom_public ; char buf [1024] ; sock = socket (PF_UNIX, SOCK_DGRAM, 0) /* creation du nom public de la socket, puis connexion */ nom_public.sun_family = AF_UNIX ; strcpy (nom_public.sun_path, "socket") ; bind (sock, (struct sockaddr*) &nom_public, sizeof nom_public) ; /* communication: on recupere evidemment le caractere \0 final */ read (sock, buf, 1024) ; printf ("le recepteur a recu: %s\n", buf) ; close (sock) ; unlink ("socket") ; } Le serveur dtruit sa socket avant de se terminer. e Exemple 2 mission/rception en datagramme dans le domaine Internet. e e Texte du processus metteur : e /* * emet.c: emet host */ #include "utilInet.h" main (int argc, char *argv[]) { SOCKETI sock ; char * buf = "un message vers le recepteur ....." ; if (argc != 2) { printf ("usage: emet host\n") ; exit (1) ; } sock = mkClient (SOCK_DGRAM, argv [1], "service"); send (sock, buf, strlen (buf) + 1, 0) ; close (sock) ;

28 }

CHAPTER 4. LES SOCKETS DE TYPE DATAGRAM (SOCK DGRAM)

Texte du processus rcepteur : e /* * recep.c */ #include "utilInet.h" main (void) { SOCKETI sock ; char buf [1024] ; sock = mkServeur (SOCK_DGRAM, "service") ; read (sock, buf, 1024) ; printf ("le recepteur a recu: %s\n", buf) ; delServeur (sock, "service") ; } Exemple 3 service de question/rponse dans le domaine Internet e Texte du client /* * qr.c: qr noport question * * !!! REMARQUE: le serveur doit tourner sur "homel" * */ #include "utilInet.h" #define HOTE_SERVEUR "homel" main (int argc, char *argv []) ; { SOCKETI sock ; char reponse [1024] ; if (argc != 3) { printf ("usage: qr question\n") ; exit (1) ; } sock = mkClient (SOCK_DGRAM, HOTE_SERVEUR, "qr"); send (sock, argv [2], strlen (argv [2]) + 1, 0) recv (sock, reponse, 1024, 0) < 0) ; printf ("La reponse: %s\n", reponse) ; close (sock) ; } Texte du serveur /*

4.3. COMMENT CONCEVOIR UN NOUVEAU SERVICE * qrserveur.c: qrserveur * * mode demploi: doit etre lance en interactif et sur "homel" */ #include "utilInet.h" #define HOTE_SERVEUR "homel" main (void) {SOCKETI sock ; sock = mkServeur (SOCK_DGRAM, "qr") ; while (1) { struct sockaddr_in client ; int lg = sizeof client ; char question [1024], reponse [1024] ; recvfrom (sock, question, 1024, 0, (struct sockaddr*) &client, &lg) ; printf ("Question: %s\nReponse? ", question) ; scanf (" %[^\n]", reponse) ; sendto(sock, reponse, strlen(s)+1, 0, (struct sockaddr*) &client, lg) ; } }

29

4.3

Comment concevoir un nouveau service

Les deux exemples prcdents sont particuli`rement simples, il ny quasimenent pas de protoe e e cole ! Considrons un exemple plus complexe. Le probl`me consiste a implanter un service de mini e e ` confrence : chaque machine peut possder ce service. Les commandes destines aux utilisateurs sont : e e e show host qui ache le dernier message stock par la machine host, e over host message qui crase le dernier message de la machine host avec message. e La rsolution dun tel probl`me peut passer par plusieurs tapes e e e 1. crire les prototypes des souches (stub) dacc`s au serveur ; les souches sont des sous-programmes e e dont le seul but est de faciliter lutilisation du serveur en cachant les sockets (on obtient ici conf.h). Ces souches sont donc destines exclusivement aux clients chaque souche correspond e a un service. ` 2. dduire des services attendus un protocole de communication avec le serveur (ici le rsultat est e e un chier confd.h), le chier .h obtenu est destin a la fois au serveur et aux fonctions souches e` (voir plus loin). 3. crire le code du serveur (ici confd.c), il sagit dobtenir un excutable qui implante les services. e e 4. crire les corps des souches (on obtient conf.c). e 5. crire le code des commandes clientes (ici show.c et over.c). e Voici ce que ca donne avec le probl`me de confrence : e e 1. les prototypes des souches sont dans conf.h

30

CHAPTER 4. LES SOCKETS DE TYPE DATAGRAM (SOCK DGRAM) /* * conf.h */ void conf_show (char *rhost, char *o_texte) ; void conf_over (char *rhost, char *texte) ; 2. le chier confd.h contient le protocole clients/serveur /* * confd.h */ #define LG_TEXTE 100 #define CONF_NAME "confd" enum COMMANDE { CONF_SHOW, /* commande */ CONF_OVER, /* commande */ CONF_OK, /* compte-rendu */ CONF_ERROR /* compte-rendu */ } ; /* lunique type de mesage transfere entre un client et le serveur */ struct conf_message { enum COMMANDE com ; char texte [LG_TEXTE] ; } ; /* protocole unique (inefficace mais simple): * 1) client >---- message (CONF_SHOW/CONF_OVER) ----> confd * 2) execution du service par confd * 3) cliend <--- message (CONF_OK/CONF_ERROR) -----< confd */ 3. le chier confd.c contient le code du serveur : /* * confd.c */ #include "confd.h" void main (void) { int s = mkServeur (SOCK_DGRAM, CONF_NAME) ; char texte [LG_TEXTE] ; while (1) { struct conf_message m ; struct sockaddr_in client ; int lg_client = sizeof client ; recvfrom (s, &m, sizeof m, 0, &client, &lg_client) ;

4.3. COMMENT CONCEVOIR UN NOUVEAU SERVICE switch (m.com) { case: CONF_OVER: strcpy (m.texte, texte) ; m.com = CONF_OK ; break ; case: CONF_SHOW: strcpy (texte, m.texte) ; m.com = CONF_OK ; break ; default: m.com = CONF_ERROR ; break ; } sendto (s, &m, sizeof m, 0, &client, sizeof client) ; } } 4. les corps des souches sont dans conf.c /* * conf.c */ #include "utilInet.h" #include "confd.h" #include "conf.h" void conf_show (char *rhost, char *o_texte) { struct conf_message m ; int s = mkClient (SOCK_DGRAM, rhost, CONF_NAME) ; m.com = CONF_SHOW ; write (s, &m, sizeof m) ; read (s, &m, sizeof m) ; strcpy (o_texte, m.texte) ; close (s) ; } void conf_over (char *rhost, char *texte) { struct conf_message m ; int s = mkClient (SOCK_DGRAM, rhost, CONF_NAME) ; m.com = CONF_OVER ; strcpy (m.texte, texte) ; write (s, &m, sizeof m) ; read (s, &m, sizeof m) ; close (s) ; } 5. enn, le code de la commande show. /* * show.c */ #include "conf.h"

31

32

CHAPTER 4. LES SOCKETS DE TYPE DATAGRAM (SOCK DGRAM)

void main (int argc, char *argv[]) { if (argc == 2) { char texte [2000] ; conf_show (argv [1], texte) ; printf ("%s\n", texte) ; } } On remarque que grce aux stubs on crit une commande client sans avoir a conna lexistence a e ` tre des sockets. Un certain nombre de fonctions de librairie utilisent le rseau sans le montrer a lextrieur, par e ` e exemple rnusers(host) qui renvoie le nombre dusagers connects sur la machine host, ou encore e callrpc(). Exercice : Notre serveur de nom snp du chapitre prcdent est bien utile ! implantez le en utilisant e e le mme type darchitecture. e

Chapter 5 Les sockets de type circuit virtuel (SOCK STREAM)


Avec les sockets SOCK DGRAM, les dirents partenaires dune communication sont sur un e pied dgalit, autrement dit, au niveau des primitives, rien ne permet de dire si un processus est e e un client ou un serveur. Dautre part, puisquil ny a pas a proprement parler de connexion entre ` les partenaires, un processus peut, avec la mme socket, dialoguer successivement avec plusieurs e partenaires. Avec les sockets SOCK STREAM, on distingue clairement les serveurs des clients : la relation nest pas symtrique. Dautre part, la liaison qui existe entre un client et son serveur est ge et ne e e peut tre modie, cest naturel puisquil sagit dune commutation de circuit. e e La connexion client serveur (cest a dire la mise en place du circuit virtuel entre les deux parte` naires) stablit de la mani`re suivante : e e ct serveur il faut o e 1. crer une socket qui va recevoir les demandes de connexion des futurs clients socket(), e 2. lui donner une adresse bind(), 3. indiquer qu` partir de maintenant elle attend des demandes de connexion listen(), a 4. honorer les demandes de connexion des clients avec la primitive accept() dont le rsultat e est une nouvelle socket qui est eectivement connecte a celle du client. e ` ct client il faut o e 1. crer la socket socket(), e 2. la connecter a celle du serveur connect(). ` Il faut noter une dirence importante du serveur stream par rapport au serveur datagramme : la e socket cre initialement sert exclusivement a recevoir les demandes de connexions, cest une autre ee ` socket, cre par accept(), qui va servir a la communication eective avec le client. Ceci permet a ee ` ` un serveur unique de servir plusieurs clients. Une fois la connexion tablie, il est possible de la spcialiser avec shutdown(), par exemple si e e on sait que les informations ne circulent que dans un seul sens. Le dialogue peut ensuite prendre place avec les primitives send(), recv(), read() et write(). Enn les deux partenaires dtruisent e la connexion avec close(). 33

34

CHAPTER 5. LES SOCKETS DE TYPE CIRCUIT VIRTUEL (SOCK STREAM)

5.1
5.1.1

Les primitives spciques aux serveurs en circuits vire tuels


listen()
listen (int s, int backlog) ;

Dclare s comme une socket de connexion. s doit tre de type SOCK STREAM ou e e SOCK SEQPACKET et possder une adresse (bind()), backlog indique le nombre maximum de e demandes de connexion en attente (limit par la constante SOMAXCONN dnie dans sys/socket.h). e e listen() doit tre excute avant de pouvoir excuter un accept() sur s. e e e e Le connect() dun client choue si la le dattente de la socket de connexion du serveur est e sature. e

5.1.2

accept()
#include <sys/types.h> #include <sys/socket.h> int accept (int s, struct sockaddr *o addr, int *io addrlen) ;

s est une socket SOCK STREAM ou SOCK SEQPACKET, qui poss`de un nom public (bind()) e et une le dattente de demandes de connexion (listen()). accept() extrait la premi`re demande de connexion de la le dattente de s, lidentit du dee e mandeur est rang dans o_addr, io_addrlen, une nouvelle socket est cre (rsultat de accept()) e ee e qui est mise en connexion avec le demandeur. Ensuite, s reste disponible pour accepter les autres demandes de connexion. Autant se faire tout de suite un petit utilitaire int accepter (int sock) { struct sockaddr_in *peer ; int lpeer = sizeof peer ; return accept (sock, (struct sockaddr *)& peer, &lpeer) ; } en eet lidentit du partenaire est rarement ncessaire. e e

5.2

Un premier exemple

Cet exemple reprend lexemple metteur/rcepteur dj` dvelopp en datagramme (sockets e e ea e e SOCK DGRAM) et illustre lutilisation du listen() et du accept() pour limplantation dun serveur. Texte du processus metteur (cest le client) : e /* * emet.c: emet host noport */

5.2. UN PREMIER EXEMPLE #include "utilInet.h" void envoyer (int s, char *buf, int lgbuf) /* lgbuf >= 1 */ { while (1) { int lgeff = send (s, buf, lgbuf, 0) ; if (lgeff == -1) { perror ("envoyer()") ; exit (1) ; } buf += lgeff ; lgbuf -= lgeff ; if (lgbuf == 0) break ; } } void main (int argc, char *argv []) ; { SOCKETI sock ; char * buf = "un message vers le recepteur ....." ; if (argc != 3) { printf ("usage: emet host noport\n") ; exit (1) ; } sock = mkClient (SOCK_STREAM, argv [1], atoi (argv [2])); envoyer (sock, buf, strlen (buf) + 1) ; close (sock) ; } Texte du processus rcepteur (cest le serveur) : e /* * recep.c */ #include "utilInet.h" void recevoir (int s, char *buf, int lgbuf) { while (1) { int lgeff = read (s, buf, lgbuf) ; if (lgeff == -1) { perror ("recevoir()") ; exit (1) ; } if (lgeff != 0 && buf [lgeff-1] == 0) break ; lgbuf -= lgeff ; buf += lgeff ; } }

35

36

CHAPTER 5. LES SOCKETS DE TYPE CIRCUIT VIRTUEL (SOCK STREAM) void main (void) { SOCKETI sockConn ; char buf [1024] ; sockConn = mkServeur (SOCK_STREAM) ; /* a partir dici notez la difference avec la version SOCK_DGRAM */ listen (sockConn, 1) ; { SOCKETI s = accepter (sockConn) ; recevoir (s, buf, 1024) ; printf ("le recepteur a recu: %s\n", buf) ; close (s) ; } close (sockConn) ; } Exercice : adaptez le service de question/rponse pour quil fonctionne en circuit virtuel. e

5.3

Utilisation des sockets SOCK STREAM

Gnralement, un serveur est un dmon qui tourne indniment. Un tel serveur peut servir e e e e plusieurs clients. On peut distinguer plusieurs types de serveurs suivant la mani`re dont les clients e sont servis. serveur squentiel : les clients sont satisfaits les uns a la suite des autres. La boucle dun tel serveur e ` a typiquement la forme suivante : while (1) { int s = accepter (sockConn) ; servir_client (s) ; close (s) ; } serveur parall`le : plusieurs clients client peuvent tre servis simultanment. Le serveur peut tre e e e e mono-processus (utilisation du select()) ou multi-processus (utilisation du fork()). multi-processus cest le plus simple a raliser et aussi le plus co teux, il a la forme suivante : ` e u void recupere (void) { while (wait (0) != -1) ; } void main (void) { ... signal (SIGCHLD, recupere) ; /* pour eviter les zombies */ while (1) { int s = accepter (sockConn) ; if (fork() == 0) {

5.3. UTILISATION DES SOCKETS SOCK STREAM close (sockConn) ; servir_client (s) ; exit (0) ; } close (s) ;

37

} mono-processus il utilise le select() pour determiner le client a servir. Il doit donc grer ` e lensemble des clients en cours de connexion (autrement dit lensemble des sockets connectes), e il a la forme suivante : void main (void) { fd_set clients ; ... FD_ZERO (&clients) while (1) { fd_set lecture = clients ; FD_SET (sockConn, &lecture) ; if (select (getdtablesize (), &lecture, NULL, NULL, NULL) > 0) { if (FD_ISSET (sockConn, &lecture)) { int nouveau_client = accepter (sockConn) ; FD_SET (nouveau_client, &clients) ; } else { honorer_la_demande_de (un_client_pret (&lecture)) ; } } } } static int un_client_pret (fd_set *masque) { unsigned fd = 0 ; while (! FD_ISSET (fd, masque)) fd++ ; return fd ; } On utilise deux ensembles de descripteurs : lecture qui est temporaire et ne sert que pour le select() et clients qui est permanent et permet de mmoriser lensemble des e clients connects. A nouveau, on remarque linquit de la fonction un_client_pret(). e e e Fonctionnellement, la principale dirence entre le mono et le multi-processus est la possibilit e e ou non de partager les donnes entre les clients. e Exercice : faire que le serveur parall`le mono-processus rponde de faon plus quitable a ses e e c e ` clients. Lavantage vident dun serveur parall`le est quun client nest pas bloqu par ceux qui le e e e prc`dent. e e Exercice : adaptez ces schmas de serveurs squentiel, parall`le multi-processus et parall`le monoe e e e processus au type de communication datagramme.

38

CHAPTER 5. LES SOCKETS DE TYPE CIRCUIT VIRTUEL (SOCK STREAM)

5.4

Autres exemples

Pour la conception des exemples suivants, on applique la mthode dcrite prcdemment pour les e e e e sockets datagramme : numration des services, choix du protocole, implantation du serveur et des e e sous-programmes souches.

5.4.1

Un serveur de piles parall`le et multi-processus e

Voici un serveur de piles dentiers (on lappelle piled), chaque client dispose de sa propre pile, en eet le serveur est parall`le multi-processus ; cet exemple illustre lutilisation conjointe du accept() e et du fork() et lutilisation bidirectionnelle des sockets. 1. piled.h, le chier dinterface du serveur /* * piled.h: protocole du serveur de piles dentiers */ #define NOM_PILE "piled" enum COMMANDE { EMPILER, /* DEPILER, /* DETRUIRE, /* PILEVIDE /* } ; #define TMESS sizeof 2. piled.c, le code du serveur /* * piled.c: le code du serveur */ #include "utilInet.h" #include "piled.h" #define TPILE 10 struct pile { int som ; int p [TPILE] ; } ; static void servir_un_client (SOCKETI sclient) ; { struct pile p ; p.som = -1 ; while (1) {enum COMMANDE c ; int vide, sortir = 0 ; read (sclient, (char *) &c, sizeof c) ; switch (c) {

un parametre, par de retour */ retour de lancien sommet */ pas de retour */ retour dun entier */ (int) /* un message est un simple entier ! */

5.4. AUTRES EXEMPLES case EMPILER: read (sclient, (char *) &p.p [++p.som], sizeof (int)) ; break ; case DEPILER: write (sclient, (char *) &p.p [p.som--], sizeof (int)) ; break ; case DETRUIRE: sortir = 1 ; break ; case PILEVIDE: vide = p.som == -1 ; write (sclient, (char *) &vide, sizeof vide) ; break ; default: break ; } if (sortir) break ; } close (sclient) ; } main (void) { SOCKETI sockConn ; sockConn = mkServeur (SOCK_STREAM, NOM_PILE) ; listen (sockConn, SOMAXCONN) ; while (1) { SOCKETI sclient = accepter (sockConn) ; if (fork () == 0) { close (sockConn) ; servir_un_client (sclient) ; exit (0) ; } close (sclient) ; } }

39

Exercice 1 : crire la partie souche du serveur (pile.h et pile.c), e Exercice 2 : adaptez ce serveur pour quil soit mono-processus, ainsi, les dirents clients partageront e une seule et mme pile (ceci peut-il avoir un sens pratique ? ?) e Exercice 3 : adaptez ce serveur pour le type de communication datagramme. Exercice : il est possible de rediriger lentre standard dune commande sur une socket avec e dup2(). Faites le pour utiliser la commande more pour acher le contenu dun chier distant avec une commande du genre plus host file.

40

CHAPTER 5. LES SOCKETS DE TYPE CIRCUIT VIRTUEL (SOCK STREAM)

Chapter 6 Le mcanisme XDR (eXternal Data e Representation)


On lappelle aussi mcanisme dempaquetage/dpaquetage, de marshalling ou tout simplement e e de transmission. Dans la suite, le terme transmission dsigne a la fois lmission et la rception. e ` e e Le but du mcanisme XDR est double : e permettre a deux machines ayant des reprsentations internes de donnes direntes de se ` e e e comprendre. En eet, que se passe-t-il si un Sun Sparc envoie un nombre ottant vers un Vax avec un bte send() ? Pour cela, le mcanisme XDR propose une reprsentation externe e e e standard des donnes. e permettre la transmission de structures de donnes volues (par exemple a base de pointeurs). e e e ` Les primitives du type de send() et recv() ne permettent de transmettre que des squences e doctets sans signication particuli`re. e Lacc`s au XDR se fait avec e #include <rpc/xdr.h>

6.1

Gnralits sur le mcanisme XDR e e e e

Lobjet central du mcanisme XDR est le ot XDR de type XDR. En fonctions des param`tres e e xs lors de sa cration, un ot XDR ne peut assurer quune fonctionnalit parmi les trois suivantes : e e e lmission, la rception ou la libration de mmoire. Cette derni`re fonctionnalit semble trange, e e e e e e e elle est d e au fait que, lors de la rception, un ot XDR peut tre amen a allouer dynamiquement u e e e` la zone de mmoire rceptrice ; il est alors ncessaire par la suite de librer cette zone. e e e e

6.2

Fonctions de gestion des ots XDR

Avant dtre utilisable, un ot XDR doit tre construit au dessus dun FILE * qui lui-mme est e e e construit au dessus dun descripteur (descripteur de chier, de socket, de mmoire partage, ...). e e

6.2.1

fdopen() et fflush()
FILE *fdopen (int fd, char *mode /* "r", "w" ou "r+" */) ; 41

Pour construire un FILE * au dessus dune socket on utilise la fonction :

42

CHAPTER 6. LE MECANISME XDR (EXTERNAL DATA REPRESENTATION)

o` fd est le descripteur de la socket. u Puisquun ot XDR utilise un FILE * qui poss`de un tampon local, il est parfois ncessaire de e e vider ce tampon explicitement an de sassurer quune donne a bien t envoye. Pour cela, on e ee e utilise la fonction int fflush (FILE *f) ; qui vide le tampon sur le descripteur associ. e

6.2.2

xdrstdio create()
void xdrstdio create (XDR *o xdrs, FILE *filep, enum xdr op op) ;

cre dans o_xdrs un ot XDR. Les futures oprations de lecture (rception) ou dcriture e e e e (mission) auront lieu sur le ot dentre sortie standard filep. Le param`tre op xe le fonctionnee e e ment du ot : XDR ENCODE le ot fonctionne en mission, il traduit la reprsentation interne des objets dans e e leur reprsentation standard externe, e XDR DECODE le ot fonctionne en rcption, il traduit la reprsentation externe standard des e e e objets dans leurs reprsentations interne. Il peut y avoir allocation dynamique de la zone de e mmoire destine a lobjet reu. APRES ALLOCATION, CETTE ZONE EST AUTOMAe e ` c TIQUEMENT MISE A ZERO, ceci est indispensable lorsque lobjet contient lui-mme des e pointeurs qui doivent eux aussi tre allous. e e e e e e e ` XDR FREE la fonction lib`re la mmoire prcdemment alloue lors de la a un objet, *pobjet est considr comme un param`tre donne/rsultat. Ceci permet de librer la mmoire alloue ee e e e e e e lors dun dcodage antrieur. e e Les constantes prcdentes sont dnies dans le chier rpc/xdr.h. e e e

6.2.3

xdr destroy()
void xdr destroy (XDR *xdrs) ;

vide le tampon du ot dentre sortie standard sous-jacent (fflush()) puis dtruit le ot XDR e e lui-mme. Attention, un fclose() reste ncessaire pour fermer le ot dentre sortie standard, ainsi e e e quun close() pour le descripteur.

6.3

Fonctions de transmission XDR

Ce sont les fonctions de transmission o` simplement fonctions XDR qui assurent lencou dage/dcodage dobjets sur un ots XDR. e Ces fonctions renvoient vrai si la transmission sest correctement droule et faux si une erreur e e sest produite. Certaines de ces fonctions ont un param`tre qui est lui-mme une fonction XDR, le type de ce e e param`tre est xdrproc_t qui est dni par : e e typedef bool t (*xdrproc t) (XDR *, void *) ;

6.3. FONCTIONS DE TRANSMISSION XDR

43

Puisquune unique fonction XDR sert a la fois pour lmission et la rception, lobjet quelle ` e e transmet doit toujours tre un pointeur : e bool t xdr TYPE OBJET (XDR *x, TYPE OBJET *po) ; Les fonctions de transmission se rpartissent en deux groupes : celles qui ne font jamais aucune e allocation dynamique pour lobjet a recevoir et celles qui en font ventuellement une. ` e

6.3.1

Fonctions de transmission sans allocation dynamique

Ces fonctions de transmissions ne font pas dallocation dynamique car elles sappliquent a des ` donnes de taille xe et connue par avance. e 6.3.1.1 xdr void() bool t xdr void(void) ; cette fonction indique labsence de param`tre : elle ne transmet rien. e 6.3.1.2 xdr int() bool t xdr int(XDR *xdrs, int *ip) ; une fonction de ce genre existe pour chacun des types de base de C (char, short, long, float, double, bool_t, u_int, ...). 6.3.1.3 xdr opaque() bool t xdr opaque (XDR *xdrs, char *cp, u int cnt) ; transmet telle quelle une donne opaque dsigne par cp de longueur cnt. Elle est utilise par e e e e un programme qui a besoin de stocker une information mais qui ne la consulte jamais lui-mme, par e exemple les handles de chiers NFS sont des informations opaques pour les clients NFS. 6.3.1.4 xdr union() #define NULL xdrproc t ((xdrproc t)0) struct xdr discrim { int value ; xdrproc t proc ; }; bool t xdr union(XDR *xdrs, int *dscmp, char *unp, struct xdr discrim *choices, bool t (*defaultarm) () /* may equal NULL */ );

44

CHAPTER 6. LE MECANISME XDR (EXTERNAL DATA REPRESENTATION)

pour traduire des unions avec discrimant, dscmp rep`re le discrimant, unp lunion, choices est e une table dont chaque entre contient une valeur de discrimant et le pointeur de fonction XDR e correspondant, la derni`re entre sert de sentinelle : son pointeur de fonction est NULL ; la fonction e e XDR defaultarm sera utilise si la valeur pointe par dscmp nest pas trouve dans choices. Par e e e exemple : enum type {ENTIER, FLOTTANT} ; struct exemple { enum type t ; union { int eval ; float fval ; } val ; } struct xdr_discrim choices [] = { {ENTIER, xdr_int }, {FLOTTANT, xdr_float }, {0, NULL_xdrproc_t} } ; bool_t xdr_exemple (XDR *xdrs, struct exemple *e) { return xdr_union (xdrs, &e->t, (char *) &e->val, choices, NULL) ; }

6.3.2

Fonctions de transmission avec allocation dynamique ventuelle e

Ces fonctions de transmissions sapplique a des donnes de taille variables (comme des listes ` e cha ees) ou non connues par avance. n Il est alors possible de leur demander, du ct rcepteur, dallouer dynamiquement les donnes oe e e reues. Cette allocation dynamique na lieu que si le pointeur sur la zone est nul ; la zone alloue est c e immdiatement mise a zro avant que son contenu ne soit reu (ce qui autorise la transmission de e ` e c donnes rcursives). e e 6.3.2.1 xdr string() typedef char *string ; bool t xdr string (XDR *xdrs, string *ps, u int maxsize) ; transmet une cha de caract`res C pas plus longue que maxsize. ne e 6.3.2.2 xdr wrapstring() typedef char *wrapstring ; bool t xdr wrapstring (XDR *xdrs, wrapstring *pws) ; comme xdr_string() mais sans limitation sur la taille de la cha ne.

6.3. FONCTIONS DE TRANSMISSION XDR 6.3.2.3 xdr array() typedef bool t (*xdrproc t)(XDR *, caddr t *) ; typedef elem array [] ; bool t xdr array(XDR *xdrs, array *pa, u int *effsize, u int maxsize, u int elemsize, xdrproc t xdr elem );

45

effsize indique le nombre eectif dlments du tableau *pa et doit tre infrieur a maxsize, ee e e ` xdr_elem est lXDR a appliquer a chacun des lments du tableau, la taille dun lment est ` ` ee ee elemsize. Remarquons que elemsize nest pas transmis puisque cest une information spcique e a la machine locale. `

6.3.2.4

xdr bytes() typedef char bytes [] ; bool t xdr bytes(XDR *xdrs, bytes *pb, u int *effsize, u int maxsize) ;

comme xdr_array() sauf que cette fois le type des lments est x : cest loctet (char), il nest ee e donc pas ncessaire de spcier la taille dun lment ni la fonction XDR correspondante. e e ee

6.3.2.5

xdr reference()

Pour transmettre un pointeur qui doit tre non nul lors de lmission. e e typedef objet *POINTEUR NON NULL ; bool t xdr reference(XDR *xdrs, POINTEUR NON NULL *pp, u int objetsize, xdrproc t xdr objet ); transmet, avec la fonction XDR xdr_objet, lobjet dsign par le pointeur *pp. ATTENTION, e e lors de lencodage, cette primitive ne reconna pas le pointeur NULL, cest a dire que la zone repre t ` ee par *pp sera eectivement transmise mme si elle est a ladresse 0. Lors du dcodage, *pp peut tre e ` e e NULL, dans ce cas, la primitive alloue dynamiquement lobjet, objetsize donne alors la taille de zone mmoire a allouer. e `

6.3.2.6

xdr pointer()

Pour transmettre des donnes rcursives : un pointeur nul est correctement interprt. e e ee

46

CHAPTER 6. LE MECANISME XDR (EXTERNAL DATA REPRESENTATION) typedef objet *POINTEUR ; bool t xdr pointer(XDR *xdrs, POINTEUR *pp, u int objetsize, xdrproc t xdr objet );

comme xdr_reference() sauf quelle traite correctement le cas o` *pp est NULL en mission, u e et peut donc servir a transmettre des structures rcursives. ` e

6.3.3

Libration de zone xdr free() e


typedef ... objet ; void xdr free (xdrproc t xdr objet, objet *po) ;

cette fonction applique lXDR xdr_objet passe en param`tre pour librer lobjet repr par le e e e ee pointeur *po ; ATTENTION, xdr_objet doit tre une fonction a deux param`tres XDR* et objet*. e ` e Par exemple on peut librer une cha de la mani`re suivante : e ne e char *s = NULL ; xdr_wrapstring (&xdrs, &s) ; /* reception avec allocation dans s */ ... xdr_free (xdr_wrapstring, &s) ; ... Exercice corrig : crire le source suppos de xdr_array(). e e e #define XDR(xdr_fun, objet) \ { \ if (! (* xdr_fun) (xdrs, (objet))) return FALSE ; \ } bool_t xdr_array_suppose (XDR *xdrs, char **ppo, u_int *sizep, u_int maxsize, u_int elsize, xdrproc_t elproc) { char *ptr ; unsigned cpt ; XDR (xdr_u_int, sizep) ; if (*sizep > maxsize) return FALSE ; if (xdrs->x_op == XDR_DECODE && *ppo == 0) { *ppo = calloc (*sizep, elsize) ; /* la zone est mise a 0 */ } for (ptr = *ppo, cpt = 0 ; cpt < *sizep ; ptr += elsize, cpt++) { XDR (elproc, ptr) ;

6.4. UN EXEMPLE COMPLET } if (xdrs->x_op == XDR_FREE) free (*ppo) ; return TRUE ; } Exercice : crire le code de xdr_pointer(). e bool_t xdr_pointer(XDR *xdrs, char **ppo, u_int osize, xdrproc_t xdr_o) { bool_t present ; switch (xdrs->x_op) { case XDR_ENCODE : present = *ppo != 0 ; XDR (xdr_bool, &present) ; if (present) XDR (xdr_o, *ppo) ; break ; case XDR_DECODE : XDR (xdr_bool, &present) ; if (present) { if (*ppo == 0) { *ppo = malloc (osize) ; bzero (*ppo, osize) ; } XDR (xdr_o, *ppo) ; } else *ppo = 0 ; break ; case XDR_FREE : if (*ppo != 0) { XDR (xdr_o, *ppo) ; free (*ppo) ; *ppo = 0 ; } break ; } return TRUE ; }

47

6.4

Un exemple complet
/* * exdr.c: emetteur avec XDR (usage : exdr rhost) */

Toujours le mme de service dimpression dun message a lcran. e ` e

48

CHAPTER 6. LE MECANISME XDR (EXTERNAL DATA REPRESENTATION) #include <stdio.h> #include <rpc/xdr.h> #include "utilInet.h" void main (int argc, char *argv[]) { int sock ; char *b = "un message ... vers recepteur" ; XDR x ; FILE *f ; if (argc != 2) { fprintf (stderr, "usage : exdr rhost\n") ; exit (1) ; } sock = mkClient (SOCK_DGRAM , argv [1], "impression") ; shutdown (sock, 0) ; xdrstdio_create (&x, fdopen (sock, "w"), XDR_ENCODE) ; xdr_wrapstring (&x, &b) ; xdr_destroy (&x) ; /* effectue le fflush() */ fclose (f) ; close (sock) ; } /* * rxdr.c: recepteur avec XDR (usage : rxdr) */ #include <stdio.h> #include <rpc/xdr.h> #include "utilInet.h" void main (void) { int sock = mkServeur (SOCK_DGRAM, "impression") ; XDR x ; FILE *f ; shutdown (sock, 1) ; xdrstdio_create (&x, fdopen (sock, "r"), XDR_DECODE) ; while (1) { char *b = NULL ; xdr_wrapstring (&x, &b) ; fprintf (stderr, "le recepteur a recu: %s\n", b) ; xdr_free (xdr_wrapstring, &b) ; sleep (2) ; } xdr_destroy (&x) ;

6.5. CONSTRUCTION DE NOUVELLES FONCTIONS XDR fclose (f) ; close (sock) ; }

49

Exercice 1 : adapter ce qui prc`de au SOCK STREAM, e e Exercice 2 : tranformer rxdr en serveur de question/rponse, ne pas oublier les fflush(), le e serveur doit ventuellement conna le client qui a envoy une question pour pouvoir lui renvoyer e tre e la rponse (ajouter linformation ncessaire dans le message transmis), il est possible de connece e ter/dconnecter la socket sous-jacente puisquelle est en datagram. e

6.5

Construction de nouvelles fonctions XDR

On fabrique de nouvelles fonctions de transmission en combinant les fonctions prdnies ; ces e e nouvelles fonctions doivent respecter le prototype gnral des fonctions XDR. e e

6.5.1

XDR de structures

Par exemple, pour transmettre des nombres complexes typedef struct complexe { float r, i ; } complexe ; on implante lXDR bool_t xdr_complexe (XDR *xdrs, complexe *pc) { return xdr_float (xdrs, &pc->r) && xdr_float (xdrs, &pc->i) ; }

6.5.2

XDR de tableau de taille variable

Le tableau est a lextrieur de la structure et il peut donc tre allou si on est en dcodage. ` e e e e #define TAILLE_MAX 10 typedef struct tableau_flexible { u_int nb ; /* nombre delements de t <= TAILLE_MAX */ complexe *t ; } tableau_flexible ; bool_t xdr_tableau_flexible (XDR *xdrs, tableau_flexible *tc) { return xdr_array (xdrs, &tc->t, &tc->nb, TAILLE_MAX, sizeof (complexe), xdr_complexe) ; }

50

CHAPTER 6. LE MECANISME XDR (EXTERNAL DATA REPRESENTATION)

6.5.3

XDR de structures contenant un tableau

Un exemple un peu plus compliqu a cause du tableau p qui appartient a la structure : pour e ` ` transmettre des piles struct pile { int nb ; complexe p [20] ; } ; La solution suivante est en fait errone, puisque si on utilise un xdr_free (xdr_pile, ...), le e tableau qui est dans la structure sera libr ! ee bool_t xdr_pile (XDR *xdrs, struct pile *p) { complexe *pc = p->p ; /* xdr_array() a besoin dun DOUBLE pointeur */ /* on ne transmet que les "cases pleines" de la pile */ return xdr_array (xdrs, &pc, &p->nb, 20, sizeof (complexe), xdr_complexe) ; } Une solution raisonnable consiste a ne pas utiliser xdr_array(). On crit directement une boucle ` e sur le tableau : bool_t xdr_pile (XDR *xdrs, struct pile *p) { int i ; /* on ne transmet que les "cases pleines" de la pile */ if (! xdr_int (xdrs, &p->nb)) return 0 ; for (i = 0 ; i < p->nb ; i++) { if (! xdr_complexe (xdrs, &p->p [i])) return 0 ; } return 1 ; }

6.5.4

XDR de liste cha ee n

Pour transmettre une liste cha ee, le plus simple est dcrire une XDR rcursive, par exemple : n e e typedef struct elem *liste ; bool_t xdr_liste (XDR *xdrs, liste *pl) ; typedef struct elem { complexe c ; liste s ; } elem ; bool_t xdr_elem (XDR *xdrs, elem *pe) { return xdr_complexe (xdrs, &pe->c) && xdr_liste (xdrs, &pe->s) ; }

6.5. CONSTRUCTION DE NOUVELLES FONCTIONS XDR

51

bool_t xdr_liste (XDR *xdrs, liste *pl) { /* rappelons-nous que si on est en decodage et que *pl est nul * xdr_pointer() alloue la zone receptrice puis la met a zero * ce qui assure que les elements suivant seront correctement alloues. */ return xdr_pointer (xdrs, pl, sizeof (elem), xdr_elem) ; } Le malaise vient ici de lexploration rcursive de la liste qui peut tre longue. Lalgorithme non e e rcursif ci-dessous utilise le double pointeur ps qui rep`re toujours le champ s du dernier lment e e ee transmis (encod, dcod ou libr) : e e e ee typedef struct elem *liste ; bool_t xdr_liste (XDR *xdrs, liste *pl) ; typedef struct elem { complexe c ; liste s ; } elem ; bool_t xdr_elem (XDR *xdrs, elem *pe) ; { return xdr_complexe (xdrs, &pe->c) ; /* on ne transmet plus le pointeur */ } bool_t xdr_liste (XDR *xdrs, liste *p) { while (1) { if (xdrs->x_op == XDR_ENCODE || xdrs->x_op == XDR_DECODE) { if (! xdr_pointer (xdrs, p, sizeof (elem), xdr_elem)) return FALSE ; if (*p == 0) return TRUE ; p = & (*p)->s ; } else if (xdrs->x_op == XDR_FREE) { if (*p != 0) { liste old = *p ; *p = (*p)->s ; if (! xdr_pointer (xdrs, &old, sizeof (elem), xdr_elem)) return FALSE ; } else return TRUE ; } } } Exercice les param`tres traditionnels int argc et char *argv[] de toute fonction main() e reprsentent la commande dont on a demand lexcution au syst`me. Ceci peut tre une mani`re de e e e e e e mmoriser une commande. Ecrire les structures de donnes permettant de reprsenter un historique e e e

52

CHAPTER 6. LE MECANISME XDR (EXTERNAL DATA REPRESENTATION)

de commandes, et les fonctions XDR associes. e

Chapter 7 Appel de procdure loigne SunOS 4.x e e e (RPC)


7.1 Rappel

Les appels de procdures loignes, ou RPC (Remote Procedure Call), permettent la commue e e nication interprocessus en utilisant un outil bien connu de la programmation structure : le souse programme. Les deux processus ainsi mis en communication peuvent tourner sur une mme machine ou sur e deux machines relies par un rseau. e e En gnral, le processus qui fait lappel de procdure est appel client, le processus qui excute le e e e e e corps de la procdure est appel serveur, une procdure fournie par un serveur est appele service. e e e e Un serveur peut fournir plusieurs services. Un appel RPC se droule comme suit : lobjet donne est convoy par le rseau du client au e e e e serveur, le service construit lobjet rsultat puis le renvoie vers le client. e Ces objets sont transmis avec le mcanisme XDR. e En r`gle gnrale, les appels de procdure sont donc synchrones. e e e e

7.2
7.2.1

Architecture des RPC de SunOS 4.x


Situation des RPC

Le fonctionnement des RPC est bas sur les sockets : les passages de param`tres utilisent des e e sockets connectes entre le client et le serveur. Les RPC correspondent a la couche session du mod`le e ` e de lOSI.

7.2.2

Nommage des services RPC

Sous SunOS 4.x, les services sont dsigns par les cinq informations suivantes : e e nom de la machine supportant le serveur, nom du serveur (ou programme), version du programme : plusieurs versions dun mme serveur peuvent coexister sans ambigu e, e t service demand (un serveur propose plusieurs services), e protocole utilis par la (les) socket(s) sous-jacente(s). e 53

54

CHAPTER 7. APPEL DE PROCEDURE ELOIGNEE SUNOS 4.X (RPC)

Les noms de serveur sont en fait des numros, les serveurs ociels comme NFS (Network File e System) poss`dent des numros quil ne faut pas rutiliser. Voici une table rsumant les 4 tranches e e e e de numros disponibles. e limites de la tranche usage 0x0000 0000 a 0x1FFF FFFF serveurs ociels enregistrs par Sun ` e 0x2000 0000 a 0x3FFF FFFF ` libre 0x4000 0000 a 0x5FFF FFFF ` rserv aux serveurs transitoires e e 0x6000 0000 a 0xFFFF FFFF ` rserv par Sun e e Les serveurs transitoires utilisent des numros allous dynamiquement et ne peuvent donc tre connus e e e qu` lexcution. Par exemple, si un serveur doit appeler une procdure de son client (il sagit dun a e e rtro appel), le client demande un numro de programme transitoire puis le communique a son e e ` serveur, celui-ci peut ensuite appeler le client en utilisant ce numro. e Les numros de des serveurs ociels se trouvent dans les chiers du rpertoire e e /usr/include/rpcsvc. Par exemple on trouve dans rusers.h : #define RUSERSPROG 100002 qui est le numro du serveur RUSERS, et dans nfs_proto.x crit en rpcgen : e e program NFS_PROGRAM { version NFS_VERSION { void NFSPROC_NULL(void) = 0 ; ... } = 2; } = 100003; ce qui indique que le programme NFS poss`de le numro 100003, il ny a que la deuxi`me version e e e (NFS VERSION) qui propose, entre autres, le service NFSPROC NULL de numro 0. e Un tel service de numro 0 sans param`tre et ne retournant aucune valeur devrait en principe e e tre fourni par tout serveur ; il permet de tester la prsence du serveur et est utilis par la commande e e e rpcinfo. Un serveur RPC peut tre disponible avec un des protocoles TCP et UDP ou les deux. e

7.2.3

Le processus portmap

Chaque machine susceptible de proposer des serveurs RPC doit supporter le dmon portmap qui e g`re la table des serveurs RPC disponibles sur la machine. Une entre de cette table a la forme e e suivante : [numero de programme, numero de version, protocole, numero de port] La commande rpcinfo -p imprime le contenu du portmap local. Le portmap fournit des services permettant lutilisation de sa table : crer une entre (i.e. enregistrer un nouveau serveur), e e dtruire une entre (i.e. eacer un serveur enregistr), e e e consultation dune ou plusieurs entres. e Le portmap est un serveur bien connu dont le numro de port gure dans le chier /etc/services e (sunrpc dont le numro de port est 111) pour les deux protocoles TCP et UDP ; il est donc accessible e par tout processus. Le portmap ne g`re pas les numros de service : ce sont les serveurs eux-mmes qui sen chargent. e e e Le portmap est indispensable pour quun client puisse localiser le serveur dont il a besoin.

7.3. PRIMITIVES RPC DE HAUT NIVEAU

55

7.2.4

Lenregistrement dun serveur

Pour quun serveur soit appelable il doit senregistrer (cf les fonctions registerrpc(), svc_register(), pmap_set()). Cette opration consiste dune part a demander au portmap loe ` cal de crer une nouvelle entre et dautre part a mmoriser localement la correspondance entre e e ` e un numro de service et ladresse de la fonction qui limplmente ou bien ladresse de la fonction e e dispatcher suivant le niveau de primitive utilis. e Avant de se terminer, un serveur devrait lindiquer a son portmap (cf les fonctions ` svc_unregister(), pmap_unset()).

7.2.5

Le mcanisme dappel vu du client e

Ct client, un appel de service se passe en deux temps : oe 1. consultation du portmap de la machine distante cense supporter le serveur pour rcuprer le e e e numro de port du serveur (cf les fonctions clnt_create(), clnttcp_create(), clntudp_create() e et pmap_getport()), 2. il est ensuite possible de faire plusieurs demandes de services au serveur (cf les fonctions clnt_call()). La fonction de haut niveau callrpc() encha de faon transparente ces deux tapes. ne c e

7.2.6

Smantique des appels e

La smantique dun appel RPC se rapporte au nombre dexcutions eectives dun service lors e e de son appel. Celle-ci varie en fonction de la abilit du protocole de communication utilis (en e e loccurrence udp ou tcp). En tcp, la communication est able. Si lappel russit, le client est s r que le serveur a excut e u e e exactement une fois le service. Si lappel choue, il peut y avoir plusieurs causes, le serveur tait e e indisponible et il ny a eu aucune excution, ou bien il y a eu dclenchement du dlai de garde ct e e e oe client, dans ce cas il est possible que le service ait t eectivement honor par le serveur trop lent ee e (smantique 0 ou 1). e En udp la communication nest pas able. Un appel est abandonn par le client, avec diagnostic e derreur, si aucun rsultat nest arriv au terme dun dlai de garde total. Pendant ce dlai, la demande e e e e de service est relance a chaque expiration dune priode de relance si aucun rsultat nest arriv. e ` e e e Ainsi, un seul appel peut donner lieu a plusieurs excutions du service (par exemple si le serveur est ` e tr`s lent a rpondre). le nombre maximum dexcutions du service est environ le rapport du dlai e ` e e e de garde sur la priode. Ceci signie que si un appel russit le client est assur que le service a t e e e ee excut au moins une fois, si lappel choue il se peut que le service ait t excut zro, une ou e e e ee e e e plusieurs fois ! La smantique dappel pose probl`me d`s que lexcution dun service est amen a modier ltat e e e e e` e du serveur (dans ce cas il vaut mieux utiliser tcp).

7.3

Primitives RPC de haut niveau

Les primitives de haut niveau sont simples a utiliser mais peu exibles. Cette couche ne fonctionne ` quavec le protocole UDP, ce qui limite la taille des param`tres apr`s encodage a environ 8K. Ceci a e e ` aussi pour consquence que la smantique des appels sera zro, une ou plusieurs fois. e e e Son attrait principal est sa grande simplicit de mise en uvre. e

56

CHAPTER 7. APPEL DE PROCEDURE ELOIGNEE SUNOS 4.X (RPC)

7.3.1

Le client

Un client dispose des deux primitives 7.3.1.1 callrpc() enum clnt stat callrpc ( char *host, u long prognum, u long versnum, u long procnum, xdrproc t inproc, char *in, xdrproc t outproc, char *out ); /* renvoie RPC SUCCESS (cf rpc/clnt.h) si OK */ qui eectue lappel distant du service procnum de la version versnum du programme prognum sur la machine host. in est un pointeur sur lobjet donne qui sera envoy avec lXDR inproc. out est un pointeur e e sur lobjet rsultat qui sera reu avec lXDR outproc. e c 7.3.1.2 clnt perrno() void clnt perrno (enum clnt stat stat) ; qui imprime un message derreur approrpi en cas dechec de callrpc(). e

7.3.2

Le serveur

Un serveur dispose de : 7.3.2.1 registerrpc() registerrpc ( u long prognum, u long versnum, u long procnum, char *(*procname) (), xdrproc t inproc, xdrproc t outproc ); o` procname est le code de la procdure procnum de la version versnum du programme prognum, et u e inproc et outproc sont les fonctions XDR de lobjet donne et de lobjet rsultat. Voici le prototype e e gnral de procname : e e TYPE RESULTAT *procname (TYPE DONNEE *) ; Cette fonction doit tre utilise autant de fois quil y a de services dans le serveur. e e et de :

7.3. PRIMITIVES RPC DE HAUT NIVEAU 7.3.2.2 svc run() svc run () ;

57

implmente la boucle sans n du serveur : elle attend une demande de service, tente de la satisfaire e puis attend la demande suivante...

7.3.3

Exemple : un compte bancaire

Il sagit dun serveur de compte (bancaire ?) proposant une opration lmentaire et la consultae ee tion du compte. On a intrt a se donner un chier dentte commun au serveur et au client : ee ` e /* * interfc.h: interface commun au compte et a ses clients */ #include <stdio.h> #include <rpc/rpc.h> #define PROG_NUM 0x20000000 /* numero du programme: le premier non officiel */ #define VERS_NUM 1 /* version du programme */ #define NULL_PROC 0 /* liste des operations disponibles sur un compte */ #define OPE_NUM 1 #define SEE_NUM 2 Le code du serveur est alors le suivant : /* * compte.c: Un compte sur lequel on fait des operations ou quon consulte */ #include "interfc.h" long * ope (int *val) ; long * consult (void) ; /* les deux services */ /* proposes */

void main (void) { if (registerrpc (PROG_NUM, VERS_NUM, OPE_NUM, ope, xdr_int, xdr_long) == -1 ||registerrpc (PROG_NUM,VERS_NUM,SEE_NUM,consult,xdr_void,xdr_long)==-1){ perror ("registerrpc") ; exit (1) ; } svc_run () ; } static long compte = 0 ; /* contient la valeur courante du compte */

58

CHAPTER 7. APPEL DE PROCEDURE ELOIGNEE SUNOS 4.X (RPC)

long *ope (int *val) { compte += *val ; return &compte ; } long *consult (void) { return &compte ; } on remarque que les fonction ope() et consult() qui implantent les services ont comme param`tre e un pointeur sur lobjet in et renvoient aussi un pointeur sur lobjet rsultat. e Voici le code dun client possible (celui-ci suppose que le serveur tourne sur la machine homel) : /* * ope.c: Un client du serveur de compte * * usage : ope valeur */ #include "interfc.h" void main (int argc, char *argv []) ; {int val, stat ; long compte ; val = atoi (argv [1]) ; stat = callrpc ("homel", PROG_NUM, VERS_NUM, OPE_NUM, xdr_int, &val, xdr_long, &compte) ; if (stat != RPC_SUCCESS) clnt_perrno (stat) ; else printf ("valeur du compte: %ld\n", compte) ; }

7.4

Primitives RPC de bas niveau

Elles sont plus complexes que les primitives de haut niveau, mais aussi plus riches en fonctionnalits : e choix du protocole de transport (UDP ou TCP), contrle des dlais de garde, o e ma trise de lallocation/libration mmoire par les fonctions XDR, e e possibilit didentication des clients. e ecacit accrue du fait que la consultation du portmap na lieu quune seule fois pour plusieurs e appels de service.

7.4.1

Le client

Les structures de donnes : e

7.4. PRIMITIVES RPC DE BAS NIVEAU CLIENT * ; enum clnt stat ; struct timeval { /* sys/time.h */ long tv sec ; /* seconds */ long tv usec ; /* and microseconds */ };

59

Le type CLIENT permet de conserver la localisation du serveur apr`s consultation du portmap. e Les primitives de gestion des clients, en particulier, les primitives de cration renvoient le pointeur e nul sil y a eu une erreur, dautre part, sur un client UDP ne peuvent circuler que des param`tres de e taille infrieure a 8K : e ` 7.4.1.1 clnt create () CLIENT * clnt create ( char *host, u long prognum, u long versnum, char *protocol /* "udp" ou "tcp" */) ; CLIENT * clnttcp create ( struct sockaddr in *addr, /* addr->sin port == 0 => consultation du portmap distant */ u long prognum, u long versnum, int *sockp, /* *sockp == RPC ANYSOCK ==> creation dune socket */ u int sendsz, u int recvsz /* 0, 0 => valeurs par defaut */) ; CLIENT * clntudp create( struct sockaddr in *addr, /* addr->sin port == 0 => consultation du portmap distant */ u long prognum, u long versnum, struct timeval periode de relance, int *sockp) ; /* *sockp == RPC ANYSOCK ==> creation dune socket */ Ces trois primitives interrogent le portmap loign et construisent la structure CLIENT. e e 7.4.1.2 clnt control () void clnt control (CLIENT *clnt, const u int req, char *info) ; Permet, par exemple, de changer la priode de relance : e struct timeval retry = {1, 0} ; clnt_control (c, CLSET_RETRY_TIMEOUT, &retry) ;

60

CHAPTER 7. APPEL DE PROCEDURE ELOIGNEE SUNOS 4.X (RPC)

Ceci nest possible quen datagramme. 7.4.1.3 clnt pcreateerror () void clnt pcreateerror (char *str) ; imprime un message derreur appropri apr`s une erreur dinterrogation du portmap. e e 7.4.1.4 clnt destroy() void clnt destroy (CLIENT *clnt) ; Pour dtruire un client. e 7.4.1.5 clnt call()

La primitive dappel de service est enum clnt stat clnt call ( CLIENT *clnt ; u long procnum ; xdrproc t inproc, char *in, xdrproc t outproc ; char *out ; struct timeval delai de garde total) ; Dans le protocole UDP, le nombre dessais dun appel loign avant de considrer quil y a chec e e e e est en gros de delai_de_garde_total/periode_de_relance. En TCP, il ny a quun dlai de garde total puisque le transport est able. e 7.4.1.6 clnt freeres() bool t clnt freeres (CLIENT *clnt, xdrproc t outproc, char *out) ; Pour restituer la mmoire alloue dynamiquement par lXDR de dcodage outproc. e e e 7.4.1.7 clnt perror() void clnt perror (CLIENT *clnt, char *str) ; Pour imprimer un message lors dune erreur dappel.

7.4.2

Le serveur
SVCXPRT

Structure de donne dun serveur dans <rpc/svc.h> : e

7.4. PRIMITIVES RPC DE BAS NIVEAU 7.4.2.1 svctcp create()

61

Cration dun serveur : e SVCXPRT * svctcp create ( int sock, /* RPC ANYSOCK ==> creation dune socket */ u int sendsz, recvsz /* 0, 0 ==> valeurs par defaut */) ; SVCXPRT * svcudp bufcreate ( int sock, /* RPC ANYSOCK ==> creation dune socket */ u int sendsz, recvsz /* PAS de valeurs par defaut */) ; SVCXPRT * svcudp create (RPC ANYSOCK) ; void svc destroy (SVCXPRT *xprt) ;

7.4.2.2

svc register()

Enregistrement du dispatcher dun serveur : bool t svc register ( SVCXPRT *xprt, u long prognum, versnum, void (*dispatcher) (struct svc req *requete, SVCXPRT *), u long protocol /* IPPROTO UDP ou bien IPPROTO TCP */) ; void svc unregister (u long prognum, u long versnum) ; enregistre le dispatcher correspondant a la version versnum du programme prognum. ` 7.4.2.3 Le dispatcher

Le dispatcher est une routine spcique a lapplication, son rle est daiguiller les appels des clients e ` o sur les routines eectives de service. Le prototype dun dispatcher doit tre e void dispatcher (struct svc req *req, SVCXPRT *X) ; Le dispatcher doit : 1. identier le service demand grce a req->rq_proc, qui est le numro de service demand. e a ` e e 2. rcuprer lobjet donne dans in avec : e e e bool t svc getargs (SVCXPRT *xprt, xdrproc t inproc, char *in) ; 3. excuter le code de service qui fabrique lobjet rsultat out, e e 4. renvoyer lobjet rsultat avec : e bool t svc sendreply (SVCXPRT *xprt, xdrproc t outproc, char *out) ;

62

CHAPTER 7. APPEL DE PROCEDURE ELOIGNEE SUNOS 4.X (RPC) 5. librer lobjet donne : e e bool t svc freeargs (SVCXPRT *xprt, xdrproc t obfproc, char *obj) ; De plus, on dispose de : void svcerr noproc (SVCXPRT *xprt) ; /* service inexistant */ si le service nexiste pas, et de : void svcerr decode (SVCXPRT *xprt) ; si svc_getargs() choue. e Le prototype dune fonction de service est : TYPE RESULTAT *service (TYPE DONNEE *donnee) ;

7.4.3

Exemple : le compte bancaire

Il sagit toujours du compte bancaire : programmons le serveur a un niveau plus n (le chier ` interfc.h est inchang) : e /* * compte.c: Un compte sur lequel on fait des operations ou quon consulte */ #include "interfc.h" void dispatcher (struct svc_req *rqstp, SVCXPRT *transp) ; void main (void) {SVCXPRT *transp ; transp = svcudp_create (RPC_ANYSOCK) ; if (transp == NULL) { fprintf (stderr, "creation du serveur UDP impossible\n") ; exit (1) ; } pmap_unset (PROG_NUM, VERS_NUM) ; if (! svc_register (transp, PROG_NUM, VERS_NUM, dispatcher, IPPROTO_UDP)) { fprintf (stderr, "enregistrement du serveur impossible\n") ; exit (1) ; } svc_run () ; } static long compte = 0 ; /* contient la valeur courante du compte */ long *ope (int *val) { compte += *val ;

7.5. RPC NON BLOQUANT return &compte ; } void dispatcher (struct svc_req *rqstp, SVCXPRT *transp) { switch (rqstp->rq_proc) { case NULL_PROC: /* appelee pour verifier la presence du serveur */ if (! svc_sendreply (transp, xdr_void, NULL)) fprintf (stderr, "renvoi au client impossible\n") ; return ; case SEE_NUM: if (! svc_sendreply (transp, xdr_long, &compte)) fprintf (stderr, "renvoi au client impossible\n") ; return ; case OPE_NUM: {int v ; if (! svc_getargs (transp, xdr_int, &v)) { svcerr_decode (transp) ; return ; } if (! svc_sendreply (transp, xdr_long, ope (&v))) fprintf (stderr, "renvoi au client impossible\n") ; svc_freeargs (transp, xdr_int, &v) ; } return ; default: svcerr_noproc (transp) ; return ; } }

63

Remarque : on a rutilis la fonction ope() telle que dnie dans lexemple illustrant les RPC de e e e haut niveau. Cette fois on aurait pu utiliser le protocole TCP plutt que UDP, (ou mme les deux, il faut o e crer deux structures SVCXPRT et enregistrer le dispatcher sur les deux). La procdure dispatcher e e consulte elle-mme la demande puis laiguille sur le bon service ; le cas NULL_PROC permet a un client e ` de tester la prsence dun serveur (tous les serveurs ociels implmentent ce service minimum avec e e le numro 0). e La version haut niveau du client peut tre utilise telle quelle avec ce nouveau serveur. e e Exercice : programmer le client du compte bancaire avec les primitives de bas niveau.

7.5

RPC non bloquant

Certains services peuvent tre rendus non bloquants, voici les conditions qui autorisent ce fonce tionnement : protocole able (TCP), le serveur nexcute pas svc_sendreply() en n de service, e

64

CHAPTER 7. APPEL DE PROCEDURE ELOIGNEE SUNOS 4.X (RPC)

ct client, la routine de dcodage des rsultats outproc doit tre NULL et le dlai de garde oe e e e e doit tre nul. e Cette technique permet daugmenter les performances lors de transferts volumineux. Pour resynchroniser le client et son serveur il est toujours possible dappeler un service bloquant. Exercice illustrer ce gain en performance pour transmettre un gros chier a un serveur par bloc ` de 1024 (faites des mesures dabord en bloquant puis en non bloquant).

7.6

Diusion dappel

Il est possible de diuser un appel de service a tous les les rseaux de diusion connects localement ` e e avec la primitive : enum clnt stat clnt broadcast( u long prognum, u long versnum, u long procnum, xdrproc t inproc, char *in, xdrproc t outproc, char *out, resultproc t eachresult) ; typedef int (*resultproc t) (char *out, struct sockaddr in *addr) ; Le seul protocole utilisable est UDP et la taille des requtes doit tre infrieure a 1500 octets. La e e e ` primitive eachresult() est appele pour chaque rponse reue. Le out de eachresult dsigne ce e e c e rsultat et est le mme que le out de clnt_broadcast. addr est ladresse de la machine qui rpond. e e e eachresult() doit renvoyer vrai pour arrter la diusion. e Par dfaut, clnt_broadcast() utilise le syst`me dauthentication Unix. e e Exercice crire un programme qui imprime les noms des 5 premi`res machines qui proposent un e e serveur donn. e Exercice crire une commande qui imprime les noms des machines sur lesquelles est connect e e un utilisateur donn (voir rusers(3)). e

7.7

Rtro appel e

Un client peut sallouer dynamiquement un numro transitoire de programme aupr`s de son e e portmap local grce a pmap_set(). Il le communique a son serveur qui peut ensuite lui demander a ` ` des services.

7.8

RPC scuriss e e

Les RPC permettent a un serveur didentier les usagers qui lui demandent un service. `

7.8.1

Exemple : le compte bancaire scuris e e

Le but est dassurer que le client qui manipule le compte est le propritaire du compte bancaire e (i.e. du serveur), en fait seule lopration ope est ici scurise. e e e

7.8. RPC SECURISES Voici le code du serveur :

65

int c_est_moi (struct svc_req *rq) { if (rq->rq_cred.oa_flavor == AUTH_UNIX) { return ((struct authunix_parms *) rq->rq_clntcred)->aup_uid == getuid () ; } else return 0 ; } void dispatcher (struct svc_req *rqstp, SVCXPRT *transp) { switch (rqstp->rq_proc) { ... case SEE_NUM: /* operation non securisee */ if (! svc_sendreply (transp, xdr_long, &compte)) fprintf (stderr, "renvoi au client impossible\n") ; return ; case OPE_NUM: /* operation securisee */ if (c_est_moi (rqstp)) {int v ; if (! svc_getargs (transp, xdr_int, &v)) { svcerr_decode (transp) ; return ; } if (! svc_sendreply (transp, xdr_long, ope (&v))) fprintf (stderr, "renvoi au client impossible\n") ; svc_freeargs (transp, xdr_int, &v) ; } else { svcerr_weakauth (transp) ; } return ; ... } } De son ct, le client doit sidentier avant deectuer un appel : oe #include "interfc.h" main (int argc, char *argv []) { CLIENT * c = clnt_create ("homel", PROG_NUM, VERS_NUM, "udp") ; int v = atoi (argv[1]) ; long compte ; struct timeval delai ; delai.tv_sec = 20 ; delai.tv_usec = 0 ; if (c == NULL) { clnt_pcreateerror ("serveur de compte") ; exit (1) ;

66 }

CHAPTER 7. APPEL DE PROCEDURE ELOIGNEE SUNOS 4.X (RPC)

/* detruire linformation didentification par defaut (rien) */ auth_destroy (c->cl_auth) ; /* creer linformation didentification */ c->cl_auth = authunix_create_default () ; if (clnt_call (c, OPE_NUM, xdr_int, &v, xdr_long, &compte, delai) == RPC_SUCCESS) { printf ("compte = %ld\n", compte) ; } else { clnt_perror (c, "ope") ; } /* detruire linformation didentification */ auth_destroy (c->cl_auth) ; clnt_destroy (c) ; } Exercice : examiner la doc de authunix_create_default() et authunix_create() pour shunter facilement la protection AUTH_UNIX. Exercice : utilisez clnt_control() dans un client du compte bancaire et la primitive sleep(3V) dans le serveur pour mettre en vidence les smantiques dappel : en udp, un seul appel peut donner e e lieu a plusieurs excutions par le serveur, en tcp, lexcution peut tre eective mme si le client na ` e e e e pas eu de rponse (atteinte du dlai de garde chez le client). e e

Chapter 8 Le gnrateur RPCGEN e e


Rpcgen propose dune part un langage de description dinterface pour lutilisation des appels de procdure loigne (RPC) et dautre part, un compilateur de ce langage permettant dobtenir les e e e sources C des procdures souches destines aux clients et un squelette du serveur dni par linterface. e e e En particulier le langage permet la description dans une syntaxe proche de celle du C des direntes structures de donnes correspondant aux param`tres des procdures loignes, et celle e e e e e e de la liste des services fournis par le serveur. Il reste alors au programmeur a crire le code des procdures du serveur et le code dun ou de `e e plusieurs clients ventuel utilisant les souches fournies par rpcgen. e Ces intrts sont les suivant : ee produire un interface commun client/serveur, fabrication automatique du source C des fonctions XDR dapr`s la description des structures e de donnes, e fabrication automatique du squelette du serveur avec introduction du service 0 permettant de tester la prsence du serveur, e fabrication en C des souches dappel du client (stub), possibilit de modier apr`s coup les sources C obtenus. e e La fabrication dune application peut se rsumer a trois activits : e ` e 1. conception de linterface public du serveur qui aboutit a la rdaction dun chier .x en langage ` e rpcgen, 2. implantation des fonctions du serveur, 3. criture du code des clients. e Cette dmarche est illustre dans la suite sur un annuaire. e e

8.1

Ecriture de linterface en rpcgen

Voici le source rpcgen du chier annuaire.x : /* * annuaire.x */ const MAX = 100 ;

67

68 struct ENTREE { string nom <MAX> ; int tel [4] ; } ; enum STATUT {EXISTE, INEXISTANT} ; union REPONSE switch (STATUT err) { case EXISTE : ENTREE e ; case INEXISTANT : void ; } ; program ANN_PROG { version ANN_VERS { REPONSE get (string) = 1 ; void set (ENTREE) = 2 ; } = 1 ; } = 99 ; apr`s la commande e rpcgen annuaire.x

CHAPTER 8. LE GENERATEUR RPCGEN

on obtient les chiers suivants : annuaire.h contient la traduction en C des structures de donnes, les constantes symboliques hoe monymes des noms de service et les prototypes des fonctions de service correspondant aux dclarations du chier .x, voici un extrait de ce chier : e
/* Please do not edit this file. It was generated using rpcgen. */ ... #define MAX 100 struct ENTREE {char *nom ; int tel[4];}; typedef struct ENTREE ENTREE; enum STATUT {EXISTE = 0, INEXISTANT = 1,}; typedef enum STATUT STATUT; struct REPONSE { STATUT err; union { ENTREE e; } REPONSE_u; }; typedef struct REPONSE REPONSE; #define ANN_PROG ((u_long)99) #define ANN_VERS ((u_long)1) ...

8.2. ECRITURE DES SERVICES

69

annuaire clnt.c contient le code des souches destines aux programmes clients, et dont voici les e prototyes REPONSE * get_1 (char **argp, CLIENT *clnt) ; void * set_1 (ENTREE *argp, CLIENT *clnt) ;

Le 1 apr`s le nom de fonction correspond au numro de version du serveur. e e annuaire svc.c contient le squelette du serveur disponible en udp et tcp dont il reste a crire les `e services en respectant les prototypes : REPONSE * get_1 (char **, struct svc_req *) ; void * set_1 (ENTREE *, struct svc_req *) ;

Le 1 apr`s le nom de fonction correspond au numro de version du serveur. e e annuaire xdr.c contient le code des fonctions XDR de chacun des types dnis. e Par la suite, le seul chier quil est vraiment intressant de consulter est celui des structures de e donnes annuaire.h. e

8.2

Ecriture des services

On peut ranger dans services.c le code des dirents services, par exemple : e void * set_1 (ENTREE *abonne, struct svc_req *inutile_ici) { /* ranger le nouvel abonne dans une table interne */ ... return 0 ; } Puis on obtient un serveur excutable par e acc -o sv annuaire_svc.c services.c annuaire_xdr.c

8.3

Ecriture dun client

Les souches dacc`s au serveur ont un param`tre de type CLIENT quil faut fabriquer au pralable : e e e /* cl.c */ #include "annuaire.h" main (int argc, char *argv[]) { CLIENT *cl = clnt_create ("homel", ANN_PROG, ANN_VERS, "udp") ; REPONSE *rep ; rep = get_1 (argv [1], cl) ;

70

CHAPTER 8. LE GENERATEUR RPCGEN switch (rep->err) { case EXISTE: printf ("%s : ", rep->REPONSE_u.e.nom) ; for (i = 0 ; i < 4 ; i++) { printf ("%d ", rep->REPONSE_u.e.tel [i]) ; } break ; case INEXISTANT: printf ("%s: nest pas abonne\n", argv [1]) ; } clnt_freeres (cl, xdr_REPONSE, rep) ; clnt_destroy (cl) ;

} Remarque, il est toujours de la responsabilit du programme client de librer les structures de donnes e e e ventuellement alloues par les fonctions XDR lors du dcodage. e e e Puis on obtient lexcutable du client par e acc -o cl cl.c annuaire_clnt.c annuaire_xdr.c

Chapter 9 Linterface TLI


Linterface TLI (Transport Layer Interface) se situe au mme niveau que les sockets. Il correspont e au standard ISO 8072 et se veut indpendant de tout protocole ou toute famille de protocoles de la e couche transport. Il peut donc fonctionner au dessus de la famille TCP/IP mais aussi au dessus de tout autre famille. A lorigine, il a t implant dans Unix System 5 version 3. Il est mul dans SunOS 4.2 et intgr, ee e e e e e au dpend des sockets, dans SunOs 5.x (i.e. SVR4). e

9.1

Architecture des TLI

Un utilisateur des TLI peut demander a utiliser les services dun fournisseur de transport (trans` port provider ou TP) quelconque, par exemple /dev/tcp. Lentit manipule est alors le point de e e transport (transport endpoint ou tep). Par lintermdiaire du tep, lutilisateur a acc`s a un certain nombre de requtes destines au e e ` e e TP. Dautre part, des vnements en provenance du TP sont consultables par lutilisateur par la e e fonction t_look().

9.1.1

Fournisseurs de transport ou TP (Transport Provider)

Un fournisseur de transport particulier est caractris par le style de communication quil autorise e e (voir la table 9.1 et t_getinfo()). Plusieurs fournisseurs de transport sont en gnral disponibles e e T CLTS T COTS T COTS ORD communication sans connexion (i.e. udp) communication avec connexion (i.e. tcp) et dconnexion brutale e communication avec connexion et dconnexion sans perte de message e (i.e. tcp sous SVR4)

Figure 9.1: Les dirents styles de communication. e sur le syst`me, ils sont dsigns par une entre dans le rpertoire /dev, on trouve entre autres : e e e e e udp et tcp , qui sont des TP Internet, ticlts, ticots et ticotsord qui proposent les dirents styles de communication de la gure 9.1, e mais uniquement localement a la machine : il ny a pas de communication rseau. ` e 71

72

CHAPTER 9. LINTERFACE TLI

9.1.2

Ouverture dun tep : t open()


int tep = t open ("/dev/tcp", O RDWR, 0) ;

Il est possible dobtenir un tep pour un fournisseur de transport avec t_open() comme dans :

qui fournit un tep sur le TP tcp.

9.1.3

Publication dun tep : t bind()

Pour quun tep soit accessible de lextrieur, il faut, comme pour les sockets, le publier en lui e attachant une adresse. La primitive t_bind() eectue ce travail (t_unbind() en annule leet). Par exemple t bind (tep, 0, 0) ; associe une adresse approprie au tep, celle-ci ntant pas explicitement indique par lappelant e e e (car le deuxi`me param`tre est 0) sera dynamiquement alloue par le TP. e e e Le format de ladresse dpend, bien entendu, du TP utilise ; ainsi, pour tcp ou udp, les adresses e e sont du type bien connu struct sockaddr_in.

9.1.4

Les vnements : t look() e e

D`s quun tep poss`de une adresse, des processus externes peuvent lui envoyer des requtes e e e (envoi de message, demande de connexion, ...). Le propritaire du tep voit ces requtes comme des e e vnements, en gnral asynchrones avec son propre droulement, quil doit dtecter et, en principe, e e e e e e traiter. Voici quelques-uns de ces vnements (voir t_look() pour la liste compl`te) : e e e T DATA T DISCONNECT T ORDREL des donnes sont arrives e e une dconnexion brutale est arrive e e une dconnexion en douceur est arrive e e

On peut dtecter (et traiter) ces vnements soit de faon synchrone avec le programme en e e e c utilisant le fait quun vnement provoque lchec des primitives TLI, soit de faon asynchrone en e e e c utilisant le signal SIGPOLL qui peut tre envoy par le TP lorsquun vnement se produit : e e e e dtection synchrone si une primitive TLI choue (renvoie -1) on regarde si la variable globale e e t_errno contient la valeur TLOOK. Si cest le cas alors un vnement sest produit. e e dtection asynchrone on arme le signal SIGPOLL et on demande au TP de dclencher SIGPOLL e e sur certains de ces vnements (voir streamio(7) avec I_SETSIG comme valeur du param`tre e e e command de la primitive ioctl()). Lorsquun vnement a t dtect par lune ou lautre technique, on peut utiliser t_look(tep) pour e e ee e e conna sa nature et le traiter. tre Voici un exemple de traitement synchrone dvnement dans le cas dune dconnexion en douceur e e e e (communication T COTS ORD) alors que le processus est en attente de rception : ... if (t_rcv (tep, buf, lgbuf, 0) == -1) { if (t_errno == TLOOK && t_look (tep) == T_ORDREL) { t_rcvrel (tep) ; t_sndrel (tep) ;

9.1. ARCHITECTURE DES TLI goto fin ; } else { t_error ("t_rcv()") ; exit (1) ; } } else { /* traitement du message recu */ ... fin:

73

9.1.5

Les tats e

Le fonctionnement dun tep et les primitives qui lui sont applicables sont parfaitement dnis, e dune part, par le style de communication supporte par le TP sous-jacent, dautre part, par une e table de transitions qui dnit lvolution de ltat dun tep en fonction des primitives qui lui sont e e e appliques. Ltat dun tep peut tre examin avec la primitive t_getstate (). Les trois gures 9.2, e e e e 9.3 et 9.4, prsentent une version simplie de cette table de transition pour chacun des trois styles e e de communication. Dans chacune de ces gures, les `ches hachures reprsentent la transmission e e e dun vnement depuis la primitive qui le dclenche jusqu` la primitive qui le traite. e e e a

t_close()

t_open()

T_UNBND

t_unbind()

t_bind()

t_rcvudata() T_IDLE t_sndudata()

Figure 9.2: Encha nement des tats pour une communication sans connexion (T CLTS). e Quand il ny a pas de connexion (gure 9.2), rien ne distingue le serveur du client : ils ont tous deux les mmes transitions. e Quand il y a connexion, le serveur peut communiquer soit sur le tep de connexion, soit sur un nouveau tep cr a cet eet et associ au tep du client (voir t_accept()). Dans les gures 9.3 et ee ` e 9.4, cest cette derni`re possibilit qui est illustre, cest pourquoi le ct serveur se compose de e e e oe deux graphes. On remarque que nimporte lequel des deux partenaires peut avoir linitiative de la dconnexion brutale ou ordonne , ceci est reprsent par les ou bien. e e e e

74

CHAPTER 9. LINTERFACE TLI

t_close()

t_open()

condition T_UNBND

action t_unbind()

t_bind() nombre de connexion=0

T_IDLE

t_close()

t_open()

t_close()

t_open()

nombre de connexion==0

t_listen() nombre de connexion+=1 T_INCON

T_UNBND

T_UNBND

t_unbind()

t_bind()

t_unbind()

t_bind()

t_listen() nombre de connexion+=1

T_IDLE t_rcvdis() ou bien t_snddis() t_snddis() ou bien t_rcvdis() t_rcv() T_DATAXFER t_snd()

T_IDLE

t_connect()

t_accept() nombre de connexion=1 t_rcv() T_DATAXFER t_snd()

tep de dialogue CLIENT SERVEUR

tep de connexion

Figure 9.3: Encha nement des tats pour une communication avec connexion et dconnexion brutale e e (T COTS). Au contraire des sockets, la primitive t listen() est bloquante jusqu` rception dune a e demande de connexion. La primitive t accept() permet dhonorer une demande de connexion et de xer le tep de communication. t accept() doit donc tre appele apr`s un t listen() russi. e e e e

9.1. ARCHITECTURE DES TLI

75

t_close() condition T_UNBND action t_unbind()

t_open()

t_bind() nombre de connexion=0

T_IDLE

t_close()

t_open()

t_close()

t_open()

nombre de connexion==0

t_listen() nombre de connexion+=1

T_UNBND

T_UNBND

T_INCON

t_unbind()

t_bind()

t_unbind()

t_bind()

t_listen() nombre de connexion+=1

T_IDLE

T_IDLE

t_connect()

t_accept() nombre de connexion=1 t_rcv() t_sndrel() ou bien t_rcvrel() T_DATAXFER t_rcv() t_snd()

t_rcvrel() ou bien t_sndrel() t_sndrel() ou bien t_rcvrel() T_OUTREL ou bien T_INREL T_DATAXFER

t_snd()

t_rcvrel() ou bien t_sndrel() t_rcv() ou bien t_snd() T_INREL ou bien T_OUTREL tep de dialoghe t_snd() ou bien t_rcv()

tep de connexion SERVEUR

CLIENT

Figure 9.4: Encha nement des tats pour une communication avec connexion et dconnexion ordonne e e e (T COTS ORD). Au contraire des sockets, la primitive t listen() est bloquante jusqu` rception a e dune demande de connexion. La primitive t accept() permet dhonorer une demande de connexion et de xer le tep de communication. t accept() doit donc tre appele apr`s un t listen() russi. e e e e On remarque que la dconnexion se fait en deux phases et quil est toujours possible de communiquer e (dans un seul sens) entre ces deux phases : les rceptions sont possibles pour linitiateur de la e dconnexion, les missions sont possibles pour celui qui a reu la demande de dconnexion. e e c e

76

CHAPTER 9. LINTERFACE TLI

9.1.6

Les structures de donnes : t alloc() e

Linterface TLI doit pouvoir sadapter a nimporte quel protocole, cest pourquoi toute informa` tion est stocke dans un tableau exible dcrit par la structure netbuf : e e struct netbuf { unsigned int maxlen ;/* longueur max de buf */ unsigned int len ; /* longueur effective de buf */ char *buf ; /* la zone "flexible" */ }; netbuf est utilise pour construire la plupart des donnes utilises par les primitives TLI. Par e e e exemple, le message envoy par la primitive t_sndudata() lors dune communication sans connexion e (T CLTS) a le format suivant : struct t unitdata { struct netbuf addr ; /* adresse du destinataire */ /* * (addr.buf est (struct sockaddr in *) pour un TP udp */ struct netbuf opt ; /* options specifiques au protocole */ struct netbuf udata ; /* message utile */ /* * udata.maxlen == udata.len est la longueur de la * zone reperee par udata.buf */ } La plupart des primitives TLI utilisent des structures de donnes spciques dont linitialisation e e dpend du TP utilis. La primitives t_alloc() alloue et initialise correctement ces derni`res, la e e e primitive t_free() permet de les librer : e char * t alloc (int tep, int struct type, int fields) ; int t free (char *struct, int struct type) ; Le param`tre struct_type indique le type de structure a allouer ou a librer, le param`tre fields e ` ` e e indique les champs de la structure quil faut allouer et ventuellement initialiser (si fields==T_ALL, e alors tous les champs doivent tre initialiss). e e Voici quelques-unes des valeurs de struct_type et les structures correspondantes (voir t_alloc() pour une liste compl`te) : e

struct type T BIND T CALL T DIS T UNITDATA T INFO

structure associe e struct t bind struct t call struct t dis struct t unitdata struct t info

9.2. ENVIRONNEMENT DE DEVELOPPEMENT

77

9.2

Environnement de dveloppement e
#include <tiuser.h>

Pour utiliser TLI il faut inclure tiuser.h comme dans

et compiler avec la biblioth`que libnsl.a comme dans e cc ... -lnsl

9.3

Un exemple de communication en T COTS

Dans cet exemple, un client se connecte au serveur, puis lui envoie toutes les lignes lues sur lentre e standard. Le serveur reoit ces lignes et les imprime sur sa sortie standard. Le serveur sert un seul c client puis se termine. Cest le client qui a linitiative de la dconnexion. Le serveur dtecte et traite e e de faon asynchrone la rception de et la demande de dconnexion. c e e Voici tout dabord le module tliInet.c qui spcialise un certain nombre de primitives pour la e famille TCP/IP et cache lusage de t_alloc(). #include "tliInet.h" #include <netdb.h> #include <netinet/in.h> void td_bind_qlen (int tep, int qlen) { struct t_bind *req = (struct t_bind *) t_alloc (tep, T_BIND, T_ALL), *rep = (struct t_bind *) t_alloc (tep, T_BIND, T_ALL) ; req->qlen = qlen ; t_bind (tep, req, rep) ; if (qlen) {/* cest en principe un "serveur" sur lequel on se connecte */ printf ("port = %d\n", ntohs (((struct sockaddr_in*) rep->addr.buf)->sin_port)) ; } t_free ((char *) req, T_BIND) ; t_free ((char *) rep, T_BIND) ; } struct t_call *td_listen (int listen_tep) /* renvoit ladresse du partenaire (peer), necessaire pour le t_accept() */ { struct t_call *peer = (struct t_call *) t_alloc (listen_tep, T_CALL, T_ALL) ; t_listen (listen_tep, peer) ; return peer ;

78 }

CHAPTER 9. LINTERFACE TLI

int td_accept (int listen_tep, struct t_call *peer) { int ntep = t_open ("/dev/tcp", O_RDWR, 0) ; t_bind (ntep, 0, 0) ; t_accept (listen_tep, ntep, peer) ; return ntep ; } void td_connect (int tep, const char *rhost, int port) { struct t_call *sndcall = (struct t_call *) t_alloc (tep, T_CALL, T_ALL) ; { struct hostent *h = gethostbyname (rhost) ; struct sockaddr_in *a = (struct sockaddr_in *) sndcall->addr.buf ; bzero (a, sizeof (struct sockaddr_in)) ; a->sin_family = AF_INET ; a->sin_port = htons (port) ; bcopy ((char*) h->h_addr, (char*) &a->sin_addr, h->h_length); } sndcall->addr.len = sizeof (struct sockaddr_in) ; t_connect (tep, sndcall, 0) ; t_free ((char *) sndcall, T_CALL) ; } Voici le code du client /* CLIENT, usage : cl port_du_serveur */ #include "tliInet.h" main (int argc, char * argv[]) { int port = atoi (argv [1]) ; int tep = t_open("/dev/tcp", O_RDWR, 0) ; char buf [1024] ; t_bind (tep, 0, 0) ; td_connect (tep, "lifl", port) ; while (printf ("? "), gets (buf) != NULL) { t_snd (tep, buf, strlen (buf) + 1, 0) ; } t_snddis (tep, 0) ; t_unbind (tep) ; t_close (tep) ;

9.3. UN EXEMPLE DE COMMUNICATION EN T COTS } Voici le code du serveur /* SERVEUR */ #include "tliInet.h" #include <sys/types.h> #include <stropts.h> #include <signal.h> static int listen_tep, comm_tep ; void traite_evenement (int sig) { signal (SIGPOLL, traite_evenement) ; switch (t_look (comm_tep)) { case T_DATA: { char buf [1024] ; int flags ; /* flags est necessaire mais pas utilise, voir le man */ t_rcv (comm_tep, buf, sizeof buf, &flags) ; printf ("SERVEUR recu : %s\n", buf) ; break ; } case T_DISCONNECT: t_rcvdis (comm_tep, 0) ; t_unbind (comm_tep) ; t_close (comm_tep) ; t_unbind (listen_tep) ; t_close (listen_tep) ; exit (0) ; break ; default: t_error ("evenement non traite") ; exit (1) ; } } main (int argc, char * argv[]) { listen_tep = t_open("/dev/tcp", O_RDWR, 0) ; td_bind_qlen (listen_tep, 55) ; { comm_tep = td_accept (listen_tep, td_listen (listen_tep)) ; signal (SIGPOLL, traite_evenement) ; ioctl (comm_tep, I_SETSIG, S_INPUT) ; /* demande au TP denvoyer SIGPOLL a chaque reception devenement */ while (1) pause () ; }

79

80 }

CHAPTER 9. LINTERFACE TLI

Vous aimerez peut-être aussi