Modèle client-serveur
Objectif de la séance
Le but de cette séance est d'expérimenter le modèle client-serveur. On ne va pas encore utiliser MQTT. On le fera à la prochaine séance entre les raspberrypi 3 et les esp32.
Mise en œuvre du modèle client-serveur sur une application de vote
L’apprentissage du modèle client-serveur va se faire en étant guidé par un exemple que vous allez devoir comprendre et à partir duquel vous allez devoir créer une nouvelle application. Il y a donc deux temps :
- Vous devez commenter l’exemple dans le code en trouvant la documentation. Le but de ces commentaires est de vous forcer à lire la documentation pour les quelques fonctions présentes dans l’exemple.
- 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. Le second permet de recueillir le vote, c’est le serveur.
- 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 envoie 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...
Dans un premier temps, vous pouvez tout faire en local en étant à la fois le serveur et les clients. Dans un second temps, vous pouvez vous associer à d'autres binôme et ne pas avoir le serveur et les clients sur la même machine. Pour cela, vous devez connaitre l'adresse IP de votre machine.
Si vous le souhaitez, vous pouvez enregistrer les votes dans une base de données ou dans un fichier, avec le format que vous décidez.
Racontez tout ça dans votre compte-rendu.
Schéma de principe d'un échange client-serveur avec le protocole TCP (connecté)
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 ""