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