| 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 | |