Modèle client-serveur
Objectif de la séance
Le but de cette séance est d'expérimenter le modèle client-serveur au dessus de TCP-IP au travers de la programmation d'un jeu. Le TP va se dérouler sur les PC de développement. Nous verrons plus tard deux autres applications du modèle client-serveur avec le serveur HTTP et le protocole MQTT qui permet l'échange d'informations entre des capteurs WIFI et une station de base suivant un modèle client-broker.
Pour aujourd'hui, vous allez programmer deux applications. La première met en œuvre N clients TCP et 1 serveur TCP. La seconde application mettra en œuvre N client-serveurs et 1 serveur.
Mise en œuvre du modèle client-serveur sur une application de vote
Pour présenter le modèle client-serveur, nous allons suivre un exemple que vous allez devoir comprendre et à partir duquel vous allez devoir créer une nouvelle applications.
- Vous devez exécuter l’exemple en vous aidant de la documentation. Je vous propopse de le commenter en vous aidant de la documentation.
- Vous allez créer une nouvelle application permettant de recueillir le vote de personne concernant le choix de l’heure d’hiver ou d’été. Vous avez deux programmes à écrire. Le premier permet de voter, c’est le client TCP. Le second permet de recueillir le vote, c’est le serveur TCP.
- L'application "client"
- Paramètres :
- L’adresse IP du serveur
- Le numéro de port de l’application serveur
- Le nom de la personne
- Le vote avec deux possibilités : été ou hiver
- Comportement attendu :
- Le client crée un socket, se connecte et envoi son vote au serveur.
- Le serveur lui renvoie un acquittement de son vote.
- Paramètres :
- L'application "serveur"
- Paramètres :
- Le numéro du port d’écoute.
- Comportement attendu :
- Le serveur un socket et écoute
- Lorsqu’un client se connecte, il vérifie que le client n’a pas encore voté
- Si ok, le serveur envoie le message « a voté »
- Si ko, le serveur envoie le message « erreur, vote déjà réalisé »
- Paramètres :
- Remarques sur le choix du port d'écoute
- Le port d'écoute est imposé par le serveur et client doit connaître ce numéro pour lui envoyer des messages.
- Si c'est un test local:
- Dans un terminal : ./server 32000
- Dans un autre terminal : ./client localhost 32000
- Si c'est un test distant:
- Il suffit de remplacer localhost par l'adresse IP du serveur. vous pouvez connaitre votre adresse IP avec la commande
hostname -I
- Il suffit de remplacer localhost par l'adresse IP du serveur. vous pouvez connaitre votre adresse IP avec la commande
Schéma de principe d'un échange client-serveur avec le protocole TCP (connecté)
Illustration dans un programme où le client envoie un message à un serveur (qui ne lui répond pas).
server.c :
/* A simple server in the internet domain using TCP The port number is passed as an argument */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
void error(const char *msg)
{
perror(msg);
exit(1);
}
int main(int argc, char *argv[])
{
int sockfd, newsockfd, portno;
socklen_t clilen;
char buffer[256];
struct sockaddr_in serv_addr, cli_addr;
int n;
if (argc < 2) {
fprintf(stderr, "ERROR, no port provided\n");
exit(1);
}
// 1) on crée la socket, SOCK_STREAM signifie TCP
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
error("ERROR opening socket");
// 2) on réclame au noyau l'utilisation du port passé en paramètre
// INADDR_ANY dit que la socket va être affectée à toutes les interfaces locales
bzero((char *) &serv_addr, sizeof(serv_addr));
portno = atoi(argv[1]);
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);
if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
error("ERROR on binding");
// On commence à écouter sur la socket. Le 5 est le nombre max
// de connexions pendantes
listen(sockfd, 5);
while (1) {
newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
if (newsockfd < 0)
error("ERROR on accept");
bzero(buffer, 256);
n = read(newsockfd, buffer, 255);
if (n < 0)
error("ERROR reading from socket");
printf("Received packet from %s:%d\nData: [%s]\n\n",
inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port),
buffer);
close(newsockfd);
}
close(sockfd);
return 0;
}
client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
void error(const char *msg)
{
perror(msg);
exit(0);
}
int main(int argc, char *argv[])
{
int sockfd, portno, n;
struct sockaddr_in serv_addr;
struct hostent *server;
char buffer[256];
// Le client doit connaitre l'adresse IP du serveur, et son numero de port
if (argc < 3) {
fprintf(stderr,"usage %s hostname port\n", argv[0]);
exit(0);
}
portno = atoi(argv[2]);
// 1) Création de la socket, INTERNET et TCP
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
error("ERROR opening socket");
server = gethostbyname(argv[1]);
if (server == NULL) {
fprintf(stderr,"ERROR, no such host\n");
exit(0);
}
// On donne toutes les infos sur le serveur
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length);
serv_addr.sin_port = htons(portno);
// On se connecte. L'OS local nous trouve un numéro de port, grâce auquel le serveur
// peut nous renvoyer des réponses, le \n permet de garantir que le message ne reste
// pas en instance dans un buffer d'emission chez l'emetteur (ici c'est le clent).
if (connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0)
error("ERROR connecting");
strcpy(buffer,"Coucou Peri\n");
n = write(sockfd,buffer,strlen(buffer));
if (n != strlen(buffer))
error("ERROR message not fully trasmetted");
// On ferme la socket
close(sockfd);
return 0;
}
Makefile
CFLAGS = -Wall -Werror
dep : server client usage
all : clean dep
clean :
-rm server client
usage:
@echo ""
@echo "Usage :"
@echo " On the server computer, start the server:"
@echo " ./server port"
@echo " On the client computer, start the client:"
@echo " ./client ipserver portserver"
@echo ""
Programmation d'un jeu
Le second exemple est un peu plus compliqué. Il s'agit de programmer un jeu dont les règles sont les suivantes :
- Le jeu se joue avec au plus 4 joueurs.
- Au départ, chaque joueur reçoit une carte tirée au hasard parmi les 100 cartes numérotées de 1 à 100.
- Lorsque tous les joueurs sont servis, ils reçoivent un signal de démarrage.
- Chaque joueur doit alors compter dans sa tête le nombre de secondes à partir du signal de démarrage et poser sa carte lorsque le nombre de secondes écoulées est égal à la valeur de sa carte.
- Si les cartes sont posées dans l'ordre croissant, le tour est gagné. Notez que seul l'ordre est important.
- Si le tour est gagné, chaque joueur reçoit une carte de plus au tour suivant et il devra déposer toutes ses cartes dans l'ordre.
- Si le tour est perdu, on recommence le jeu avec 1 carte par joueur.
Remarques :
- Le but est de gagner contre le jeu, il n'y a pas un gagnant parmi les joueurs.
- Vous pouvez vous limiter à une carte par tour et compter le nombre de tour gagnant au début.
- Vous pouvez vous limiter à 50 cartes pour réduire la durée d'un tour.
Pour ce jeu, vous allez devoir écrire 3 programmes :
- Le maitre du jeu qui
- enregistre les joueurs,
- leur distribue les cartes,
- donne le signal de démarrage,
- reçoit les cartes
- et teste si l'ordre est correct pour déterminer si le tour est gagnant
- Le joueur aspect client qui
- s'enregistre auprès du maitre du jeu
- pose sa carte en envoyant un message lorsque le temps est écoulé
- Le joueur aspect serveur qui
- reçoit une carte (un nombre) et c'est le signal de démarrage
- reçoit l'information sur la réussite du tour.
