wiki:IOC_T06

Version 6 (modified by franck, 5 years ago) (diff)

--

Modèle client-serveur et MQTT

Objectif de la séance

Le but de cette séance est d'expérimenter le modèle client-serveur et une évolution de ce modèle client-broker pour permettre l'échange d'informations entre les capteurs et la base au dessus du protocole WiFi?. MQTT est un protocole construit au dessus de TCP/IP. Dans un premier temps, vous allez commencer par mettre en œuvre le modèle clients-serveur en créant votre propre application sur ce modèle et, dans un second temps, vous utilisez MQTT.

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 :

  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.
  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. 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.
  • 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...
      • Sauf que si le serveur est une raspberry de la salle de TP, c'est différent parce que le routeur de la salle fait un redirection des messages reçus en fonction du port vers une raspberry parctulière.
      • Les cartes RaspberryPi3 sont numérotées N = 1 à 12 et le routeur est configuré pour reconnaître leur adresse MAC et leur attribuer l'adresse IP 192.168.1.40+N
      • Le routeur est configuré pour que les messages qu'il reçoit sur le port 8800+x soient envoyés vers le port 8000 de la RaspberryPi3 d'adresse IP 192.168.1.x
      • Dans ce cas :
        • Sur le server (RaspberryPI3 n°N) : ./server 8000
        • Sur votre PC : ./client peri 8840+N
          évidemment vous faîtes l'addition

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