|  | 1 | = Modèle client-serveur | 
                          |  | 2 |  | 
                          |  | 3 | == Objectif de la séance | 
                          |  | 4 |  | 
                          |  | 5 | 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. | 
                          |  | 6 | 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. | 
                          |  | 7 |  | 
                          |  | 8 | 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. | 
                          |  | 9 |  | 
                          |  | 10 | == Mise en œuvre du modèle client-serveur sur une application de vote | 
                          |  | 11 |  | 
                          |  | 12 | 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. | 
                          |  | 13 |  | 
                          |  | 14 | 1. Vous devez commenter l’exemple dans le code en vous aidant de la documentation. Le but de ces commentaires est de vous forcer à lire la documentation pour les quelques fonctions présentes dans l’exemple. | 
                          |  | 15 | 2. 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. | 
                          |  | 16 |  | 
                          |  | 17 | * **L'application "client" | 
                          |  | 18 | * Paramètres : | 
                          |  | 19 | * L’adresse IP du serveur | 
                          |  | 20 | * Le numéro de port de l’application serveur | 
                          |  | 21 | * Le nom de la personne | 
                          |  | 22 | * Le vote avec deux possibilités : été ou hiver | 
                          |  | 23 | * Comportement attendu : | 
                          |  | 24 | * Le client crée un socket, se connecte et envoi son vote au serveur. | 
                          |  | 25 | * Le serveur lui renvoie un acquittement de son vote. | 
                          |  | 26 |  | 
                          |  | 27 | * **L'application "serveur" | 
                          |  | 28 | * Paramètres : | 
                          |  | 29 | * Le numéro du port d’écoute. | 
                          |  | 30 | * Comportement attendu : | 
                          |  | 31 | * Le serveur un socket et écoute | 
                          |  | 32 | * Lorsqu’un client se connecte, il vérifie que le client n’a pas encore voté | 
                          |  | 33 | * Si ok, le serveur envoie le message « a voté » | 
                          |  | 34 | * Si ko, le serveur envoie le message « erreur, vote déjà réalisé » | 
                          |  | 35 |  | 
                          |  | 36 | * **Remarques sur le choix du port d'écoute | 
                          |  | 37 | * Le port d'écoute est imposé par le serveur et client doit connaître ce numéro pour lui envoyer des messages. | 
                          |  | 38 | * __Si c'est un test local__: | 
                          |  | 39 | * Dans un terminal : ./server 32000 | 
                          |  | 40 | * Dans un autre terminal : ./client localhost 32000 | 
                          |  | 41 | * __Si c'est un test distant__: | 
                          |  | 42 | * Il suffit de remplacer localhost par l'adresse IP du serveur. vous pouvez connaitre votre adresse IP avec la commande `hostname -I` | 
                          |  | 43 |  | 
                          |  | 44 | ** Schéma de principe d'un échange client-serveur avec le protocole TCP (connecté) | 
                          |  | 45 | [[Image(htdocs:png/client-serveur.png,300px,nolink)]] | 
                          |  | 46 |  | 
                          |  | 47 | Illustration dans un programme où le client envoie un message à un serveur (qui ne lui répond pas). | 
                          |  | 48 |  | 
                          |  | 49 | ** server.c : | 
                          |  | 50 | {{{ | 
                          |  | 51 | #!c | 
                          |  | 52 | /* A simple server in the internet domain using TCP The port number is passed as an argument */ | 
                          |  | 53 | #include <stdio.h> | 
                          |  | 54 | #include <stdlib.h> | 
                          |  | 55 | #include <string.h> | 
                          |  | 56 | #include <unistd.h> | 
                          |  | 57 | #include <sys/types.h> | 
                          |  | 58 | #include <sys/socket.h> | 
                          |  | 59 | #include <netinet/in.h> | 
                          |  | 60 |  | 
                          |  | 61 | #include <netdb.h> | 
                          |  | 62 | #include <arpa/inet.h> | 
                          |  | 63 |  | 
                          |  | 64 | void error(const char *msg) | 
                          |  | 65 | { | 
                          |  | 66 | perror(msg); | 
                          |  | 67 | exit(1); | 
                          |  | 68 | } | 
                          |  | 69 |  | 
                          |  | 70 | int main(int argc, char *argv[]) | 
                          |  | 71 | { | 
                          |  | 72 | int sockfd, newsockfd, portno; | 
                          |  | 73 | socklen_t clilen; | 
                          |  | 74 | char buffer[256]; | 
                          |  | 75 | struct sockaddr_in serv_addr, cli_addr; | 
                          |  | 76 | int n; | 
                          |  | 77 |  | 
                          |  | 78 | if (argc < 2) { | 
                          |  | 79 | fprintf(stderr, "ERROR, no port provided\n"); | 
                          |  | 80 | exit(1); | 
                          |  | 81 | } | 
                          |  | 82 |  | 
                          |  | 83 | // 1) on crée la socket, SOCK_STREAM signifie TCP | 
                          |  | 84 |  | 
                          |  | 85 | sockfd = socket(AF_INET, SOCK_STREAM, 0); | 
                          |  | 86 | if (sockfd < 0) | 
                          |  | 87 | error("ERROR opening socket"); | 
                          |  | 88 |  | 
                          |  | 89 | // 2) on réclame au noyau l'utilisation du port passé en paramètre | 
                          |  | 90 | // INADDR_ANY dit que la socket va être affectée à toutes les interfaces locales | 
                          |  | 91 |  | 
                          |  | 92 | bzero((char *) &serv_addr, sizeof(serv_addr)); | 
                          |  | 93 | portno = atoi(argv[1]); | 
                          |  | 94 | serv_addr.sin_family = AF_INET; | 
                          |  | 95 | serv_addr.sin_addr.s_addr = INADDR_ANY; | 
                          |  | 96 | serv_addr.sin_port = htons(portno); | 
                          |  | 97 | if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) | 
                          |  | 98 | error("ERROR on binding"); | 
                          |  | 99 |  | 
                          |  | 100 |  | 
                          |  | 101 | // On commence à écouter sur la socket. Le 5 est le nombre max | 
                          |  | 102 | // de connexions pendantes | 
                          |  | 103 |  | 
                          |  | 104 | listen(sockfd, 5); | 
                          |  | 105 | while (1) { | 
                          |  | 106 | newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); | 
                          |  | 107 | if (newsockfd < 0) | 
                          |  | 108 | error("ERROR on accept"); | 
                          |  | 109 |  | 
                          |  | 110 | bzero(buffer, 256); | 
                          |  | 111 | n = read(newsockfd, buffer, 255); | 
                          |  | 112 | if (n < 0) | 
                          |  | 113 | error("ERROR reading from socket"); | 
                          |  | 114 |  | 
                          |  | 115 | printf("Received packet from %s:%d\nData: [%s]\n\n", | 
                          |  | 116 | inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), | 
                          |  | 117 | buffer); | 
                          |  | 118 |  | 
                          |  | 119 | close(newsockfd); | 
                          |  | 120 | } | 
                          |  | 121 |  | 
                          |  | 122 | close(sockfd); | 
                          |  | 123 | return 0; | 
                          |  | 124 | } | 
                          |  | 125 | }}} | 
                          |  | 126 |  | 
                          |  | 127 | ** client.c | 
                          |  | 128 | {{{ | 
                          |  | 129 | #!c | 
                          |  | 130 | #include <stdio.h> | 
                          |  | 131 | #include <stdlib.h> | 
                          |  | 132 | #include <unistd.h> | 
                          |  | 133 | #include <string.h> | 
                          |  | 134 | #include <sys/types.h> | 
                          |  | 135 | #include <sys/socket.h> | 
                          |  | 136 | #include <netinet/in.h> | 
                          |  | 137 | #include <netdb.h> | 
                          |  | 138 |  | 
                          |  | 139 | void error(const char *msg) | 
                          |  | 140 | { | 
                          |  | 141 | perror(msg); | 
                          |  | 142 | exit(0); | 
                          |  | 143 | } | 
                          |  | 144 |  | 
                          |  | 145 | int main(int argc, char *argv[]) | 
                          |  | 146 | { | 
                          |  | 147 | int sockfd, portno, n; | 
                          |  | 148 | struct sockaddr_in serv_addr; | 
                          |  | 149 | struct hostent *server; | 
                          |  | 150 |  | 
                          |  | 151 | char buffer[256]; | 
                          |  | 152 |  | 
                          |  | 153 | // Le client doit connaitre l'adresse IP du serveur, et son numero de port | 
                          |  | 154 | if (argc < 3) { | 
                          |  | 155 | fprintf(stderr,"usage %s hostname port\n", argv[0]); | 
                          |  | 156 | exit(0); | 
                          |  | 157 | } | 
                          |  | 158 | portno = atoi(argv[2]); | 
                          |  | 159 |  | 
                          |  | 160 | // 1) Création de la socket, INTERNET et TCP | 
                          |  | 161 |  | 
                          |  | 162 | sockfd = socket(AF_INET, SOCK_STREAM, 0); | 
                          |  | 163 | if (sockfd < 0) | 
                          |  | 164 | error("ERROR opening socket"); | 
                          |  | 165 |  | 
                          |  | 166 | server = gethostbyname(argv[1]); | 
                          |  | 167 | if (server == NULL) { | 
                          |  | 168 | fprintf(stderr,"ERROR, no such host\n"); | 
                          |  | 169 | exit(0); | 
                          |  | 170 | } | 
                          |  | 171 |  | 
                          |  | 172 | // On donne toutes les infos sur le serveur | 
                          |  | 173 |  | 
                          |  | 174 | bzero((char *) &serv_addr, sizeof(serv_addr)); | 
                          |  | 175 | serv_addr.sin_family = AF_INET; | 
                          |  | 176 | bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length); | 
                          |  | 177 | serv_addr.sin_port = htons(portno); | 
                          |  | 178 |  | 
                          |  | 179 | // On se connecte. L'OS local nous trouve un numéro de port, grâce auquel le serveur | 
                          |  | 180 | // peut nous renvoyer des réponses, le \n permet de garantir que le message ne reste | 
                          |  | 181 | // pas en instance dans un buffer d'emission chez l'emetteur (ici c'est le clent). | 
                          |  | 182 |  | 
                          |  | 183 | if (connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0) | 
                          |  | 184 | error("ERROR connecting"); | 
                          |  | 185 |  | 
                          |  | 186 | strcpy(buffer,"Coucou Peri\n"); | 
                          |  | 187 | n = write(sockfd,buffer,strlen(buffer)); | 
                          |  | 188 | if (n != strlen(buffer)) | 
                          |  | 189 | error("ERROR message not fully trasmetted"); | 
                          |  | 190 |  | 
                          |  | 191 | // On ferme la socket | 
                          |  | 192 |  | 
                          |  | 193 | close(sockfd); | 
                          |  | 194 | return 0; | 
                          |  | 195 | } | 
                          |  | 196 | }}} | 
                          |  | 197 |  | 
                          |  | 198 | ** Makefile | 
                          |  | 199 |  | 
                          |  | 200 | {{{ | 
                          |  | 201 | #!make | 
                          |  | 202 | CFLAGS = -Wall -Werror | 
                          |  | 203 | dep : server client usage | 
                          |  | 204 | all : clean dep | 
                          |  | 205 | clean : | 
                          |  | 206 | -rm server client | 
                          |  | 207 | usage: | 
                          |  | 208 | @echo "" | 
                          |  | 209 | @echo "Usage :" | 
                          |  | 210 | @echo "    On the server computer, start the server:" | 
                          |  | 211 | @echo "       ./server port" | 
                          |  | 212 | @echo "    On the client computer, start the client:" | 
                          |  | 213 | @echo "       ./client ipserver portserver" | 
                          |  | 214 | @echo "" | 
                          |  | 215 | }}} | 
                          |  | 216 |  | 
                          |  | 217 | == Programmation d'un jeu | 
                          |  | 218 |  | 
                          |  | 219 |  |