Ce document est accessible à partir de l'url http://igm.univ-mlv.fr/~duris/RESEAU/archiProg.html.
Il pourra être complété ou mis à jour à cet URL.
Version 4. Dernière modification le 05/04/2005 (couleur des modifications).
Le but de ce projet est de fournir une implantation en Java d'un système de distribution d'objets dans une mémoire virtuelle partagée entre différentes machines d'un réseau sur IP.
La problématique est simple. On suppose qu'on dispose de plusieurs applications (programmes Java), qui possèdent leur mémoire propre (celle de la JVM). Ces applications peuvent s'exécuter sur des machines différentes. On suppose que ces machines peuvent communiquer entre elles par les protocoles de transport classiques au dessus d'IP (UDP et TCP) et qu'elles constituent ainsi un réseau. Chaque application peut créer, consulter ou modifier des objets particuliers, appelés par la suite objets distribués, et partager les accès à ces objets avec toutes les autres applications du réseau. Pour cela, on souhaite que ces objets distribués puissent migrer d'une application à une autre du réseau, en fonction de l'accès à ces objets dont les applications ont besoin.
Le principal problème à gérer par les algorithmes et les protocoles que vous devrez mettre en œuvre, est celui de la cohérence de ces objets distribués. Chaque application peut en effet vouloir consulter des objets répartis (lecture) ou les modifier (écriture). On considérera que plusieurs lectures peuvent avoir lieu simultanément par différentes applications, qui disposeront de multiples copies d'un objet réparti, mais qu'une écriture dans un objet réparti ne peut être réalisée que par une application à la fois, et qu'elle requière l'invalidation de toutes les copies en lecture de cet objet réparti détenues par d'autres applications. À chaque instant, une application peut donc avoir une copie d'un objet réparti, valide ou invalide, et un droit d'accès à cet objet réparti en lecture ou en écriture.
Dans la mise en œuvre d'un réseau DiONet, on suppose qu'une
première application, dite référente, est démarrée sur une
machine dans le réseau. Les coordonnées de cette application référente
(typiquement une adresse IP et un numéro de port ; on choisira
par défaut le port 4468
) doivent alors être connues de
toute machine désirant participer au réseau. Cette machine référente
crée un objet réparti particulier appelé
registry, qui est une sorte de service de nommage permettant
d'enregistrer tous les objets répartis partagés dans le réseau (par
ailleurs, le registry est lui-même un objet réparti dans lequel
il est enregistré, par bootstrap). On suppose en effet que
chaque objet réparti est associé dans le réseau DiONet à un
« nom », identifiant unique par lequel il peut
être recherché et manipulé par toutes les applications du réseau.
L'application référente initie ainsi le réseau DiONet en y plaçant le premier objet réparti : le registry. En tant qu'application référente, elle en est, jusqu'à preuve du contraire, la propriétaire. En tant qu'application « normale », elle peut créer d'autres objets répartis, qu'elle peut enregistrer dans le registry et injecter dans le réseau DiONet à leur tour.
Un réseau DiONet est donc constitué autour d'une application référente, comme celle décrite ci-dessus, par un ensemble d'applications initialisées à partir de cette application référente. Ainsi, toutes les autres applications de DiONet paramétrées avec la même machine référente participent implicitement au même réseau d'objets répartis. Elles n'auront pas à créer de registry, mais simplement à utiliser celui de l'application référente. Ces applications peuvent alors utiliser ou créer des objets répartis du réseau DiONet. Typiquement, elles pourront:
Tout objet créé par une application et destiné à intégrer un réseau
DiONet devra être sérialisable. Pour pouvoir être accédé en tant
qu'objet réparti, il devra être associé à un nom auprès du
registry qui produira, lors de cette association, une instance
de proxy d'accès à cet objet réparti. Toute application du réseau
DiONet manipulera les objets répartis à travers ces proxies. Pour
simplifier l'implémentation, on supposera que les seuls accès aux
objets répartis se limiteront aux getters et aux
setters : les getters seront interprétés par les proxies
comme des demandes d'accès en lecture et les setters comme des
demandes d'accès en écriture. L'implémentation de ces proxies pourra
être réalisée grâce à la classe java.lang.reflect.Proxy
,
et le travail relatif à la préservation de la cohérence (recherche de
la dernière version de l'objet, prise des droits relatifs à
l'opération, invalidation éventuelle des autres copies dans le réseau,
etc.) pourra être implémenté sous la forme du
java.lang.reflect.InvocationHandler
associé à l'objet
réparti.
A priori, les applications qui utilisent un réseau DiONet sont des applications Java standard. Néanmoins, pour accéder aux objets répartis, elles doivent utiliser un registry implantant une interface comme celle-ci :
package fr.umlv.dionet.registry; import java.io.*; import java.net.*; import java.util.Set; /** * Registry is a particular distributed object that allows all kind of * distributed object to be associated with a name, which is the * unique identifier of this distributed object through the * network. This interface provides methods for lookup, binding, * unbinding and listing distributed object. Once registred in a * registry, ditributed objects are accessible through proxies * <code>java.lang.reflect.Proxy</code> that relay method invocations * to the actual objects; thus, the (proxies representing) distributed * object must be of a given interface type. */ public interface Registry { /** * Registers the object <code>o</code> and returns its representing * proxy. The returned proxy, a <code>java.lang.reflect.Proxy</code> * instance, is also of type <code>T</code>. With this method, the * registry chooses a free name for this distributed object to be * associated with. * @param o the object to be registered in this registry. * @return the proxy, of type <code>T</code>, representing the * registered distributed object. */ public <T> T bind(T o); /** * Registers the object <code>o</code>, associated with the name * <code>name</code> and returns its representing proxy. The * returned proxy, a <code>java.lang.reflect.Proxy</code> instance, * is also of type <code>T</code>. * @param o the object to be registered in this registry. * @param name the name to be associated with this object. * @return the proxy, of type <code>T</code>, representing the * registered distributed object. * @throws AlreadyBoundException if the provided <code>name</code> * is already bound to another distributed object in this registry. */ public <T> T bind(T o, String name) throws AlreadyBoundException; /** * Search for the distributed object associated in this registry to * the identifier <code>name</code> and returns its proxy instance. * @param name the name of the searched distributed object. * @return the <code>Proxy</code> object associated with * <code>name</code>. * @throws NotBoundException if no distributed object are bound to * the provided <code>name</code> in this registry. */ public <T> T lookup(String name) throws NotBoundException; /** * Returns the name associated in this registry with the distributed * object represented by the <code>proxy</code> parameter. * @param proxy the proxy representing a distributed object * @return the associated name * @throws NoSuchObjectException if the provided proxy does not * correspond with any registred distributed object. */ public <T> String getName(T proxy) throws NoSuchObjectException; /** * Returns the list of the names of distributed objects registred in * this registry. */ public Set<String> names(); /** * Remove the distributed object associated with the name * <code>name</code> in this registry. This operation may require to * invalidate existing occurrences of the corresponding proxy * through the network. * @param name the name of the distributed object to remove. * @throws NotBoundException if no distributed object are bound to * the provided <code>name</code> in this registry. */ public void unbind(String name) throws NotBoundException; /** * Remove the distributed object associated with the name * <code>name</code> in this registry and then bind the new * distributed object <code>o</code> to this name. * @param o the object to be registered in this registry. * @param name the name of the distributed object to be removed, and * to be associated with the new registred distributed object. * @return the <code>Proxy</code> representing the newly registered * distributed object. */ public <T> T rebind(T o, String name); }
Dans cette interface, le type paramétré T
représente
l'interface par laquelle l'objet réparti doit être utilisé, et qui est
le type donné au proxy par le registry, qui permet de le
manipuler dans le réseau DiONet.
Par ailleurs, pour installer (publier) le registry sur la machine de l'application référente ou pour récupérer le (proxy du) registry de l'application référente depuis une application non-référente du réseau DiONet, il sera nécessaire d'utiliser une classe de genre de celle donnée ci-dessous (d'autres signatures ou d'autres méthodes pourront étendre cette classe, qui est donnée ici à titre indicatif) :
package fr.umlv.dionet.registry; import java.io.*; import java.net.*; import java.util.*; /** * RegistryManager is responsible of providing a Registry proxy for * any application of a DiONet network. */ public class RegistryManager { // ... /** * Return the registry manager for this application, singleton * instance of this class. * @return the registry manager for this application. */ public static RegistryManager getRegistryManager() { // to be implemented... } /** * Publish a registry at the given referent socket address of the * machine invoking the method. Only the referent application of a * given DiONet network must call this method. Invoking more than * once this method with the same socket address will throw an * <code>IOException</code>. The <code>referent</code> socket * address must be local to the machine invoking this method. * @param referent the local socket address to which other (non referent) * application will ask for the registry proxy. * @return the published proxy for the registry of the DiONet * network identified by the argument socket address. * @throws IOException if the attachement of the referent * application to this socket address fails. */ public Registry publishRegistry(SocketAddress referent) throws IOException { // to be implemented... } /** * Retrieves a proxy registry from the referent application * located at <code>referent</code> socket address. All * application of a given DiONet network must call this method to * get the registry proxy. * @param referent the socket address of the referent application * that could provide the registry proxy. * @return the proxy for the registry of the DiONet network * indentified by the argument socket address. * @throws IOException if some IO error occurs while contacting the * referent application. */ public Registry locateRegistry(SocketAddress referent) throws IOException { // to be implemented... } // ... }
Ainsi, par exemple, l'application référente (démarrée sur la machine
referent.dionet.org
) crée l'unique objet
registry du réseau DiONet et le rend disponible son proxy à
toutes les autres applications de la manière suivante :
RegistryManager.publishRegistry(new InetSocketAddress("referent.dionet.org",4468));
De même, une application sur le réseau DiONet doit récupérer le proxy du registry et peut ensuite demander à enregistrer un objet qu'elle a créé ou, comme dans l'exemple ci-dessous, demander à récupérer le proxy d'un objet réparti dont elle connait le nom, pour appeler des méthodes de lecture ou d'écriture sur cet objet.
// Proxy du registry du réseau DiONet, récupéré auprès de // l'application référente InetSocketAddress referent = new InetSocketAddress("referent.dionet.org",4468); Registry registry = RegistryManager.locateRegistry(referent); // Récupération du proxy d'un objet réparti de DiONet, connaissant // son nom et une interface qu'il implémente GivenDistributedObjectInterface mdoi = registry.<GivenDistributedObjectInterface>.lookup("Distributed Object Name"); // Appel qui demande l'accès en lecture (getter) à l'objet réparti System.out.println(mdoi.getValue()); // Appel qui demande l'accès en écriture (setter) à l'objet réparti mdoi.setValue(12);
Le choix qui a été fait dans cette interface est d'utiliser les types
paramétrés de Java (generics). Vous devrez prendre garde à
l'utilisation de ces types et aux bugs qui subsistent dans
l'implémentation de certains compilateurs (en particulier celui
d'Eclipse : si vous avez l'impression qu'il n'y a pas d'erreur et
qu'Eclipse n'est pas content, essayez de compiler sur la ligne de
commande avec javac
...).
L'autre solution aurait pu consister à ne pas paramétrer les méthodes
du registry et utiliser Object
. Il est important
d'être conscient que l'objectif prioritaire de ce projet n'est pas de
faire des generics, mais bien du réseau : aussi, focalisez vous
sur l'aspect réseau.
Dans un premier temps, vous vous concentrerez sur une version minimaliste de l'implémentation de DiONet :
unbind()
et
rebind()
; Bien sûr, une fois cette version minimaliste mise en œuvre, ces précédentes limitations devront être levées, dans cet ordre.
Nous prenons un ensemble d'autres hypothèses simplificatrices pour développer les applications DiONet :
La levée de chacune de ces hypothèses constitue un problème intéressant, qui pourra être traité en tant qu'option au projet. Comme d'habitude, les « options » ne sont intéressantes que si les fonctionnalités « de bases » sont opérationnelles.
Les soutenances de ce projet auront lieu de préférence le mercredi 13 Avril, en fin de matinée et l'après midi (voir listes d'inscription au 3ième). Il est impératif de s'inscrire pour ces soutenances. Si ces créneaux ne suffisent pas, quelques soutenances auront lieu l'après-midi du vendredi 15 Avril (si les inscriptions pour le 13 sont complètes).
Lors de ce soutenances, les examinateurs
pourront vous demander de compiler et d'exécuter du code qui utilise
les bibliothèques que vous aurez développées. Pour cela, il est
impératif que vos classes respectent l'interface
Registry
et les signatures des méthodes de
RegistryManager
données dans ce sujet.
La durée d'une soutenance est d'une vingtaine de minutes. Prévoyez de présenter votre travail, vos choix décisifs en terme d'architecture, de fonctionnalités et d'implémentation, ainsi que quelques exemples illustratifs. Prévoyez également des argumentaires sur les qualités de votre projet, mais aussi sur ses défauts, ses limitations et ses améliorations possibles.
Ce projet est à réaliser en binôme. Il doit être rendu au plus tard le Dimanche 10 Avril 2005, sous la forme d'un fichier d'archive zip qui devra contenir les fichiers et répertoires suivants :
Gilles.Roussel@univ-mlv.fr
,
Julien.Allali@univ-mlv.fr
,
Christophe.Deleray@univ-mlv.fr
et
Etienne.Duris@univ-mlv.fr