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
etftp
. 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 commandetar -xzvf 2333.tgz
. Renommez le fichier résultant2333l1.txt
enserver.c
, et le fichier2333l2.txt
en. Éditez
client.c
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 declient.c
ou mettez la en commentaire.
Compilez
server.c
avec la commandegcc -o server server.c
etclient.c
avecgcc -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
où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 commandegcc -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 bouclewhile
ferme le client quand le serveur arrête d'envoyer des messages.Recompilez
server.c
etclient.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
dansserver.c
. Veuillez lire la page de man defork
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
etclient.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
et le client dans une autre avec la commande
server 1024
où
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
etclientquit
signalent ça. La seconde est la petite boucle attendant la réponse de l'autre côté. La fonctionread
retourne le nombre de caractères lus sur la socket, stocké dansstatus
. 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 :
Et la même vu du client :
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
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