2 Organisation du travail
2.1 Généralités
Il est fortement conseillé de suivre les étapes proposées ci-après pour réaliser le travail.
2.2 Organisation modulaire
Le projet est constitué de 4 bibliothèques et des programmes serveur
principal, client d'administration et serveur d'affichage.
Chacune de ces entités est développée dans un répertoire propre. On gère donc
les 7 répertoires suivants :
-
Socket -
- pour les sources de la bibliothèque contenant les fonctions
de gestion des sockets libsck,
- Threads -
- pour les sources de la bibliothèque contenant les fonctions
de gestion des threads libthrd,
- IPC -
- pour les sources de la bibliothèque contenant les fonctions de
gestion des communications inter-processus libipc,
- Webcam -
- pour les sources de la bibliothèque contenant les fonctions de gestion des webcams libcam,
- Demon -
- pour le programme du serveur et les modules de traitement des
commandes client et administrateur,
- Admin -
- pour le client d'administration,
- Affiche -
- pour le serveur d'affichage.
Cette arborescence et quelques squelettes de fichiers sont disponibles sous
forme d'un fichier au format tar et compressé à l'URL
http://www.plil.net/~rex/Enseignement/Systeme/Tutorat.GIS2.webcam/webcam.tgz.
Transférez ce fichier dans votre compte Polytech'Lille et décompressez-le avec
la commande tar xvzf webcam.tgz.
Le répertoire créé contient déjà un fichier Makefile avec deux cibles
all et clean. Créez les Makefile manquants dans les
sous-répertoire afin que, lorsque l'on lance make avec ces deux
cibles, tous les Makefile qui se trouvent dans les différents
répertoires soient exécutés.
On prévoira de pouvoir compiler les différents sources avec un drapeau
DEBUG (option -DDEBUG de gcc), permettant un affichage
conditionnel d'informations de déverminage des programmes.
2.3 Sockets et serveurs
Il s'agit dans cette étape de réaliser un serveur TCP basique à l'aide de
l'interface de programmation des sockets.
Écrivez dans le module libsck.c (répertoire Socket), les deux
fonctions suivantes :
-
int initialisationServeur(short int *); Cette fonction prend en
paramètre le port sur lequel il faut écouter et retourne la socket de
lecture.
- int boucleServeur(int, int (*)(int)); Cette fonction effectue
l'écoute sur la socket passée en premier argument et lors d'une
connexion, exécute la fonction passée en second argument. Cette
fonction passée en argument doit être une fonction qui prend une socket
en unique paramètre. Lors d'une connexion de client, la fonction
boucleServeur lance donc cette fonction avec la socket de dialogue
en paramètre.
Testez cette bibliothèque en écrivant un programme serveur.c (répertoire
Demon) qui l'utilise et dont la fonction de traitement des connexions
(celle appelée par boucleServeur) effectue juste une écriture de message
dans la socket et clôt la connexion.
Ce programme peut prendre des arguments : -p <port> ou --port <port>
pour spécifier un numéro de port différent de celui par défaut (port
4000). Pour traiter les arguments, utilisez la fonction
getopt_long (voir la page de manuel correspondante). Si les arguments
sont incorrects, on doit afficher un message qui précise la syntaxe.
Pour plus de clarté, l'analyse des arguments et l'affichage de la syntaxe
seront écrits dans des fonctions séparées.
Modifiez la fonction de traitement des connexions afin que le serveur affiche
le nom de la machine distante (sur la sortie standard) et qu'elle ne ferme la
connexion que si elle lit le mot-clé quitter sur la socket. Pour ceci,
écrivez dans libsck une fonction SocketVersNom qui prend une socket
en argument et renvoie le nom dans une chaîne de caractères. Vous utiliserez
les fonctions getpeername et gethostbyaddr (voir les pages de
manuel et le support du cours de réseau
http://www.plil.net/~rex/Enseignement/Reseau/Reseau.GIS2)
Testez votre serveur avec plusieurs telnet ou nc simultanés.
Conclusions ?
2.4 Un serveur d'affichage
Le problème constaté dans le paragraphe précédent est rédhibitoire pour le
serveur principal (serveur de webcams) mais pas pour le serveur d'affichage
qui ne doit en fait gèrer qu'un seul client.
Vous allez donc utiliser les fonctions de votre bibliothéque de sockets pour
écrire le serveur d'affichage. Ce programme peut prendre des arguments :
-p <port> ou --port <port> pour spécifier un numéro de port
différent de celui par défaut (port 5000).
Le fonctionnement du serveur d'affichage suit les grandes lignes suivantes :
-
attendre la connexion d'un client,
- attendre que le client lui fournisse la résolution des images à afficher
en ASCII sous la forme d'une ligne de la forme largeur hauteur,
- afficher une fenêtre X11 de la taille adéquate,
- lire une image sous un format RGB24 brut (avec un seul fread)
et l'afficher dans la fenêtre X11, passer à l'image suivante.
Le serveur d'affichage doit aussi se terminer lorsqu'une touche du clavier
est pressée avec la fenêtre d'affichage comme cible.
Un serveur X11 ne peut pas afficher directement les images au format RGB24
(où chaque pixel est défini par 3 octets codant respectivement le rouge,
le vert et le bleu). Pour compliquer les choses les serveurs X11 peuvent
utiliser une profondeur de 8,16 ou 24 bits par pixel et plusieurs modes
d'encodage des couleurs. Nous nous limiterons à l'encodage TrueColor.
Dans ce mode les couleurs sont encodées de la façon suivante :
-
Profondeur 8 bits
- : les 3 bits de poids fort codent le rouge, les
3 bits intermédiaires codent le vert et les deux bits de poids faible codent
le bleu.
- Profondeur 15 bits
- : le bit de poids fort est inutilisé et ensuite
les couleurs sont codées sur 5 bits.
- Profondeur 16 bits
- : les 5 bits de poids fort codent le rouge, les
6 bits intermédiaires codent le vert et les 5 bits de poids faible codent
le bleu.
- Profondeur 24 bits
- : chaque couleur est codée sur un octet et un
octet supplémentaire code la transparence (donc un pixel nécessite 32
bits).
Attention les octets doivent suivre l'ordre des octets de la machine. En particulier
si le processeur est un Intel l'ordre est inversé.
Pour tester votre serveur d'affichage vous pouvez lui envoyer les deux clichés
ci-dessous en utilisant la commande nc :
http://www.plil.net/~rex/Enseignement/Systeme/Tutorat.GIS2.webcam/cliche1.rgb24,
http://www.plil.net/~rex/Enseignement/Systeme/Tutorat.GIS2.webcam/cliche2.rgb24.
2.5 Un serveur principal à base de processus légers
Pour que votre serveur puisse accepter plusieurs clients simultanément, vous
allez lancer un processus léger (thread) par client. Pour cela, implémentez la
fonction publique de libthrd (répertoire Threads) :
void lanceClientLeger(int) ;
Cette fonction doit avoir comme action de lancer un thread dans le mode
détaché. Ce thread doit exécuter la fonction
void gestionClient(int) ;
Cette dernière est le point d'entrée pour la gestion d'un client du serveur
de webcam. Vous en avez déjà implanté une première version dans le serveur de
test. Vous devrez en écrire une version plus élaborée, pour plus de clareté,
ce serait une bonne idée de déplacer cette fonction du fichier serveur.c
vers le fichier gestionClient.c.
2.6 Structure de données du serveur
Les principales informations gérées par le serveur sont :
-
la table des webcams qui, pour chaque webcam donne : un champ indiquant
si la caméra est utilisable par les clients, la description de la caméra
(valide si la caméra est utilisable), le mot de passe pour visualisation
de la caméra par les clients (valide si la caméra est utilisable), la
liste des descripteurs de sockets des serveurs d'affichage recevant le
flux vidéo de la caméra et une structure permettant de stocker les
informations techniques de la caméra.
- la table des clients connectés qui, pour chaque client indique le nom de
la machine du client et le surnom de connexion du client,
- deux sémaphores d'exclusion mutuelle (de type pthread_mutex) pour
protéger les accès à chacune des tables et les sémaphores de contrôle
nécessaires au bon déroulement des opérations !
Définissez les structures de données correspondantes dans le fichier
entête serveur.h et écrivez les fonctions d'initialisation des tables
(ne vous préoccupez pas encore des sémaphores).
2.7 Analyse des opérations sur la structure de données
Analysez les opérations nécessaires sur les structures de données afin de
déterminer celles qui nécessitent l'utilisation de sémaphores.
Implantez dans votre bibliothèque libthrd.a les deux fonctions publiques :
void P(int) ;
void V(int) ;
Ces fonctions cachent totalement le fait que vous utilisez des verrous
d'exclusion mutelle pour threads POSIX. En particulier, les verrous sont
représentés par une constante.
En implantant dans le module cdesClient.c les fonctions nécessaires
à la gestion des clients et dans le module cdesAdmin.c les fonctions
nécessaires à la gestion du client d'administration vous prendrez soin
d'y ajouter les poses et levées de verrous nécessaires.
2.8 La gestion des clients
La fonction gestionClient crée une nouvelle entrée dans la table des
utilisateurs puis, boucle sur une écoute de la socket de dialogue, en attente
d'une requête du client.
Lorsqu'un client souhaite visualiser une caméra, le serveur effectue les
tâches suivantes :
-
vérification du mot de passe pour accès à la caméra,
- lancement d'un thread d'envoi du flux vidéo au serveur d'affichage
précisé par l'utilisateur (le thread est envoyé par la fonction
lanceThreadCamAffiche de la bibliothèque libthrd qui
utilise la fonction gestionCamEnvoiFlux définie dans le fichier
gestionCam.c du répertoire Demon).
Bien entendu ces tâches doivent être correctement protégées par des verrous
d'exclusion mutuelle. Une difficulté supplémentaire est de ne pas effectuer
de conversion d'image superflue. Deux webcams différentes n'enverront pas
des images dans le même format (les encodages des couleurs appelés "palettes"
peuvent être différents). Il faut donc convertir les images capturées dans
un format commun (ici RGB24). Vous prendrez soin de n'effectuer la conversion
qu'une seule fois quelque soit le nombre de serveurs d'affichage à alimenter.
Vous devez prévoir un mécanisme pour que l'envoi d'un flux vidéo puisse se
terminer sur ordre de l'administrateur. Une solution est de stocker le
descripteur de la socket vers le serveur d'affichage dans la table des
caméras, de fermer le descripteur à la demande de l'administrateur et de
faire en sorte que le thread d'envoi de flux vidéo se termine proprement
en cas de problème sur sa connexion vers le serveur d'affichage.
Dans le même ordre d'idée, prévoyez d'arrêter les flux vidéos d'un client
lorsqu'il met fin à sa connexion au serveur de webcams.
2.9 Le client d'administration
Il vous faut implanter le client d'administration et le thread chargé
de traiter les requêtes de l'administrateur dans le serveur.
Les commandes sont passées en argument au client d'administration (donc pas
d'interface textuelle, mais une analyse des arguments au moyen de
getopt_long). Ce processus communique avec le serveur au moyen
de 2 files de messages : une pour les commandes et une pour les réponses.
La file des commandes est créée par le serveur à l'initialisation. Un thread,
lancé par le serveur est chargé de scruter en permanence cette file. La file
des réponses est créée par le client d'administration.
La bibliothèque libipc contiendra toutes les fonctions permettant de
cacher le fait que la communication est implantée par IPC (selon le même
principe que celui utilisé pour les sémaphores).
La fonctionnalité la plus complexe à implanter est la configuration d'une
caméra. L'administrateur commence par ajouter une caméra dans la table
des caméras du serveur (en remplissant juste la structure des paramètres
techniques). Le serveur peut tenter à ce moment d'initialiser la webcam
pour vérifier son existence et la validité des paramètres fournis. Par
la suite l'administrateur va préciser les paramètres administratifs
(description et mot de passe). C'est à ce moment que le serveur va
lancer un thread pour faire du "streaming" sur la webcam (récupérer
constament des images pour que le capteur soit électroniquement
opérationnel).
Le thread sera lancé par la fonction lanceThreadCamCapture de la
bibliothèque libthrd qui utilise elle-même la fonction
gestionCamCapture définie dans le fichier gestionCam.c du
répertoire Demon. Pour que ce thread puisse se terminer lorsque
l'on supprime une caméra il peut être utile d'ajouter un drapeau d'arrêt
dans la structure des caméras définie dans serveur.h.
Lorsque qu'une caméra est supprimée, il ne faut pas seulement arrêter
le thread de "streaming" mais aussi tous les threads alimentant des
serveurs d'affichage avec des images de cette caméra.