Page suivante Page précédente Table des matières

16. Communication par le réseau TCP-IP

Les concepts fondamentaux (sockets, adresses, communication par datagrammes et flots, client-serveur etc.) sont les mêmes que pour les sockets locaux (voir sockets). Les particularités viennent des adresses : comment fabriquer une adresse à partir d'un nom de machine (résolution) et d'un numéro de port, comment retrouver le nom d'une machine à partir d'une adresse (résolution inverse) etc.

16.1 Sockets, addresses

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

Pour communiquer, les applications doivent créer des sockets (prises bidirectionnelles) par la fonction socket() et les relier entre elles. On peut ensuite utiliser ces sockets comme des fichiers ordinaires (par read, write, ...) ou par des opérations spécifiques ( send, sendto, recv, recvfrom, ...).

Pour désigner un socket sur une machine il faut une adresse de socket. Comme il existe différents types de sockets, les opérations sur les adresses concerne un type d'adresse général abstrait (struct sockaddr) qui recouvre tous les types concrets particuliers.

Pour TCP-IP (Inet), les adresses de sockets sont déterminées par un numéro IP, et un numéro de port. Pour IPv4, on utilise des struct sockaddr_in, qui possèdent 3 champs importants :

Attention, les octets de l'adresse IP et le numéro de port sont stockés dans l'ordre réseau (big-endian), qui n'est pas forcément celui de la machine hôte sur laquelle s'exécute le programme. Voir plus loin les fonctions de conversion hôte/réseau.

Pour IPv6, on utilise des struct sockaddr_in6, avec

16.2 Remplissage d'une adresse

Comme pour les sockets locaux, on crée les sockets par socket() et on « nomme la prise » par bind().

Préparation d'une adresse distante

Dans le cas le plus fréquent, on dispose du nom de la machine destinataire (par exemple la chaîne de caractères "www.elysee.fr"), et du numéro de port (un entier).

       #include <netdb.h>
       struct hostent *gethostbyname(const char *name);
gethostbyname() retourne un pointeur vers une structure hostent qui contient diverses informations sur la machine en question, en particulier une adresse h_addr
Une machine peut avoir plusieurs adresses
que l'on mettra dans sin_addr.

Le champ sin_port est un entier court en ordre réseau, pour y mettre un entier ordinaire il faut le convertir par htons()

Host TO Network Short
.

Voir exemple client-echo.c.

Préparation d'une adresse locale

Pour une adresse sur la machine locale

ne pas confondre avec la notion de socket du domaine local vue en sockets
, on utilise

Voir exemple serveur-echo.c.

Pour IPV6

Examen d'une adresse

La fonction getsockname() permet de retrouver l'adresse associée à un socket.

#include <sys/socket.h>

int   getsockname(int s  ,  
                  struct sockaddr  *name  ,
                  socklen_t * namelen )

Le numéro de port est dans le champ sin_port, pour le convertir en entier normal, utiliser ntohs() (Network TO Host short).

L'adresse IP peut être convertie en chaîne de caractères en notation décimale pointée (exemple "147.210.94.194") par inet_ntoa() (network to ascii),

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

char *inet_ntoa(struct in_addr in);
C'est ce qui est utilisé dans serveur-echo.c pour déterminer et afficher la provenance des requêtes.

On peut également tenter une résolution inverse, c'est-à-dire de retrouver le nom à partir de l'adresse, en passant par gethostbyaddr, qui retourne un pointeur vers une structure hostent, dont le champ h_name désigne le nom officiel de la machine.

Cette résolution n'aboutit pas toujours, parce que tous les numéros IP ne correspondent pas à des machines déclarées.

#include <netdb.h>
extern int h_errno;

struct hostent *gethostbyaddr(const char *addr, int len, int type);

struct hostent {
  char    *h_name;        /* official name of host */
  char    **h_aliases;    /* alias list */
  int     h_addrtype;     /* host address type */
  int     h_length;       /* length of address */
  char    **h_addr_list;  /* list of addresses */
}
#define h_addr  h_addr_list[0]  /* for backward compatibility */

16.3 Fermeture d'un socket

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

int shutdown(int fd, int how);

Un socket est bidirectionnel, le paramètre how indique quelle(s) moitié(s) on ferme : 0 pour la sortie, 1 pour l'entrée, 2 pour les deux (équivaut à close()).

16.4 Communication par datagrammes (UDP)

Création d'un socket

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

Cette fonction construit un socket et retourne un numéro de descripteur. Pour une liaison par datagrammes sur IPv4, utilisez la famille AF_INET, le type SOCK_DGRAM et le protocole par défaut 0.

Retourne -1 en cas d'échec.

Connexion de sockets

La fonction connect met en relation un socket (de cette machine) avec un autre socket désigné, qui sera le « correspondant par défaut » pour la suite des opérations.

#include <sys/types.h>
#include <sys/socket.h>

int  connect(int sockfd, 
             const struct sockaddr *serv_addr,
             socklen_t addrlen);

Envoi de datagrammes

Sur un socket connecté (voir ci-dessus), on peut expédier des datagrammes (contenus dans un tampon t de longueur n) par write(sockfd,t,n).

La fonction send()

int send(int s, const void *msg, size_t len, int flags);
permet d'indiquer des flags, par exemple MSG_DONTWAIT pour une écriture non bloquante.

Enfin, sendto() envoie un datagramme à une adresse spécifiée, sur un socket connecté ou non.

int sendto(int s, const void *msg, size_t len, int flags,
           const struct sockaddr *to, socklen_t tolen);

Réception de datagrammes

Inversement, la réception peut se faire par un simple read(), par un recv() (avec des flags), ou par un recvfrom, qui permet de récupérer l'adresse from de l'émetteur.

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

Exemple UDP : serveur d'écho

Principe:

Usage:

Le client


 
  1     /*
  2       client-echo.c

  3       Envoi de datagrammes

  4       Exemple de client qui 
  5       - ouvre un socket 
  6       - envoie des datagrammes sur ce socket (lignes de textes
  7       de l'entrée standard)
  8       - attend une réponse
  9       - affiche la réponse
 10       */

 11     #include <unistd.h>
 12     #include <sys/types.h>
 13     #include <sys/socket.h>
 14     #include <netinet/in.h>
 15     #include <signal.h>
 16     #include <stdio.h>
 17     #include <netdb.h>
 18     #include <string.h>
 19     #include <stdlib.h>

 20     #define TAILLE_TAMPON 1000
 21     static int fd;

 22     void ErreurFatale(char message[])
 23     {
 24             printf("CLIENT> Erreur fatale\n");
 25             perror(message);
 26             exit(EXIT_FAILURE);
 27     }

 28     int main(int argc, char *argv[])
 29     {
 30             struct sockaddr_in adresse_socket_serveur;
 31             struct hostent *hote;
 32             int taille_adresse_socket_serveur;
 33             char *nom_serveur;
 34             int numero_port_serveur;
 35             char *requete, reponse[TAILLE_TAMPON];
 36             int longueur_requete, longueur_reponse;

 37             /* 1. réception des paramètres de la ligne de commande */

 38             if (argc != 4) {
 39                     printf("Usage: %s hote port message\n", argv[0]);
 40                     exit(EXIT_FAILURE);
 41             };
 42             nom_serveur = argv[1];
 43             numero_port_serveur = atoi(argv[2]);
 44             requete = argv[3];

 45             /* 2. Initialisation du socket */

 46             /* 2.1 création du socket en mode datagramme */

 47             fd = socket(AF_INET, SOCK_DGRAM, 0);
 48             if (fd < 0)
 49                     ErreurFatale("Creation socket");

 50             /* 2.2 recherche de la machine serveur */

 51             hote = gethostbyname(nom_serveur);
 52             if (hote == NULL)
 53                     ErreurFatale("Recherche serveur");

 54             /* 2.3 Remplissage adresse serveur */

 55             adresse_socket_serveur.sin_family = AF_INET;
 56             adresse_socket_serveur.sin_port = htons(numero_port_serveur);
 57             adresse_socket_serveur.sin_addr =
 58                 *(struct in_addr *) hote->h_addr;

 59             taille_adresse_socket_serveur = sizeof adresse_socket_serveur;

 60             longueur_requete = strlen(requete) + 1;

 61             /* 3. Envoi de la requête */
 62             printf("REQUETE> %s\n", requete);
 63             if (sendto(fd, requete, longueur_requete, 0,    /* flags */
 64                        (struct sockaddr *) &adresse_socket_serveur,
 65                        taille_adresse_socket_serveur)
 66                 < 0)
 67                     ErreurFatale("Envoi requete");

 68             /* 4. Lecture de la réponse */
 69             longueur_reponse = recvfrom(fd,
 70                                         reponse,
 71                                         TAILLE_TAMPON, 0, NULL, 0);
 72             if (longueur_reponse < 0)
 73                     ErreurFatale("Attente réponse");
 74             printf("REPONSE> %s\n", reponse);
 75             close(fd);

 76             printf("CLIENT> Fin.\n");
 77             exit(0);
 78     }
 

Exercice : faire en sorte que le client réexpédie sa requête si il ne reçoit pas la réponse dans un délai fixé. Fixer une limite au nombre de tentatives.

Le serveur


 
  1     /*
  2       serveur-echo.c -  Réception de datagrammes

  3       Exemple de serveur qui
  4       - ouvre un socket sur un port en mode non-connecté
  5       - affiche les messages (chaînes de caractères)
  6       qu'il reçoit par ce socket.
  7       - envoie une réponse
  8       */

  9     #include <unistd.h>
 10     #include <sys/types.h>
 11     #include <sys/socket.h>
 12     #include <netinet/in.h>
 13     #include <signal.h>
 14     #include <stdio.h>
 15     #include <stdlib.h>
 16     #include <arpa/inet.h>
 17     #include <ctype.h>

 18     #define TAILLE_TAMPON 1000
 19     static int fd;

 20     void ErreurFatale(char message[])
 21     {
 22             printf("SERVEUR> Erreur fatale\n");
 23             perror(message);
 24             exit(EXIT_FAILURE);
 25     }

 26     void ArretServeur(int signal)
 27     {
 28             close(fd);
 29             printf("SERVEUR> Arrêt du serveur (signal %d)\n", signal);
 30             exit(EXIT_SUCCESS);
 31     }

 32     int main(int argc, char *argv[])
 33     {
 34             struct sockaddr_in adresse_serveur;
 35             size_t taille_adresse_serveur;
 36             int numero_port_serveur;
 37             char *src, *dst;
 38             struct sigaction a;

 39             /* 1. réception des paramètres de la ligne de commande */

 40             if (argc != 2) {
 41                     printf("usage: %s port\n", argv[0]);
 42                     exit(1);
 43             };
 44             numero_port_serveur = atoi(argv[1]);

 45             /* 2. Si une interruption se produit, arrêt du serveur */
 46             /* signal(SIGINT,ArretServeur); */

 47             a.sa_handler = ArretServeur;
 48             sigemptyset(&a.sa_mask);
 49             a.sa_flags = 0;
 50             sigaction(SIGINT, &a, NULL);

 51             /* 3. Initialisation du socket de réception */

 52             /* 3.1 Création du socket en mode non-connecté 
 53                (datagrammes) */

 54             fd = socket(AF_INET, SOCK_DGRAM, 0);
 55             if (fd < 0)
 56                     ErreurFatale("socket");

 57             /* 3.2 Remplissage de l'adresse de réception 
 58                (protocole Internet TCP-IP, réception acceptée sur toutes 
 59                les adresses IP du serveur, numéro de port indiqué)
 60              */

 61             adresse_serveur.sin_family = AF_INET;
 62             adresse_serveur.sin_addr.s_addr = INADDR_ANY;
 63             adresse_serveur.sin_port = htons(numero_port_serveur);

 64             /* 3.3 Association du socket au port de réception */

 65             taille_adresse_serveur = sizeof adresse_serveur;
 66             if (bind(fd,
 67                      (struct sockaddr *) &adresse_serveur,
 68                      taille_adresse_serveur) < 0)
 69                     ErreurFatale("bind");

 70             printf("SERVEUR> Le serveur écoute le port %d\n",
 71                    numero_port_serveur);

 72             while (1) {
 73                     struct sockaddr_in adresse_client;
 74                     int taille_adresse_client;
 75                     char tampon_requete[TAILLE_TAMPON],
 76                         tampon_reponse[TAILLE_TAMPON];
 77                     int lg_requete, lg_reponse;

 78                     /* 4. Attente d'un datagramme (requête) */

 79                     taille_adresse_client = sizeof(adresse_client);
 80                     lg_requete = recvfrom(fd, tampon_requete, TAILLE_TAMPON, 0,     /* flags */
 81                                           (struct sockaddr *)
 82                                           &adresse_client,
 83                                           (socklen_t *) &
 84                                           taille_adresse_client);
 85                     if (lg_requete < 0)
 86                             ErreurFatale("recfrom");

 87                     /* 5. Affichage message avec sa provenance et sa longueur */

 88                     printf("%s:%d [%d]\t: %s\n",
 89                            inet_ntoa(adresse_client.sin_addr),
 90                            ntohs(adresse_client.sin_port),
 91                            lg_requete, tampon_requete);

 92                     /* 6. Fabrication d'une réponse */

 93                     src = tampon_requete;
 94                     dst = tampon_reponse;
 95                     while ((*dst++ = toupper(*src++)) != '\0');
 96                     lg_reponse = strlen(tampon_reponse) + 1;

 97                     /* 7. Envoi de la réponse */

 98                     if (sendto(fd,
 99                                tampon_reponse,
100                                lg_reponse,
101                                0,
102                                (struct sockaddr *) &adresse_client,
103                                taille_adresse_client)
104                         < 0)
105                             ErreurFatale("Envoi de la réponse");
106             };
107     }
 

16.5 Communication par flots de données (TCP)

La création d'un socket pour TCP se fait ainsi

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

Programmation des clients TCP

Le socket d'un client TCP doit être relié (par connect()) à celui du serveur, et il est utilisé ensuite par des read() et des write(), ou des entrées-sorties de haut niveau fprintf(), fscanf(), etc. si on a défini des flots par fdopen().

Exemple : client web


 
  1     /* client-web.c */

  2     /* 
  3        Interrogation d'un serveur web 

  4        Usage:
  5        client-web serveur port adresse-document

  6        retourne le contenu du document d'adresse
  7        http://serveur:port/adresse-document

  8        Exemple:
  9        client-web www.info.prive 80 /index.html

 10        Fonctionnement:
 11        - ouverture d'une connexion TCP vers serveur:port
 12        - envoi de la requête   GET adresse-document HTTP/1.0[cr][lf][cr][lf]
 13        - affichage de la réponse
 14     */

 15     #include <unistd.h>
 16     #include <sys/types.h>
 17     #include <sys/socket.h>
 18     #include <netinet/in.h>
 19     #include <signal.h>
 20     #include <stdio.h>
 21     #include <netdb.h>
 22     #include <string.h>
 23     #include <stdlib.h>

 24     #define CRLF "\r\n"
 25     #define TAILLETAMPON 1000

 26     /* -- connexion vers un serveur TCP --------------------------- */
 27     int connexion_tcp(char nom_serveur[], int port_serveur)
 28     {
 29             struct sockaddr_in addr_serveur;
 30             struct hostent *serveur;
 31             int fd;

 32             fd = socket(AF_INET, SOCK_STREAM, 0);   /* création prise */
 33             if (fd < 0) {
 34                     perror("socket");
 35                     exit(EXIT_FAILURE);
 36             }

 37             serveur = gethostbyname(nom_serveur);   /* recherche adresse serveur */
 38             if (serveur == NULL) {
 39                     perror("gethostbyname");
 40                     exit(EXIT_FAILURE);
 41             }

 42             addr_serveur.sin_family = AF_INET;
 43             addr_serveur.sin_port = htons(port_serveur);
 44             addr_serveur.sin_addr = *(struct in_addr *) serveur->h_addr;

 45             if (connect(fd,         /* connexion au serveur */
 46                         (struct sockaddr *) &addr_serveur,
 47                         sizeof addr_serveur)
 48                 < 0) {
 49                     perror("connect");
 50                     exit(EXIT_FAILURE);
 51             }
 52             return (fd);
 53     }

 54     /* ------------------------------------------------------ */

 55     void demander_document(int fd, char adresse_document[])
 56     {
 57             char requete[TAILLETAMPON];
 58             int longueur;
 59             /* constitution de la requête, suivie d'une ligne vide */
 60             longueur = snprintf(requete, TAILLETAMPON,
 61                                 "GET %s HTTP/1.0" CRLF CRLF,
 62                                 adresse_document);
 63             write(fd, requete, longueur);   /* envoi */
 64     }

 65     /* ------------------------------------------------------ */

 66     void afficher_reponse(int fd)
 67     {
 68             char tampon[TAILLETAMPON];
 69             int longueur;
 70             while (1) {
 71                     longueur = read(fd, tampon, TAILLETAMPON);      /* lecture par bloc */
 72                     if (longueur <= 0)
 73                             break;
 74                     write(1, tampon, longueur);     /* copie sur sortie standard */
 75             };
 76     }

 77     /* -- main --------------------------------------------------- */

 78     int main(int argc, char *argv[])
 79     {
 80             char *nom_serveur, *adresse_document;
 81             int port_serveur;
 82             int fd;

 83             if (argc != 4) {
 84                     printf("Usage: %s serveur port adresse-document\n",
 85                            argv[0]);
 86                     exit(EXIT_FAILURE);
 87             };

 88             nom_serveur = argv[1];
 89             port_serveur = atoi(argv[2]);
 90             adresse_document = argv[3];

 91             fd = connexion_tcp(nom_serveur, port_serveur);

 92             demander_document(fd, adresse_document);
 93             afficher_reponse(fd);
 94             close(fd);

 95             exit(EXIT_SUCCESS);
 96     }
 

Remarque: souvent il est plus commode de créer des flots de haut niveau au dessus du socket (voir fdopen()) que de manipuler des read et des write. Voir dans l'exemple suivant.

Réaliser un serveur TCP

Un serveur TCP doit traiter des connexions venant de plusieurs clients.

Après avoir créé et nommé le socket, le serveur spécifie qu'il accepte les communications entrantes par listen(), et se met effectivement en attente d'une connexion de client par accept().

#include <sys/types.h>
#include <sys/socket.h> 

int listen(int s, int backlog);
int accept(int s,  struct  sockaddr  *addr,  
                   socklen_t  *addrlen);

Le paramètre backlog indique la taille maximale de la file des connexions en attente. Sous Linux la limite est donnée par la constante SOMAXCONN (qui vaut 128), sur d'autres systèmes elle est limitée à 5.

La fonction accept() renvoie un autre socket, qui servira à la communication avec le client. L'adresse du client peut être obtenue par les paramètres addr et addrlen.

En général les serveurs TCP doivent traiter plusieurs connexions simultanément. La solution habituelle est de lancer, après l'appel à accept() un processus fils (par fork())qui traite la communication avec un seul client. Ceci induit une gestion des processus, donc des signaux liés à la terminaison des processus fils.

16.6 Exemple TCP : un serveur Web

Dans ce qui suit nous présentons un serveur Web rudimentaire, capable de fournir des pages Web construites à partir des fichiers d'un répertoire. Nous donnons deux implémentations possibles, à l'aide de processus lourds et légers.

Serveur Web (avec processus)

Cette version suit l'approche traditionnelle. Pseudo-code:

ouvrir socket serveur (socket/bind/listen)
répéter indéfiniment
|    attendre l'arrivée d'un client (accept)
|    créer un processus (fork) et lui déléguer
|      la communication avec le client
fin-répeter


 
  1     /* ------------------------------------------------
  2        Serveur TCP

  3        serveur web, qui renvoie les fichiers (textes)
  4        d'un répertoire sous forme de pages HTML

  5       usage :  serveur-web port repertoire
  6       exemple: serveur-web 8000 /usr/doc/exemples
  7     --------------------------------------------------*/

  8     #include <unistd.h>
  9     #include <sys/types.h>
 10     #include <sys/errno.h>
 11     #include <sys/socket.h>
 12     #include <sys/wait.h>
 13     #include <sys/stat.h>
 14     #include <netinet/in.h>
 15     #include <arpa/inet.h>
 16     #include <signal.h>
 17     #include <stdio.h>
 18     #include <stdlib.h>

 19     #include "declarations.h"

 20     void fin_serveur(int sig);
 21     void fin_fils(int sig);

 22     int fdServeur;                  /* variable globale, pour partager
 23                                        avec traitement signal fin_serveur */

 24     void demarrer_serveur(int numero_port, char repertoire[])
 25     {
 26             int numero_client = 0;
 27             int fdClient;
 28             struct sigaction action_fin, action_fin_fils;

 29             fdServeur = serveur_tcp(numero_port);

 30             /* positionnement des signaux SIGINT et SIGCHLD */

 31             action_fin.sa_handler = fin_serveur;
 32             sigemptyset(&action_fin.sa_mask);
 33             action_fin.sa_flags = 0;
 34             sigaction(SIGINT, &action_fin, NULL);

 35             action_fin_fils.sa_handler = fin_fils;
 36             sigemptyset(&action_fin_fils.sa_mask);
 37             action_fin_fils.sa_flags = SA_NOCLDSTOP;
 38             sigaction(SIGCHLD, &action_fin_fils, NULL);

 39             printf("> Serveur " VERSION
 40                    " (port=%d, répertoire de documents=\"%s\")\n",
 41                    numero_port, repertoire);

 42             while (1) {
 43                     struct sockaddr_in a;
 44                     size_t l = sizeof a;

 45                     fdClient = attendre_client(fdServeur);
 46                     getsockname(fdClient, (struct sockaddr *) &a, &l);
 47                     numero_client++;
 48                     printf("> client %d [%s]\n", numero_client,
 49                            inet_ntoa(a.sin_addr));

 50                     if (fork() == 0) {
 51                             /* le processus fils ferme le socket serveur 
 52                                et s'occupe du client */
 53                             close(0);
 54                             close(1);
 55                             close(2);
 56                             close(fdServeur);
 57                             dialogue_client(fdClient, repertoire);
 58                             close(fdClient);
 59                             exit(EXIT_SUCCESS);
 60                     }
 61                     /* le processus père n'a plus besoin du socket client.
 62                        Il le ferme et repart dans la boucle */
 63                     close(fdClient);
 64             }
 65     }

 66     /* -------------------------------------------------------------
 67      Traitement des signaux
 68     --------------------------------------------------------------- */

 69     void fin_serveur(int sig)
 70     {
 71             printf("=> fin du serveur\n");
 72             shutdown(fdServeur, 2); /* utile ? */
 73             close(fdServeur);
 74             exit(EXIT_SUCCESS);
 75     }

 76     void fin_fils(int sig)
 77     {
 78             /* cette fonction est appelée chaque fois qu'un signal SIGCHLD 
 79                indique la fin d'un processus fils _au moins_.  */
 80             while (waitpid(-1, NULL, WNOHANG) > 0) {
 81                     /* attente des fils arrêtés, tant qu'il y en a */
 82             };
 83     }

 84     /* -------------------------------------------------------------*/
 85     void usage(char prog[])
 86     {
 87             printf("Usage : %s [options\n\n", prog);
 88             printf("Options :"
 89                    "-h\tcemessage\n"
 90                    "-p port\tport du serveur          [%d]\n"
 91                    "-d dir \trépertoire des documents [%s]\n",
 92                    PORT_PAR_DEFAUT, REPERTOIRE_PAR_DEFAUT);
 93     }

 94     /* -------------------------------------------------------------*/
 95     int main(int argc, char *argv[])
 96     {
 97             int port = PORT_PAR_DEFAUT;
 98             char *repertoire = REPERTOIRE_PAR_DEFAUT;       /* la racine 
 99                                                                des documents */
100             char c;

101             while ((c = getopt(argc, argv, "hp:d:")) != -1)
102                     switch (c) {
103                     case 'h':
104                             usage(argv[0]);
105                             exit(EXIT_SUCCESS);
106                             break;
107                     case 'p':
108                             port = atoi(optarg);
109                             break;
110                     case 'd':
111                             repertoire = optarg;
112                             break;
113                     case '?':
114                             fprintf(stderr,
115                                     "Option inconnue -%c. -h pour aide.\n",
116                                     optopt);
117                             break;
118                     };
119             demarrer_serveur(port, repertoire);
120             exit(EXIT_SUCCESS);
121     }
 

Serveur Web (avec threads)

Les processus légers permettent une autre approche : on crée préalablement un « pool » de processus que l'on bloque. Lorsqu'un client se présente, on confie la communication à un processus inoccupé.

ouvrir socket serveur (socket/bind/listen)
créer un pool de processus
répéter indéfiniment
|    attendre l'arrivée d'un client (accept)
|    trouver un processus libre, et lui
|        confier la communication avec le client
fin-répeter


 
  1     /* serveur-web.c */

  2     /* ------------------------------------------------
  3        Serveur TCP

  4        serveur web, qui renvoie les fichiers (textes)
  5        d'un répertoire sous forme de pages HTML

  6       usage :  serveur-web port repertoire
  7       exemple: serveur-web 8000 /usr/doc/exemples

  8       Version basée sur les threads. Au lieu de créer 
  9       un processus par connexion, on gère un pool de tâches
 10       - au démarrage du serveur les tâches sont créees,
 11       et bloquées par un verrou
 12       - quand un client se connecte, la connexion est
 13       confiée à une tâche inactive, qui est débloquée
 14       pour l'occasion.
 15     --------------------------------------------------*/

 16     #include <pthread.h>
 17     #include <unistd.h>
 18     #include <sys/types.h>
 19     #include <sys/errno.h>
 20     #include <sys/socket.h>
 21     #include <sys/wait.h>
 22     #include <sys/stat.h>
 23     #include <netinet/in.h>
 24     #include <arpa/inet.h>
 25     #include <signal.h>
 26     #include <stdio.h>
 27     #include <stdlib.h>

 28     #include "declarations.h"

 29     void fin_serveur(int sig);

 30     #define NB_TACHES 5

 31     struct tache {
 32             pthread_t id;           /* identificateur de thread  */
 33             pthread_mutex_t verrou;
 34             int actif;              /* 1 => le client est occupe */
 35             int fd;                 /* socket du client          */
 36             char *repertoire;
 37     };

 38     struct tache taches[NB_TACHES];
 39     int fdServeur;                  /* variable globale, pour partager
 40                                        avec traitement signal fin_serveur */

 41     /* ------------------------------------------------------- 
 42        travail_tache: contient le code de l'action 
 43        lancée pour chaque tâche
 44        ------------------------------------------------------- */
 45     void *travail_tache(void *data)
 46     {
 47             struct tache *t = data; /* les données spécifiques 
 48                                        de la tâche */
 49             while (1) {
 50                     pthread_mutex_lock(&t->verrou);
 51                     dialogue_client(t->fd, t->repertoire);
 52                     close(t->fd);
 53                     t->actif = 0;
 54             };
 55             return NULL;
 56     }

 57     /* ------------------------------------------------------- */
 58     void creer_taches(char repertoire[])
 59     {
 60             int k;

 61             for (k = 0; k < NB_TACHES; k++) {
 62                     struct tache *t = taches + k;
 63                     t->actif = 0;
 64                     t->repertoire = repertoire;
 65                     pthread_mutex_init(&t->verrou, NULL);
 66                     pthread_mutex_lock(&t->verrou);
 67                     pthread_create(&t->id, NULL, travail_tache,
 68                                    (void *) t);
 69             }
 70     }

 71     /* -----------------------------------------------------
 72        demarrer_serveur: crée le socket serveur
 73        et lance des processus pour chaque client
 74        ----------------------------------------------------- */

 75     int demarrer_serveur(int numero_port, char repertoire[])
 76     {
 77             int numero_client = 0;
 78             int fdClient;
 79             struct sigaction action_fin;

 80             printf("> Serveur " VERSION "+threads "
 81                    "(port=%d, répertoire de documents=\"%s\")\n",
 82                    numero_port, repertoire);

 83             /* positionnement du signaux SIGINT */

 84             action_fin.sa_handler = fin_serveur;
 85             sigemptyset(&action_fin.sa_mask);
 86             action_fin.sa_flags = 0;
 87             sigaction(SIGINT, &action_fin, NULL);

 88             /* création du socket serveur et du pool de tâches */
 89             fdServeur = serveur_tcp(numero_port);
 90             creer_taches(repertoire);

 91             /* boucle du serveur */
 92             while (1) {
 93                     struct sockaddr_in a;
 94                     size_t l = sizeof a;
 95                     int k;

 96                     fdClient = attendre_client(fdServeur);
 97                     getsockname(fdClient, (struct sockaddr *) &a, &l);
 98                     numero_client++;

 99                     /* recherche d'une tâche inoccupée */
100                     for (k = 0; k < NB_TACHES; k++)
101                             if (taches[k].actif == 0)
102                                     break;
103                     if (k == NB_TACHES) {   /* pas de tâche libre ? */
104                             printf
105                                 ("> client %d [%s] rejeté (surcharge)\n",
106                                  numero_client, inet_ntoa(a.sin_addr));
107                             close(fdClient);
108                     } else {
109                             /* affectation du travail et déblocage de la tâche */
110                             printf
111                                 ("> client %d [%s] traité par tâche %d\n",
112                                  numero_client, inet_ntoa(a.sin_addr), k);
113                             taches[k].fd = fdClient;
114                             taches[k].actif = 1;
115                             pthread_mutex_unlock(&taches[k].verrou);
116                     }
117             }
118     }

119     /* -------------------------------------------------------------
120      Traitement des signaux
121     --------------------------------------------------------------- */

122     void fin_serveur(int sig)
123     {
124             printf("=> fin du serveur\n");
125             shutdown(fdServeur, 2); /* utile ? */
126             close(fdServeur);
127             exit(EXIT_SUCCESS);
128     }

129     /* -------------------------------------------------------------*/
130     void usage(char prog[])
131     {
132             printf("Usage : %s [options\n\n", prog);
133             printf("Options :"
134                    "-h\tcemessage\n"
135                    "-p port\tport du serveur          [%d]\n"
136                    "-d dir \trépertoire des documents [%s]\n",
137                    PORT_PAR_DEFAUT, REPERTOIRE_PAR_DEFAUT);
138     }

139     /* -------------------------------------------------------------*/
140     int main(int argc, char *argv[])
141     {
142             int port = PORT_PAR_DEFAUT;
143             char *repertoire = REPERTOIRE_PAR_DEFAUT;       /* la racine 
144                                                                des documents */
145             char c;

146             while ((c = getopt(argc, argv, "hp:d:")) != -1)
147                     switch (c) {
148                     case 'h':
149                             usage(argv[0]);
150                             exit(EXIT_SUCCESS);
151                             break;
152                     case 'p':
153                             port = atoi(optarg);
154                             break;
155                     case 'd':
156                             repertoire = optarg;
157                             break;
158                     case '?':
159                             fprintf(stderr,
160                                     "Option inconnue -%c. -h pour aide.\n",
161                                     optopt);
162                             break;
163                     };
164             demarrer_serveur(port, repertoire);
165             exit(EXIT_SUCCESS);
166     }
 

Parties communes aux deux serveurs

Les déclarations de constantes et les entêtes des fonctions communes :


 
  1     /* Serveur Web - declarations.h */

  2     #define CRLF "\r\n"
  3     #define VERSION "MegaSoft 0.0.7 pour Unix"

  4     #define PORT_PAR_DEFAUT 8000
  5     #define REPERTOIRE_PAR_DEFAUT "/tmp"

  6     #define FATAL(err) { perror((char *) err); exit(1);}

  7     /* --  Prototypes des fonctions ----------------------- */

  8     extern void dialogue_client(int fdClient, char repertoire[]);
  9     extern void envoyer_document(FILE * out, char nom_document[],
 10                                  char repertoire[]);
 11     extern void document_non_trouve(FILE * out, char nom_document[]);
 12     extern void requete_invalide(FILE * out);
 13     extern int serveur_tcp(int numero_port);
 14     extern int attendre_client(int fdServeur);
 

les fonctions réseau :


 
  1     /*
  2       Projet serveur Web - reseau.c
  3       Fonctions réseau
  4     */

  5     #include <sys/types.h>
  6     #include <sys/errno.h>
  7     #include <sys/socket.h>
  8     #include <sys/wait.h>
  9     #include <sys/stat.h>
 10     #include <netinet/in.h>
 11     #include <signal.h>
 12     #include <stdio.h>
 13     #include <stdlib.h>

 14     #include "declarations.h"

 15     /* ------------------------------------------------------------
 16       Fonctions réseau
 17     ------------------------------------------------------------ */

 18     int serveur_tcp(int numero_port)
 19     {
 20             int fd;

 21             /* démarre un service TCP sur le port indiqué */

 22             struct sockaddr_in addr_serveur;
 23             size_t lg_addr_serveur = sizeof addr_serveur;

 24             /* création de la prise */
 25             fd = socket(AF_INET, SOCK_STREAM, 0);
 26             if (fd < 0)
 27                     FATAL("socket");

 28             /* nommage de la prise */
 29             addr_serveur.sin_family = AF_INET;
 30             addr_serveur.sin_addr.s_addr = INADDR_ANY;
 31             addr_serveur.sin_port = htons(numero_port);
 32             if (bind
 33                 (fd, (struct sockaddr *) &addr_serveur,
 34                  lg_addr_serveur) < 0)
 35                     FATAL("bind");

 36             /* ouverture du service */
 37             listen(fd, 4);
 38             return (fd);
 39     }

 40     int attendre_client(int fdServeur)
 41     {
 42             int fdClient;
 43             /* A cause des signaux SIGCHLD, la fonction accept()
 44                peut etre interrompue quand un fils se termine.
 45                Dans ce cas, on relance accept().
 46              */
 47             while ((fdClient = accept(fdServeur, NULL, NULL)) < 0) {
 48                     if (errno != EINTR)
 49                             FATAL("Fin anormale de accept().");
 50             };
 51             return (fdClient);
 52     }
 

les fonction de dialogue avec le client:


 
  1     /*
  2       traitement-client.c

  3       projet serveur WEB
  4       Communication avec un client
  5      */

  6     #include <stdio.h>
  7     #include <stdlib.h>
  8     #include <string.h>
  9     #include <unistd.h>

 10     #include "declarations.h"

 11     void dialogue_client(int fdClient, char repertoire[])
 12     {
 13             FILE *in, *out;
 14             char verbe[100], nom_document[100];
 15             int fd2;

 16             /* Ouverture de fichiers de haut niveau */
 17             in = fdopen(fdClient, "r");
 18             /* note : si on attache out au même descripteur que in,
 19                la fermeture de l'un entraine la fermeture de l'autre */
 20             fd2 = dup(fdClient);
 21             out = fdopen(fd2, "w");

 22             /* lecture de la requête, du genre
 23                "GET quelquechose ..." */
 24             fscanf(in, "%100s %100s", verbe, nom_document);
 25             fclose(in);

 26             if (strcmp(verbe, "GET") == 0)
 27                     envoyer_document(out, nom_document, repertoire);
 28             else
 29                     requete_invalide(out);
 30             fflush(out);            /* utile ? */
 31             fclose(out);
 32     }

 33     void envoyer_document(FILE * out, char nom_document[],
 34                           char repertoire[])
 35     {
 36             char nom_fichier[100];
 37             FILE *fichier;
 38             char ligne[100];

 39             sprintf(nom_fichier, "%s%s", repertoire, nom_document);
 40             if (strstr(nom_fichier, "/../") != NULL) {
 41                     /* tentative d'accès hors du répertoire ! */
 42                     document_non_trouve(out, nom_document);
 43                     return;
 44             };
 45             fichier = fopen(nom_fichier, "r");
 46             if (fichier == NULL) {
 47                     document_non_trouve(out, nom_document);
 48                     return;
 49             };

 50             fprintf(out,
 51                     "HTTP/1.1 200 OK" CRLF
 52                     "Server: " VERSION CRLF
 53                     "Content-Type: text/html; charset=iso-8859-1" CRLF
 54                     CRLF);

 55             fprintf(out,
 56                     "<html><head><title>Fichier %s</title></head>" 
 57                     "<body bgcolor=\"white\"><h1>Fichier %s</h1>" CRLF
 58                     "<center><table><tr><td bgcolor=\"yellow\"><listin
 59                     CRLF, nom_document, nom_fichier);

 60             /* le corps du fichier */
 61             while (fgets(ligne, 100, fichier) > 0) {
 62                     char *p;
 63                     for (p = ligne; *p != '\0'; p++) {
 64                             switch (*p) {
 65                             case '<':
 66                                     fputs("&lt;", out);
 67                                     break;
 68                             case '>':
 69                                     fputs("&gt;", out);
 70                                     break;
 71                             case '&':
 72                                     fputs("&amp;", out);
 73                                     break;
 74                             case '\n':
 75                                     fputs(CRLF, out);
 76                                     break;
 77                             default:
 78                                     fputc(*p, out);
 79                             };
 80                     };
 81             };

 82             /* balises de fin */
 83             fputs("</listing></table></center></body></html>" CRLF,
 84     }

 85     void document_non_trouve(FILE * out, char nom_document[])
 86     {
 87             /* envoi de la réponse : entête */
 88             fprintf(out,
 89                     "HTTP/1.1 404 Not Found" CRLF
 90                     "Server: MegaSoft 0.0.7 (CP/M)" CRLF
 91                     "Content-Type: text/html; charset=iso-8859-1" CRLF
 92                     CRLF);

 93             /* corps de la réponse */
 94             fprintf(out,
 95                     "<HTML><HEAD>" CRLF
 96                     "<TITLE>404 Not Found</TITLE>" CRLF
 97                     "</HEAD><BODY BGCOLOR=\"yellow\">" CRLF
 98                     "<H1>Pas trouvé !</H1>" CRLF
 99                     "Le document <font color=\"red\"><tt>%s</tt></font> "
100                     "demandé<br>n'est pas disponible.<P>" CRLF
101                     "<hr> Le webmaster"
102                     "</BODY></HTML>" CRLF, nom_document);
103             fflush(out);
104     }

105     void requete_invalide(FILE * out)
106     {
107             fprintf(out,
108                     "<HTML><HEAD>" CRLF
109                     "<TITLE>400 Bad Request</TITLE>" CRLF
110                     "</HEAD><BODY BGCOLOR=\"yellow\">" CRLF
111                     "<H1>Bad Request</H1>"
112                     "Vous avez envoyé une requête que "
113                     "ce serveur ne comprend pas." CRLF
114                     "<hr> Le webmaster" "</BODY></HTML>" CRLF);
115             fflush(out);
116     }
 

Exercice : modifier traitement-client pour qu'il traite le cas des répertoires. Il devra alors afficher le nom des objets de ce répertoire, leur type, leur taille et un lien.


Page suivante Page précédente Table des matières