Version 9 (modified by 23 months ago) (diff) | ,
---|
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 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...
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 ""