wiki:IOC20_T05-0

Modèle client-serveur

Objectif de la séance

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

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.

Mise en œuvre du modèle client-serveur sur une application de vote

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.

  1. Vous devez exécuter l’exemple en vous aidant de la documentation. Je vous propopse de le commenter en vous aidant de la documentation.
  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.

  • 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.
  • 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é »
  • 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. vous pouvez connaitre votre adresse IP avec la commande hostname -I

Schéma de principe d'un échange client-serveur avec le protocole TCP (connecté)

client-serveur.png

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

Programmation d'un jeu

Le second exemple est un peu plus compliqué. Il s'agit de programmer un jeu dont les règles sont les suivantes :

  • Le jeu se joue avec au plus 4 joueurs.
  • Au départ, chaque joueur reçoit une carte tirée au hasard parmi les 100 cartes numérotées de 1 à 100.
  • Lorsque tous les joueurs sont servis, ils reçoivent un signal de démarrage.
  • Chaque joueur doit alors compter dans sa tête le nombre de secondes à partir du signal de démarrage et poser sa carte lorsque le nombre de secondes écoulées est égal à la valeur de sa carte.
  • Si les cartes sont posées dans l'ordre croissant, le tour est gagné. Notez que seul l'ordre est important.
  • Si le tour est gagné, chaque joueur reçoit une carte de plus au tour suivant et il devra déposer toutes ses cartes dans l'ordre.
  • Si le tour est perdu, on recommence le jeu avec 1 carte par joueur.

Remarques :

  • Le but est de gagner contre le jeu, il n'y a pas un gagnant parmi les joueurs.
  • Vous pouvez vous limiter à une carte par tour et compter le nombre de tour gagnant au début.
  • Vous pouvez vous limiter à 50 cartes pour réduire la durée d'un tour.

Pour ce jeu, vous allez devoir écrire 3 programmes :

  1. Le maitre du jeu qui
    • enregistre les joueurs,
    • leur distribue les cartes,
    • donne le signal de démarrage,
    • reçoit les cartes
    • et teste si l'ordre est correct pour déterminer si le tour est gagnant
  2. Le joueur aspect client qui
    • s'enregistre auprès du maitre du jeu
    • pose sa carte en envoyant un message lorsque le temps est écoulé
  3. Le joueur aspect serveur qui
    • reçoit une carte (un nombre) et c'est le signal de démarrage
    • reçoit l'information sur la réussite du tour.
Last modified 4 years ago Last modified on Feb 28, 2020, 7:41:58 AM