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

2. Installation du téléphone

Pour recevoir des appels téléphoniques, il nous faut installer le téléphone. De même, nous devons créer une socket qui attendra des connexions. Cela se fait en plusieurs étapes. En premier lieu, il faut créer une nouvelle socket, ce qui est analogue à l'installation d'une nouvelle ligne téléphonique; l'appel socket() est destiné à cette opération.

Comme ces sockets peuvent être de plusieurs types, vous devez préciser celui que vous désirez lors de leur création. L'une des options, c'est le format d'adressage: tout comme un service postal utilise une méthode d'adressage fort différente qu'une compagnie de téléphone pour trouver les correspondants, les sockets peuvent également employer plusieurs méthodes. Les plus connues et les plus utilisées sont AF_UNIX et AF_INET. L'adressage AF_UNIX utilise des chemins d'accès de fichiers pour identifier les sockets\,; c'est très pratique pour les communications entre processus fonctionnant sur une même machine. AF_INET, par contre, emploie des adresses de type Internet, qui sont composées de quatre octets, généralement notés sous forme décimale, séparés par des points (comme 192.9.200.10 par exemple). En plus de l'adresse de la machine destinataire, il lui est associé un numéro de port, qui permet d'avoir plusieurs sockets AF_INET sur chaque machine. Ici, nous ne parlerons que de l'adressage AF_INET, celui qui permet la communication entre systèmes distants sur un réseau.

Lors de la création des sockets, vous devez spécifier une seconde option, le type désiré: SOCK_STREAM et SOCK_DGRAM. Dans le premier, les données sont transmises comme un flux de caractères, alors que dans le second elles transitent par blocs, appelés datagrammes. Nous ne verrons ici que le plus courant et le plus souple, SOCK_STREAM.

Après la création, nous devons indiquer à notre socket quelle adresse écouter, exactement comme le numéro de téléphone que vous obtenez vous permettra de recevoir des appels. C'est le rôle de la fonction bind(), qui ``attache'' une socket à une adresse.

Les sockets de type SOCK_STREAM ont la possibilité de mettre les requêtes de connexion dans une queue, en attendant de pouvoir les traiter; exactement comme le service de ``signal d'appel'' du téléphone. Si vous êtes en train de traiter une connexion, toutes les autres requêtes attendront que vous puissiez vous en occuper. La fonction listen permet d'indiquer le nombre maximum de requêtes possibles dans la file d'attente (cinq, généralement), les autres se verront refuser la connexion. Bien qu'il ne soit pas nécessaire d'utiliser listen(), c'est malgré tout une bonne pratique, prenez-en l'habitude.

L'exemple suivant montre comment utiliser socket(), bind() et listen() afin de créer une socket qui pourra accepter des connexions.



/* Code permettant d'etablir une socket */

int establish(portnum)
u_short portnum;
{
        char   myname[MAXHOSTNAMELEN+1];
        int    s;
        struct sockaddr_in sa;
        struct hostent *hp;

        memset(&sa, 0, sizeof(struct sockaddr_in)); /* Nettoyage de notre adresse */
        gethostname(myname, MAXHOSTNAMELEN);            /* Qui sommes-nous            */
        hp = gethostbyname(myname);                     /* Recuperons notre adresse   */
        if (hp == NULL)                                 /* Nous n'existons-pas ???    */
                return(-1);
        sa.sin_family = hp->h_addrtype;                 /* Notre adresse              */
        sa.sin_port =   htons(portnum);                 /* Notre numero de port       */
        if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)  /* Creation de la socket      */
                return(-1);
        if (bind(s,&sa,sizeof(struct sockaddr_in)) < 0) {
                close(s);
                return(-1);                             /* Attache la socket au port  */
                }
        listen(s, 3);                                   /* Taille max de la queue     */
        return(s);
  }
 

Après cette création de socket permettant de recevoir des appels, vous devez répondre aux demandes de connexions. C'est le rôle de la fonction accept(), qui est analogue à l'action de décrocher le combiné lorsque le téléphone sonne. Cette fonction accept() va retourner une nouvelle socket, qui sera connectée à l'appelant.

La fonction suivante peut être utilisée pour accepter les connexions sur une socket créée par notre exemple précédent, establish().



int get_connection(s)
int s;                                  /* Socket creee par establish() */
{
        struct sockaddr_in isa;         /* Adresse de la  socket        */
        int i;                          /* Taille de l'adresse          */
        int t;                          /* Nouvelle socket connectee    */

        i = sizeof(struct sockaddr_in);
        if ((t = accept(s, &isa, &i)) < 0)   /* accepte si connexion    */
                return(-1);
        return(t);                      /* Retourne la nouvelle socket  */
}
 

Contrairement au téléphone, vous pouvez toujours accepter des connexions pendant que vous traitez les précédentes. Pour cette raison, il est de coutume d'effectuer le traitement après un fork. L'exemple qui suit montre comment utiliser establish et get_connection afin de permettre de multiples connexions:



#include <stdio.h>              /* En-tetes obligatoires pour les declarations */
#include <errno.h>      
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/param.h>


#define PORTNUM 50000 /* Il nous faut un numero de port. Pour l'instant, nous en */
                      /* prenons un totalement arbitraire.                       */

void fireman(), travaille();

main()
{
        int s, t;

        if ((s = establish(PORTNUM)) < 0) {            /* Branchons le telephone. */
                perror("establish");
                exit(1);
                }

        signal(SIGCHLD, fireman);                       /* Evite les zombies      */

        for (;;) {                                      /* Boucle d'attente       */
                if ((t = get_connection(s)) < 0) {      /* Realisation connexion  */
                        if (errno == EINTR) continue;   /* Essayons encore...     */
                        perror("accept");               /* Pas bon du tout.       */
                        exit(1);
                        }

                switch(fork()) {                /* Essayons de gerer la connexion */
                        case -1 :               /* Houla, grave. Arretons tout.   */
                                perror("fork");
                                close(s);                                       
                                close(t);
                                exit(1);
                        case 0 :                /* Nous sommes le fils; au travail*/
                                close(s);
                                travaille(t);
                                exit(0);
                        default :               /* Nous sommes le pere, donc nous */
                                close(t);       /* attendons d'autres connexions  */
                                continue;
                        }
                }
}


/* 
 *   Nous devons capturer le retour des fils qui sont tues, car sinon
 *   nous aurons des processus zombies. Cette fonction s'en occupe.
 */
     
void fireman()
{
        union wait wstatus;
        while(wait3(&wstatus, WNOHANG, NULL) > 0);
}

/*
 *   Voici la fonction qui utilise la socket. 
 *   Elle sera appelee apres l'etablissement de la connexion. 
 */

void travaille(s)
int s;
{
        /* Ici, faites ce que vous voulez avec votre socket.
        :
        :
        */
}
 

Bien. Vous savez maintenant comment créer une socket qui accepte les demandes de connexion. Mais comment l'appeler ? Pour passer un appel téléphonique, vous devez avoir le téléphone. Et bien, vous allez utiliser la fonction socket(), exactement comme pour l'établissement d'une socket qui doit attendre les connexions.

Mais vous utiliserez la fonction connect() pour essayer de vous ``brancher'' sur une socket qui attend des connexions; on dit qui ``écoute'' un port. La fonction suivante appelle un port particulier d'une certaine machine, qu'on lui passe en paramètres:



int call_socket(hostname, portnum)
char *hostname;
int portnum;
{
        struct sockaddr_in sa;
        struct hostent *hp;
        int a, s;

        if ((hp = gethostbyname(hostname)) == NULL) { /* Connaissons-nous l'adresse */
                errno = ECONNREFUSED;                 /* de  la machine ?           */
                return(-1);                           /* Non, introuvable.          */
                }

                                                      /* Initialisation de l'adresse*/
        bzero(&,sa, sizeof(sa));                   /* et du port a utiliser      */
        bcopy(hp->h_addr, (char *)&sa.sin_addr, hp->h_length); 
        sa.sin_family = hp->h_addrtype;
        sa.sin_port = htons((u_short)portnum);

        if ((s = socket(hp->h_addrtype, SOCK_STREAM, 0)) < 0)   /* creation socket */
                return(-1);
        if (connect(s, &sa, sizeof sa) < 0) {               /* puis connexion. */
                close(s);
                return(-1);
                }
        return(s);
}                                      
 

Cette fonction retourne une socket connectée, par laquelle les données peuvent alors transiter.


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