Changes between Initial Version and Version 1 of SujetTP4-2018


Ignore:
Timestamp:
Mar 2, 2018, 7:52:03 AM (7 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • SujetTP4-2018

    v1 v1  
     1= TP4 : Serveur WEB minimaliste pour la commande des LEDs et la lecture du bouton poussoir =
     2
     3== Objectif ==
     4
     5Le but de ce TME est de créer un site web consultable par un browser web permettant de changer l'état des leds. Le serveur web est écrit en Python, à la réception des requêtes du client, il exécute des scripts CGI (Common Gateway Interface) écrit également en Python pour produire des pages HTML dynamiques. Les scripts CGI devront communiquer avec le programme écrit en C contrôlant les LEDs. La communication entre les scripts et le programme de contrôle se fera par fifo UNIX. Le programme en C sera : soit la version permettant le contrôle depuis une application utilisateur (avec les droits de **root**), soit par une application utilisateur qui communique avec un driver. Je ne demande pas cette seconde possibilité parce que vous allez devoir insérer un module dans le noyau et c'est une difficulté supplémentaire qui n'apporte rien vis-à-vis de ce que vous allez voir aujourd'hui, mais je ne vous interdit pas de le faire.
     6
     7Pour ce faire, nous allons procéder en deux temps.
     81. Nous allons faire communiquer un programme python avec un programme C par FIFO.
     92. Nous allons créer un serveur local sur le PC de développement et le faire communiquer avec le programme C.
     103. Nous allons mettre le serveur sur une raspberry PI et communiquer avec le programme C
     114. Nous allons remplacer le programme C par le programme de contrôle des LEDs.
     12
     13== 1. Communication par FIFO ==
     14
     15Pour démarrer, vous allez récupérer une [attachment:writer_reader.tgz archive] constituer de 4 fichiers: 2 lecteurs et 2 écrivains.
     16Les deux lecteurs sont interchangeables, le premier est en C, le second en Python.
     17Les deux écrivains sont aussi interchangeables.
     18{{{
     19writer_reader
     20├── Makefile
     21├── reader.c    : lit une fifo et affiche le message reçu jusqu'à recevoir le message end
     22├── reader.py   : idem reader.c mais en python
     23├── writer.c    : écrit dans une fifo 5 fois et écrit le message end
     24└── writer.py   : idem writer.c mais en python
     25}}}
     26
     27Vous pouvez tester les programmes qui vous sont proposés.
     28Je vous demande de lire les codes en commençant par les programmes python, en répondant aux questions suivantes. Si vous ne connaissez pas le langage [https://python.developpez.com/cours python] c'est le moment de vous y mettre, mais ce n'est rédhibitoire pour aujourd'hui.
     29Ces questions ne sont pas exhaustives, l'idée c'est d'avoir une "''compréhension''" de ce qu’est dans le code (vous devrez utiliser Google pour ça).
     30
     31**writer.py**
     32- Dans quel répertoire est créée la fifo ?
     33- Quelle différence mkfifo et open ?
     34- Pourquoi tester que la fifo existe ?
     35- À quoi sert flush ?
     36- Pourquoi ne ferme-t-on pas la fifo ?
     37
     38{{{#!python
     39#!/usr/bin/env python
     40import os, time
     41
     42pipe_name = '/tmp/myfifo'
     43
     44if not os.path.exists(pipe_name):
     45    os.mkfifo(pipe_name)
     46
     47pipe_out = open(pipe_name,'w')
     48
     49i=0;
     50while i < 5:
     51    pipe_out.write("hello %d fois from python\n" % (i+1,))
     52    pipe_out.flush()
     53    time.sleep(1)
     54    i=i+1
     55
     56pipe_out.write("end\n")
     57}}}
     58
     59**reader.py**
     60- Que fait readline ?
     61
     62{{{#!python
     63#!/usr/bin/env python
     64import os, time
     65
     66pipe_name = '/tmp/myfifo'
     67
     68if not os.path.exists(pipe_name):
     69    os.mkfifo(pipe_name)
     70
     71pipe_in = open(pipe_name,'r')
     72while str != "end\n" :
     73    str = pipe_in.readline()
     74    print '%s' % str,
     75
     76}}}
     77Vous allez remarquer que lorsque le vous lancer un écrivain (en C ou en Pyhton) rien ne se passe tant que vous n'avez pas lancé un lecteur.
     78
     79- Expliquez le phénomène.
     80
     81== 2. Création d'un serveur fake ==
     82
     83Le but de cette première partie est de réaliser le programme suivant:
     84
     85[[Image(htdocs:png/fake2server.png,nolink,400px)]]
     86
     87- fake lit une valeur sur stdin et place la valeur lue dans une variable.
     88- Lorsque l'on tape plusieurs valeurs de suite la nouvelle valeur écrase l'ancienne.
     89- fake est toujours en fonctionnement.
     90- fake attends aussi un message de la fifo s2f.
     91- lorsqu'il reçoit un message, il l'affiche et il renvoie dans la fifo f2s la dernière valeur lue sur stdin.
     92
     93Vous commencez par récupérer l'[attachment:fake.tgz archive] qui donne un point de départ.
     94{{{
     95fake
     96├── Makefile
     97├── server.py
     98└── fake.c
     99}}}
     100
     101- Dans un premier terminal, compilez et démarrez fake.
     102- Dans un autre terminal, exécuter ./server.py
     103  - Le programme "server" Python est lancé et arrêté, il se comporte comme se comportera le script CGI.
     104- Quand le server python démarre,
     105  - il envoie un message sur la fifo s2f
     106  - puis il lit la fifo s2f et affiche le résultat.
     107
     108Vous devez :
     1091. modifier le nom des fifo pour éviter les conflits (changer p. ex. fw avec vos initiales)
     1101. modifier le select dans fake pour lire les deux fifos d'entrées stdin et s2f.
     1111. modifier server.py pour lire la valeur lue sur stdin.
     112
     113**fake.c**
     114{{{#!c
     115#include <fcntl.h>
     116#include <stdio.h>
     117#include <stdlib.h>
     118#include <sys/time.h>
     119#include <sys/stat.h>
     120#include <unistd.h>
     121#include <string.h>
     122
     123#define MAXServerResquest 1024
     124
     125int main()
     126{
     127    int     f2s, s2f;                                       // fifo file descriptors
     128    char    *f2sName = "/tmp/f2s_fw";                       // filo names
     129    char    *s2fName = "/tmp/s2f_fw";                       //
     130    char    serverRequest[MAXServerResquest];               // buffer for the request
     131    fd_set  rfds;                                           // flag for select
     132    struct  timeval tv;                                     // timeout
     133    tv.tv_sec = 1;                                          // 1 second
     134    tv.tv_usec = 0;                                         //
     135
     136    mkfifo(s2fName, 0666);                                  // fifo creation
     137    mkfifo(f2sName, 0666);
     138
     139    /* open both fifos */
     140    s2f = open(s2fName, O_RDWR);                            // fifo openning
     141    f2s = open(f2sName, O_RDWR);
     142
     143    do {
     144        FD_ZERO(&rfds);                                     // erase all flags
     145        FD_SET(s2f, &rfds);                                 // wait for s2f
     146
     147        if (select(s2f+1, &rfds, NULL, NULL, &tv) != 0) {   // wait until timeout
     148            if (FD_ISSET(s2f, &rfds)) {                     // something to read
     149                int nbchar;
     150                if ((nbchar = read(s2f, serverRequest, MAXServerResquest)) == 0) break;
     151                serverRequest[nbchar]=0;
     152                fprintf(stderr,"%s", serverRequest);
     153                write(f2s, serverRequest, nbchar);
     154            }
     155        }
     156    }
     157    while (1);
     158
     159    close(f2s);
     160    close(s2f);
     161
     162    return 0;
     163}
     164}}}
     165
     166**server.py**
     167{{{#!python
     168#!/usr/bin/env python
     169import os, time
     170
     171s2fName = '/tmp/s2f_fw'
     172if not os.path.exists(s2fName):
     173   os.mkfifo(s2fName)
     174s2f = open(s2fName,'w+')
     175
     176f2sName = '/tmp/f2s_fw'
     177if not os.path.exists(f2sName):
     178   os.mkfifo(f2sName)
     179f2s = open(f2sName,'r')
     180
     181s2f.write("w hello\n")
     182s2f.flush()
     183str = f2s.readline()
     184print '%s' % str,
     185
     186f2s.close()
     187s2f.close()
     188}}}
     189
     190== 3. Création d'un serveur web ==
     191
     192Nous allons maintenant créer le vrai server http.
     193Dans l'[attachment:server-fake.tgz archive] vous trouvez un squelette de server que vous allez d'abord tester avant de l'exécuter sur la carte !RaspberryPi pour la commande des leds.
     194{{{
     195server-fake
     196├── fake
     197│   ├── Makefile
     198│   └── fake.c
     199└── server
     200    ├── server.py
     201    └── www
     202        ├── cgi-bin
     203        │   ├── led.py
     204        │   └── main.py
     205        ├── img
     206        │   └── peri.png
     207        └── index.html
     208}}}
     209
     210**Pour tester le server http**
     211
     212- Dans un premier terminal, après l'avoir compilé, lancez le programme fake. Il s'agit du même programme fake.c que précédemment, qui reçoit une requête depuis une fifo s2f et qui renvoie une réponse.
     213  {{{
     214  cd fake
     215  make
     216  ./fake
     217  }}}
     218- Dans un second terminal, lancez le server.py. C'est un server http en python.
     219  {{{
     220  cd server/www
     221  ../server.py
     222  }}}
     223- Sur votre navigateur préféré, visualisez la page index.html à l'adresse `localhost:8X00` (ou `127.0.0.1:8X00`)
     224  - Vous devez voir apparaitre un logo et une case avec un bouton enter.
     225  - La page `index.html` contient deux "frames":
     226    - Le premier avec le logo.
     227    - Le second est contient la case et le bouton. Le code html de cette case est obtenu par l'exécution du programme Python `cgi-bin/main.py`.
     228      - Notez qu'il n'est pas très utile d'avoir produit cette page par un programme python, car la page n'est pas dynamique (son code est toujours le même) mais c'est pour donner la possibilité de la rendre dynamique.
     229  - Lorsque vous écrivez quelque chose dans la case, la page index.html demande l'exécution de script `cgi-bin/led.py`
     230  - le script `led.py` envoi le contenu de la case sur la fifo `s2f` attendue par `fake` et produit une page presque identique à main.py avec deux différences.
     231    - Elle affiche ce qui a été reçu de la fifo `f2s`
     232    - Elle est remplacée au bout d'une seconde par la page `main.py` grace à une commande `<META>`
     233
     234**server.py**
     235Le server écoute le port 8X00 et affiche la page index.htlm présente dans le répertoire wwww.
     236{{{
     237 X est une valeur entre 0 et 3, puisque nous allons avoir 4 serveurs HTTP par Raspberry.
     238}}}
     239 
     240{{{#!python
     241#!/usr/bin/env python
     242import BaseHTTPServer
     243import CGIHTTPServer
     244import cgitb; cgitb.enable()
     245
     246server = BaseHTTPServer.HTTPServer
     247handler = CGIHTTPServer.CGIHTTPRequestHandler
     248server_address = ("", 8X00)
     249handler.cgi_directories = ["/cgi-bin"]
     250
     251httpd = server(server_address, handler)
     252httpd.serve_forever()
     253}}}
     254
     255**index.html**
     256{{{
     257<html>
     258 <head><title>Peri Web Server</title></head>
     259 <frameset rows="100,*" frameborder=0>
     260  <frame src="img/peri.png">
     261  <frame src="cgi-bin/main.py">
     262 </frameset>
     263</html>
     264}}}
     265
     266**main.py**
     267{{{#!python
     268#!/usr/bin/env python
     269
     270html="""
     271<head>
     272  <title>Peri Web Server</title>
     273</head>
     274<body>
     275LEDS:<br/>
     276<form method="POST" action="led.py">
     277  <input name="val" cols="20"></input>
     278  <input type="submit" value="Entrer">
     279</form>
     280</body>
     281"""
     282
     283print html
     284}}}
     285
     286**led.py**
     287{{{#!python
     288#!/usr/bin/env python
     289import cgi, os, time,sys
     290form = cgi.FieldStorage()
     291val = form.getvalue('val')
     292
     293s2fName = '/tmp/s2f_fw'
     294f2sName = '/tmp/f2s_fw'
     295s2f = open(s2fName,'w+')
     296f2s = open(f2sName,'r',0)
     297
     298s2f.write("w %s\n" % val)
     299s2f.flush()
     300res = f2s.readline()
     301f2s.close()
     302s2f.close()
     303
     304html="""
     305<head>
     306  <title>Peri Web Server</title>
     307  <META HTTP-EQUIV="Refresh" CONTENT="1; URL=/cgi-bin/main.py">
     308</head>
     309<body>
     310LEDS:<br/>
     311<form method="POST" action="led.py">
     312  <input name="val" cols="20"></input>
     313  <input type="submit" value="Entrer">
     314  set %s
     315</form>
     316</body>
     317""" % (val,)
     318
     319print html
     320}}}
     321
     322**Pour l'exécution sur la carte !RaspberryPi**
     323- Vous devez copier tout `server-fake` sur la carte de votre choix. Vous ne pouvez pas être plus de deux binômes par carte.
     324- Vous lancez fake et le server http comme précédement.
     325- Sur votre navigateur, vous devez mettre une exception au proxy pour l'adresse du routeur des !RaspberryPi `132.227.102.36`.
     326- Si vous êtes sur la carte **20**, vous mettez comme URL 132.227.102.36:8**X20**. Vous faites de manière semblable pour les autres cartes.
     327- Si vous êtes le second binôme sur la carte *20*, l'URL est 132.227.102.36.8**120**, et dans le script `server.py` vous devez écouter le port **8100**.
     328- Le routeur des !RaspberryPi a été programmé pour renvoyer les requêtes http reçues sur le port 8xyy avec x=(0 ou 1) et yy=(20,21,...,26) sur le carte yy et le port 8X00.
     329
     330**Accès aux leds et au bouton poussoir par le serveur**
     331
     332- Modifier fake.c en ledbp.c et led.py pour commander les leds et lire le bouton poussoir
     333
     334  [[Image(htdocs:png/ledbp2server.png,nolink,400px)]]
     335
     336
     337T