Previous Contents Next

2   Méthodologie de développement

Afin de faciliter votre travail, il est fortement conseillé de suivre les étapes proposées ci-après pour réaliser le travail.

2.1   Organisation modulaire

Le projet est initialement 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 requêtes des joueurs.
Cette arborescence et les sources sont disponibles sous forme d'un fichier au format tar et compressé à l'URL http://rex.plil.fr/Enseignement/Systeme/Tutorat.GIS4.rapido/rapido.tgz. Transférez ce fichier dans votre compte Polytech'Lille et décompressez-le avec la commande tar xvzf rapido.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.2   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. Ecrivez dans le module libsck.c (répertoire Socket), les deux fonctions suivantes : 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 <numPort> ou --port <numPort> 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://rex.plil.fr/Enseignement/Reseau/Reseau.GIS4). Testez votre serveur avec plusieurs nc simultanés. Conclusion ?

2.3   Un serveur à base de processus légers

Pour que votre serveur puisse accepter plusieurs joueurs simultanément, vous allez lancer un processus léger (thread) par joueur. Pour cela, implémentez la fonction publique de libthrd (répertoire Threads) :
void creerThreadJoueur(int) ;
Cette fonction doit avoir comme action de lancer un thread dans le mode détaché. Ce thread doit exécuter la fonction :
void gestionJoueur(int) ;
Cette dernière, définie dans gestionJoueur.c (répertoire Demon), est le point d'entrée pour la gestion d'un client du serveur de jeu. Elle sera implantée plus tard (sous-section 2.7). Pour les tests vous pouvez la remplir avec le code de la fonction de traitement des connexions de la sous-section précedente.

2.4   Structures de données du serveur

Le serveur gère principalement trois tables, dont le contenu est décrit ci-dessous :
Table des tirages :
chaque entrée de la table contient des informations concernant un tirage donné, notamment : le numéro du tirage, les numéros tirés dans la grille A, le numéro tiré dans la grille B.
Table des joueurs :
cette table indique pour chaque joueur connecté : le descripteur socket qui lui est associé, le nom de sa machine et son nom de connexion.
Table des reçus :
chaque reçu correspondant à une prise de jeu est décrit dans cette table par : son identifiant (composé de la date du jour et un numéro de séquence), le type du reçu (bulletin ou Flash), le nombre de tirages pour lesquels le reçu est valable, le numéro de début de ces tirages, les numéros choisis/tirés dans la grille A, les numéros choisis/ tirés dans la grille B, et la mise par tirage.
Ecrivez les fonctions d'initialisation des tables (ne vous préoccupez pas encore des sémaphores).

2.5   Synchronisation des opérations sur les structures 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 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 gestionJoueur.c, les poses et levées de verrous nécessaires.

2.6   Gestion des tirages

Les tirages sont effectués dans le serveur par un processus léger. Implémentez la fonction publique suivante de la bibliothèque libthrd (Répertoire Threads).
void creerThreadTirages(int);
Cette fonction permettra de lancer un thread dans le mode détaché. Ce thread aura comme tâche d'exécuter la fonction :
void gestionTirages(int);
Cette fonction exécute une boucle, à chaque itération elle effectue un tirage qu'elle enregistre dans la table des tirages, puis l'affiche à l'écran. Entre deux tirages, le thread s'endort pendant 30 secondes (et non pas 5 minutes pour pouvoir effectuer des tests plus rapidement).

2.7   Gestion des joueurs

La fonction gestionJoueur crée une nouvelle entrée dans la table des joueurs puis, boucle sur une écoute sur la socket de dialogue, en attente d'une requête du joueur.

Lorsqu'un joueur joue un Flash ou un bulletin, le thread associé au joueur crée une nouvelle entrée dans la table des reçus. Une copie du reçu ainsi ajouté est envoyée au joueur via sa socket.

2.8   Gestion des reçus

La gestion des reçus se traduit par la gestion de la table des reçus. Cela dépend de l'endroit où est stockée la table. Vous allez considérer deux versions : la version où la table est stockée en mémoire centrale ; et la version où la table est stockée sur disque.

2.8.1   Reçus en mémoire centrale

La table des reçus est mise à jour par les threads joueurs lors du traitement des requêtes de prise de jeu par bulletin ou par Flash (jouer_un_bulletin et jouer_un_flash), et des requêtes de vérification des gains (verifier_recu). Les accès concurrents à la table des reçus doivent être synchronisés au moyen des sémaphores de la sous-section 2.5.

2.8.2   Reçus sur disque

Cette version permet de pallier les problèmes de pannes du serveur. Pour augmenter l'interactivité du serveur et assurer le recouvrement des E/S, les requêtes de mise à jour de la table des reçus ne sont plus gérées par les threads joueurs eux-mêmes. Ainsi, chaque thread joueur crée un autre thread, avec lequel il communique par tube, pour gérer les requêtes de mise à jour des reçus sur disque. Modifier la fonction gestionJoueur pour lancer le thread en question en mode détaché. Pour cela, implémentez la fonction publique suivante de la bibliothèque libthrd (Répertoire Threads).
void creerThreadES(int, int);
Cette fonction permettra de lancer un thread dans le mode détaché. Les deux arguments désignent respectivement le descripteur de socket de dialogue avec le joueur et le descripteur du tube de communication entre les deux threads. Le thread lancé aura comme tâche d'exécuter la fonction :
void gestionES(int, int);
Cette fonction boucle en attente de requêtes d'ES en provenance du thread joueur. Les réponses sont directement envoyées au joueur via sa socket attitrée.


Previous Contents Next