Laurent Pautet (pautet@telecom-paristech.fr)
L'objectif de ces Travaux Pratiques consiste à mettre en oeuvre divers algorithmes de communication de groupe préservant un ordre total causal. Certains des algorithmes ne fournissent qu'un ordre total, d'autres qu'un ordre causal, mais l'application finale est telle que si l'une des propriétés est présente l'autre l'est également. Pour implanter ces algorithmes, les protocoles qui les mettent en oeuvre seront implantés sous forme d'objets. Il sera possible de choisir un algorithme donné en passant son nom sur la ligne de commande ce qui aura pour conséquence de configurer la politique de diffusion utilisée par le système de communication en invoquant les méthodes sur l'objet approprié.
L'objectif de l'application consiste à faire appliquer des opérations arithmétiques sur une variable entière partagée par plusieurs noeuds. Ces opérations devront être réalisées selon un ordre de diffusion total ou causal de sorte que les noeuds obtiennent une valeur résultante identique. En l'occurrence, le noeud 1 effectuera une addition de 1, puis le noeud 2 une multiplication par 2 et enfin le noeud 3 une soustraction par 2. Ces opérations seront renouvellées 10 fois. La variable initiale valant 1, le résultat escompté sera toujours de 1024.
Afin que les situations de non respect de l'ordre total et de l'ordre causal soient fréquentes en l'absence d'algorithmique particulière, nous nous appuyerons sur un système de communication adapté dont l'une des caractéristiques consiste à affecter un temps de latence aléatoire à chacun des messages échangés (même à un message envoyé en local).
Globalement, l'application se découpe en trois parties : le composant applicatif, le composant protocolaire et le composant de transport. Le composant applicatif met en oeuvre les calculs indiqués ci-dessus. Il diffuse ses requêtes en utilisant le composant protocolaire et en retour traite les requêtes reçues par celui-ci. Le composant protocolaire met en oeuvre l'algorithme de diffusion c'est-à-dire complète les messages de diffusion lors d'une émission, utilise le composant de transport pour effectuer la diffusion effective, traite les réceptions signalées par le composant de transport et se charge de délivrer les messages au composant applicatif lorsque l'algorithme le permet. Le composant de transport se charge des communications TCP/IP et comme cela a été indiqué, induit des temps de latence lors de la réception des messages.
Vous chargerez les sources du TP, construirez l'application initiale et constaterez son mauvais fonctionnement en l'absence d'une algorithmique adéquate. Les protocoles de diffusion présents sont les suivants :
Les protocoles autres que celui de diffusion simple ont été laissés volontairement inachevés. Il vous revient de les complèter en vous appuyant sur les composants logiciels présents dans le système de communication et en vous reportant aux transparents du cours.
Ce TP sera réalisé en Java et à ce titre s'appuye sur les cours de la brique prérequise inf223.
Chaque noeud logique exécute le même programme nommé Main. Celui-ci requiert de passer un numéro de noeud en argument pour différencier ses différentes instances. Il peut s'accompagner éventuellement d'autres arguments de configuration. Pour mettre en oeuvre le système réparti, il faut ouvrir trois sessions et lancer dans chacune d'elles le programme main avec trois numéros de noeud différents allant de 0 à 2. Par exemple :
java Main 0
Par défaut, la diffusion adopte la politique dite simple qui ne respecte absolument pas l'ordre total causal.
Changer de politique de diffusion s'obtient en rajoutant sur la ligne de commande le paramètre suivant :
java Main 1 -p=nom_du_protocole
où nom_du_protocole peut valoir :
L'argument -d permet d'obtenir des informations utiles de mise au point.
Dans le cadre du TP, nous n'avez qu'à modifier les composants dits protocolaires c'est à dire les fichiers *Protocol.java dans lesquels se trouvent décrits les politiques de diffusion qui se trouvent dans un état compilable mais incomplet. Les descriptions ci-dessous permettent de comprendre les interactions entre les différents composants applicatifs, protocolaires et de transport. Tous les fichiers sont commentés et permettent par ailleurs de comprendre l'organisation du logiciel.
Main se charge d'initialiser le système.
Notamment, comme indiqué dans la partie Architecture, Main lit sur la ligne de commande le protocole à instancier. Il consulte les protocoles disponibles pour obtenir une usine à objets et crée une instance du protocole protocol. Il indique à la classe Trasnport quel protocole elle doit appeler à la réception d'un message.
En fonction de son numéro passé en argument sur la ligne de commande, un noeud va se charger d'attendre que les opérations des noeuds précédents se soient exécutées, puis va demander la diffusion de son opération. Pour ce faire, il alloue un message et le remplit. Par exemple, pour le noeud 1, le message contiendra l'opération d'addition avec un opérande de 1.
switch (Context.myNode) {
case 0: {request = add ;break;}
case 1: {request = mul ;break;}
case 2: {request = sub ;break;}
}
Application.appendRequest(request, msg);
protocol.broadcast(msg);
Emballer un entier I dans un message M (voir Message.java) s'obtient par :
M.append (I);
Plus généralement, emballer une valeur V de type T dans un message M s'effectue grâce à l'instruction :
M.append (I);
Pareillement, lire une valeur une valeur V de type T dans un message M s'effectue grâce à l'instruction :
M.getT();
L'appel de méthode public void broadcast(Message msg) demande aux composants protocolaires d'envoyer le message en utilisant le protocole de diffusion sélectionné (éventuellement par un argument sur la ligne de commande).
La classe Application contient les callbacks qui seront appelés lorsque les composants protocolaires délivreront une requête aux composants applicatifs. Il se charge d'effectuer les calculs sur la variable partagée et également signale au noeud qu'il peut procéder à l'exécution de ses propres requêtes.
La classe Common fournit des fonctionnalités basiques comme calculer le numéro de noeud suivant ainsi qu'une fonctionnalité d'impression de message en mode debug.
Le fichier nodes contient les adresses IP à utiliser par chacun des noeuds, le noeud N utilisant la Nième adresse du fichier.
Cette interface est à l'origine de toutes les classes mettant en oeuvre les politiques de diffusion. Elle requiert l'existence de deux méthodes broadcast et receive.
La classe SimpleProtocol fournit une utilisation simple des fonctionnalités de diffusion c'est à dire sans tentative de respect de l'ordre total causal. Il est intéressant en tant que modèle pour développer les autres politiques de diffusion.
La classe CentralProtocol contient les structures de données nécessaires à la mise en oeuvre du protocole de diffusion fondée sur l'ordonnanceur central.
La classe LClockProtocol contient les structures de données nécessaires à la gestion des horloges de Lamport et surtout celles nécessaires à la gestion des messages de la politique de diffusion fondée sur les horloges de Lamport.
La classe MClockProtocol contient les structures de données nécessaires à la gestion des horloges de Mattern et surtout celles nécessaires à la gestion des messages de la politique de diffusion fondée sur les horloges de Mattern.
La classe Message permet de gérer un ensemble de messages. Globalement, un message se rapproche d'un tampon ou flux d'octets dans lequel on peut stocker des données de types différents sous forme d'une représentation donnée, ici leur représentation en mémoire. Dans notre cas, un message permettra de construire le message qui sera envoyé sur le réseau. Pour manipuler ces messages, nous disposerons de procédures d'écriture ou de lecture d'une valeur dans un message. Pour chacun des types du TP, nous fournissons une procedure d'écriture append et une procédure de lecture getT.
La classe Transport se charge de mettre en oeuvre un protocole de transport qui se caractérise par des temps de latence aléatoire même lors d'une communication sur un même noeud. Elle offre deux procédures permettant de diffuser et d'envoyer un message.
Par ailleurs, chaque message reçu par le composant de transport est délivré auprès de la partie supérieure c'est à dire le composant protocolaire par l'intermédiaire d'un processus léger (même lorsqu'il s'agit d'un message en provenance du noeud lui-même).
Vous devez rendre le compte rendu de votre TP à Laurent Pautet (pautet@enst.fr). Pour ce faire, vous devez suivre le processus de rendu suivant :
src> make clean
src> cat *Protocol.java | Mail -s "TP GCS" pautet@enst.fr
Autrement dit, il n'est besoin d'envoyer que les fichiers que vous avez modifiés et naturellement tous les fichiers nouveaux que vous aurez créés. Ne pas joindre les fichiers .class, etc.