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

4. Amusez vous avec le client/serveur

Par David Nelson

Hé! Vous voulez vous amuser ? Essayez le client/serveur. C'est comme parler à travers des pots de yaourt reliés par des ficelles et passer d'un coup à l'ère de l'informatique. Linux dispose de tous les outils dont vous pouvez avoir besoin. Vous utilisez déjà le client/serveur dans des applications telles que Netscape, telnet et ftp. Par ailleurs, il est aisé d'écrire vos propres applications client/serveur, dont certaines seront même utiles.

Le modèle client/serveur relie deux programmes différents (le client et le serveur) à travers un réseau. Pour vous exercer, vous pouvez même vous passer de réseau en faisant en sorte que Linux se parle à lui-même. Dès lors, continuez à lire même si vous n'êtes pas connectés à un réseau. (Mais votre installation Linux doit avoir été configurée pour le réseau.)

Une forme très courante du modèle client/serveur utilise les sockets BSD. BSD signifie Berkeley Software Distribution, une ancienne version d'Unix. Logiquement, une socket est la combinaison d'une adresse IP et d'un numéro de port. L'adresse IP définit l'ordinateur et le numéro de port détermine le canal de communication logique de cet ordinateur. (Dans ce cas, un port n'est pas un périphérique physique. Un périphérique physique, par exemple une carte Ethernet, peut accéder à tous les ports de l'ordinateur.)

Le Linux Journal a consacré une série bien faite d'articles en 3 volets sur la programmation réseau de Ivan Griffin et John Nelson dans les numéros de Février, Mars et Avril 1998. L'article de Février contient le code pour faire un modèle de couple client/seveur en utilisant des sockets BSD. Il contient tout ce qu'il faut pour démarrer. Vous pouvez le télécharger à SSC et ensuite utiliser cet article pour vous essayer à des choses plus ardues.

Après avoir téléchargé le fichier 2333.tgz, décompressez-le avec la commande tar -xzvf 2333.tgz. Renommez le fichier résultant 2333l1.txt en server.c, et le fichier 2333l2.txt en client.c. Éditez server.c pour effacer les caractères @cx depuis le début de la première ligne. Par ailleurs, soit vous effacez la dernière ligne, soit vous en faites un commentaire en l'insérant entre les caractères /* et */. Par ailleurs effacez la dernière ligne de client.c ou mettez la en commentaire.

Compilez server.c avec la commande gcc -o server server.c et client.c avec gcc -o client client.c.

Le serveur tourne sur l'ordinateur local : il n'a donc besoin que de son numéro de port pour définir une socket. Le client tourne sur n'importe quel ordinateur et doit donc connaître à la fois l'ordinateur serveur choisi et le numéro de port de celui-ci. Vous avez des milliers de numéros de port à utiliser. Veillez juste à ne pas utiliser un port qui est déjà utilisé. Votre fichier /etc/services liste la plupart des ports utilisés. Je trouve que le port 1024 est un bon candidat.

Bon, j'ai dit que vous n'aviez pas à être connectés à un réseau mais, pour essayer cela, vous devez avoir configuré votre ordinateur pour le réseau. En fait, ce code ne tournera pas chez moi si j'utilise le nom générique localhost. Je dois lui donner explicitement le nom de ma machine. Ainsi, en considérant que vous êtes prêts pour le réseau, lancez le serveur en tapant :


server 1024 &

et lancez ensuite le client en tapant :
client hostname 1024

hostname est le nom ou l'adresse IP de votre ordinateur. Si tout se passe bien, vous aurez une sortie semblable à ce qui suit :
Demande de connection de 192.168.1.1
14: Hello, World!

La première ligne donne l'adresse IP du client et la seconde est le message du serveur au client. En considérant tout le code qui est nécessaire, on pourrait faire passer cela au Concours De Programmes "Hello, World" Le Plus Compliqué Au Monde! Veuillez noter que le serveur continue à tourner en tâche de fond jusqu'à ce que vous le tuiez avec les commandes fg et ^C (ctrl-C).

4.1 Exemple de Requête-Réponse en Client/Serveur

Faisons maintenant quelque chose d'utile. Déboguer deux programmes à la fois n'est pas drôle, nous allons donc commencer simplement en simulant un couple de client/serveur en un seul programme. Quand vous comprendrez comment les choses marchent, nous pourront diviser le code entre le client et le serveur. Dans le programme suivant, le client est simulé par la fonction client. La routine principale simule quant à elle le serveur :


/* test local du code client-serveur */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char name[256] = "";
char buffer[256] = "";

void client(char *buffer)
{
        printf("%s", buffer);
        fgets(buffer, 256, stdin);
}

  int main(int argc, char *argv[])
{
  int year, age;

  sprintf(buffer, "Veuillez entrer votre nom: ");

  client(buffer);

  strcpy(name, buffer);
  sprintf(buffer, "Bonjour, %sVeuillez entrer votre année de naissance: ", name);

  client(buffer);

  year = atoi(buffer);
  age = 1998 - year;
  sprintf(buffer, "Votre âge approximatif est %d.\nTapez q pour quitter: ", age);

  client(buffer);

  return(0);
}

Nul besoin d'être un expert en C pour voir comment cela fonctionne. Le serveur simulé (main) envoie la chaîne "Veuillez entrer votre nom: " au client simulé (client) à travers un tampon de chaîne. Le client imprime la chaîne de caractères, lit le nom comme une chaîne en provenance du clavier et retourne la chaîne à travers le tampon. Le serveur demande alors l'année de naissance. Quand le client la récupère sous forme de chaîne de caractères, le serveur la convertit en nombre et la soustrait de 1998. Il renvoie alors l'âge approximatif résultant au client. C'est fini maintenant mais compte tenu du fait que le client attend une entrée clavier avant d'effectuer le retour de fonction, le serveur demande qu'un "q" soit tapé. Un codage plus sophistiqué pourrait éliminer cet inconvénient. Ce modèle client/serveur simulé illustre le passage de chaîne de caractères entre le serveur et le client, les requêtes et les réponses et l'utilisation d'arithmétique.

Copiez le code ci-dessus dans un éditeur et sauvez le en tant que localtest.c. Compilez le avec la commande gcc -o localtest localtest.c. Quand vous le lancerez, vous obtiendrez une sortie de ce genre :


Veuillez entrer votre nom: joe
Bonjour, joe
Veuillez entrer votre année de naissance: 1960
Votre âge approximatif est 38.
Tapez q pour quitter: q

Transformons maintenant cela en un vrai couple client/serveur. Insérez ces déclarations dans server.c en changeant les déclarations au début de main en :


int main(int argc, char *argv[])
{
int i, year, age;
char name[256] = "";
char buffer[256] = "";
char null_buffer[256] = "";
  int serverSocket = 0,

Le code spécifique à l'application se trouve vers la fin de server.c. Remplacez le par :


/*
* Le code spécifique à l'application serveur vient ici,
* i.e. effectuer une action, répondre au client, etc...
*/

sprintf(buffer, "Veuillez entrer votre nom:  ");
write(slaveSocket, buffer, strlen(buffer));
for (i = 0; i <= 255; i++) buffer[i] = 0;

/* récupérer le nom */
read(slaveSocket, buffer, sizeof(buffer));
strcpy(name, buffer);
sprintf(buffer, "Bonjour, %sVeuillez entrer votre année de naissance: ", name);
write(slaveSocket, buffer, strlen(buffer));
for (i = 0; i <= 255; i++) buffer[i] = 0;

/* récupérer l'année de naissance */
read(slaveSocket, buffer, sizeof(buffer));
year = atoi(buffer);
age = 1998 - year;
sprintf(buffer, "Votre âge approximatif est %d.\nTapez q pour quitter: ", age);
write(slaveSocket, buffer, strlen(buffer));

close(slaveSocket);
exit(0);

C'est à peu près le même code serveur que dans le cas du client/serveur à ceci près que nous lisons et écrivons dans slaveSocket plutôt que d'appeler la fonction client. Vous pouvez penser à slaveSocket comme à une connection à travers la socket entre le serveur et le client.

Le code client est très simple. Insérez les déclarations dans client.c en changeant les paramètres de main en :


int main(int argc, char *argv[])
{
  int i;
  int clientSocket,

Vous trouverez le code spécifique à l'application vers la fin de client.c et vous le remplacerez par :


/*
* Le code spécifique à l'application client vient ici
* i.e. recevoir des messages du serveur, répondre, etc...
* Recevoir et répondre jusqu'à ce que le serveur arrête d'envoyer des messages
*/

while (0 < (status = read(clientSocket, buffer, sizeof(buffer))))
   {
     printf("%s", buffer);
     for (i = 0; i <= 255; i++) buffer[i] = 0;
     fgets(buffer, 256, stdin);
     write(clientSocket, buffer, strlen(buffer));
   }
     close(clientSocket);
     return 0;
   }

Encore une fois, c'est presque le même code qui le code client que dans le modèle client/serveur simulé. Les principales différences sont dans l'utilisation dans clientSocket, l'autre bout de slaveSocket du serveur, et dans la boucle while pour le contrôle du programme. La boucle while ferme le client quand le serveur arrête d'envoyer des messages.

Recompilez server.c et client.c et lancez les comme avant. Cette fois, la sortie devrait être :


Demande de connection de 192.168.1.1
Veuillez entrer votre nom: joe
Bonjour, joe.
Veuillez entrer votre année de naissance: 1960
Votre âge approximatif est 38.
Tapez q pour quitter: q

Vous pouvez maintenant vous amuser : essayez de lancer de multiples sessions clientes qui appellent le même serveur et, si vous êtes sur un réseau, essayez de lancer le serveur sur un ordinateur différent du client. Le code du serveur est fait pour gérer de multiples requêtes simultanées en créant un processus pour chaque session cliente. Cela est fait par l'appel à fork dans server.c. Veuillez lire la page de man de fork pour plus d'informations.

4.2 Programme de chat en client/serveur

Comme dernier exemple, nous allons considérer un programme de chat pour envoyer des messages entre des utilisateurs. C'est primitif par ce que cela permet seulement une communication par alternance entre deux personnes et cela demande que le serveur garde une fenêtre ouverte. Mais cela montre comment un couple client/serveur peut supporter un dialogue illimité. Ce programme peut être étendu pour devenir un programme utile.

Insérez les déclarations dans server.c en changeant le début de main en :


int main(int argc, char *argv[])
{
   char buffer[256] = "";
   int i, serverquit = 1, clientquit = 1;
   int serverSocket = 0,

Remplacez le code spécifique à l'application à la fin de server.c par ce qui suit :


/*
* Le code spécifique à l'application serveur vient ici,
* i.e. effectuer une action, répondre à un client, etc...
*/

printf("Tapez q pour quitter.\n");
sprintf(buffer, "Bonjour, %s\nS: Veuillez lancer chat. Tapez q pour
quitter.\n", inet_ntoa(clientName.sin_addr));
write(slaveSocket, buffer, strlen(buffer));
for (i = 0; i <= 255; i++) buffer[i] = 0;

while (serverquit != 0 && clientquit != 0)
{
  status = 0;
  while (status == 0)
   status = read(slaveSocket, buffer, sizeof(buffer));
  clientquit = strcmp(buffer, "q\n");

  if (clientquit != 0)
  {
   printf("C: %s", buffer);
   for (i = 0; i <= 255; i++) buffer[i] = 0;

   printf("S: ");
   fgets(buffer, 256, stdin);
   serverquit   = strcmp(buffer, "q\n");
   write(slaveSocket, buffer, strlen(buffer));
   for (i = 0; i <= 255; i++) buffer[i] = 0;
  }
}
printf("Au revoir\n");
close(slaveSocket);
exit(0);

Insérez ce qui suit au début de client.c :


int main(int argc, char *argv[])
{
  int i, serverquit = 1, clientquit = 1;
  int clientSocket,

Remplacez le code spécifique à l'application à la fin de client.c par ce qui suit :


/*
* Le code spécifique au client vient ici
* i.e. recevoir des messages du serveur, répondre, etc...
*/

while (serverquit != 0 && clientquit != 0)
{
   status = 0;
   while (status == 0)
     status = read(clientSocket, buffer, sizeof(buffer));
   serverquit = strcmp(buffer, "q\n");

   if (serverquit != 0)
   {
       printf("S: %s", buffer);
       for (i = 0; i <= 255; i++) buffer[i] = 0;

       printf("C: ");
       fgets(buffer, 256, stdin);
       clientquit = strcmp(buffer, "q\n");
       write(clientSocket, buffer, strlen(buffer));
       for (i = 0; i <= 255; i++) buffer[i] = 0;
     }
  }
  printf("Goodbye\n");
  close(clientSocket);
  return 0;
}

Recompilez server.c et client.c et vous pouvez réessayer. Pour simuler deux ordinateurs en un, ouvrez deux fenêtres sous X ou utilisez deux consoles différentes (i.e. alt-1 et alt-2.) Lancez le serveur dans une fenêtre en utilisant la commande


server 1024

et le client dans une autre avec la commande

client hostname 1024

hostname est remplacé par votre nom hôte ou adresse IP.

Le code serveur et client pour ce programme de chat sont presque identiques et très proches de l'exemple précédent. Il y a deux différences majeures. La première est le test effectué pour voir si l'un des interlocuteurs a tapé "q" pour quitter. Les drapeaux serverquit et clientquit signalent ça. La seconde est la petite boucle attendant la réponse de l'autre côté. La fonction read retourne le nombre de caractères lus sur la socket, stocké dans status. Un nombre différent de zéro de caractères indique que l'autre côté a envoyé un message.

Voici un exemple de session vu du côté serveur :


Demande de connection de 192.168.1.1
Tapez q pour quitter.
C: Bonjour serveur
S: Bonjour client
C: Au revoir serveur
S: Au revoir client
Au revoir

Et la même vu du client :

S: Bonjour, 192.168.1.1
S: Veuillez lancer chat. Tapez q pour quitter.
C: Bonjour serveur
S: Bonjour client
C: Au revoir server
S: Au revoir client
C: q
Au revoir

J'espère que ces exemples ont montré combien il était facile de faire des applications client/serveur. Je souhaite que votre soif d'apprendre soit suffisante pour que vous essayez d'écrire vos propres applications. Si vous faîtes quelque chose de bien, faites en profiter les autres!


Copyright © 1998, David Nelson - Adaptation française de Pierre Tane


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