2 Organisation du travail
2.1 Généralités
Afin de faciliter votre travail, 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 2 bibliothèques et du programme serveur.
Chacune de ces entités est développée dans un répertoire propre. On
gère donc les 3 répertoires suivants :
-
Socket -
- pour les sources permettant de générer une bibliothèque C
libsck,
- Threads -
- pour la bibliothèque libthrd,
- Demon -
- pour le programme du serveur et les modules de traitement des
commandes client et la gestion des ventes.
Cette arborescence et les sources sont disponible sous forme d'un fichier
au format tar et compressé à l'URL
http://www.plil.net/~rex/Enseignement/Systeme/Tutorat.IMA2i.encheres/encheres.tgz.
Transférez ce fichier dans votre compte Polytech'Lille et décompressez-le
avec la commande tar xvzf encheres.tgz.
Le répertoire créé contient un fichier Makefile qui est incomplet. Notamment,
la cible all et la variable DIRS sont vides. Complétez afin que,
lorsque l'on lance make, 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 serveur
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, void (*)(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 et son type de retour doit être void. Lors
d'une connexion, la fonction boucleServeur lance donc cette
fonction avec la socket de connexion en paramètre.
Testez cette bibliothèque en écrivant un programme serveur (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.IMA2i)
Testez votre serveur avec plusieurs telnet ou nc simultanés.
Conclusions ?
2.4 Un serveur à 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, définie dans servConf.c, est le point d'entrée pour
la gestion d'un client du serveur de ventes. Elle sera implantée plus
tard (sous-section 2.7).
2.5 Structure de données du serveur
Les principales informations gérées par le serveur sont :
-
la table des produits qui, pour chaque produit, indique : sa
description, si le produit est libre, valide ou invalide, son
prix initial, son prix actuel (qui correspond à la
dernière enchère), le vendeur, le dernier enchérisseur, la liste
des clients participant aux enchères pour ce produit et enfin un
descripteur de tube vers le thread vente associé au produit,
- la table des clients connectés qui, pour chaque client, indique le
descripteur socket associé au client, le nom de la machine du
client et le nom de connexion du client.
Écrivez les fonctions d'initialisation des tables (ne vous préoccupez pas
encore des sémaphores).
2.6 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.
Ajoutez dans le module gestionClient.c, les poses et levées de verrous
nécessaires.
2.7 La gestion des clients
La fonction gestionClient crée une nouvelle entrée dans la table des
utilisateurs puis, boucle sur une écoute sur la socket de dialogue, en attente
d'une requête du client.
Lorsque le client met un produit en vente, une nouvelle entrée dans la
table des produits est créée ; le produit ainsi ajouté est dans un
premier temps invalide. Le thread ``client'' doit alors lancer un thread
``vente'', chargé de gérer les enchères pour ce produit. Le produit ne
deviendra valide que lorsque le thread vente aura été lancé. Pour
cela, implémentez la fonction publique de libthrd (répertoire Threads) :
void lanceVenteLeger(int) ;
Cette fonction doit avoir comme action de lancer un thread dans le mode
détaché. Ce thread doit exécuter la fonction
void gestionVente(int) ;
Cette dernière, définie dans serveur.c, est le point d'entrée pour la
gestion d'une vente d'un produit.
Lorsque le client propose une enchère pour un produit, le thread
client vérifie la validité de l'enchère et l'écrit sur le tube du
thread vente correspondant.
2.8 La gestion des ventes
Un thread ``vente'' gère les enchères pour un produit, la diffusion des
messages d'alerte et adjuge la vente. Il doit donc gérer un minuteur
(timeout) entre deux enchères successives. Les enchères effectuées par
un client sont lues par le thread ``vente'' sur le tube qui lui est associé.
Pour permettre une attente sur le tube avec minuteur, on utilisera la fonction
select (voir la page de manuel). Si le minuteur expire entre deux enchères,
le thread ``vente'' diffuse alors à l'ensemble des clients participant à la
vente, un message d'alerte (1 fois, 2 fois, 3 fois) ou un message "adjugé".
Lorsqu'une vente est adjugée, le produit est supprimé de la table des produits.