Projet d'architecture et de programmation réseau

Réseau d'Objets Répartis -- DiONet (Distributed Object Network)


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).

Version 3 du 11/03/2005.

Version 2 du 08/03/2005.

Version 1 du 07/03/2005.


Report de la date de rendu: au plus tard le Dimanche 10 Avril 2005

Le but du projet

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.

Le principe des algorithmes et la situation du problème

L'article Memory Coherence in Shared Virtual Memory Systems de K. Li et P. Hudak vous servira de référence pour appréhender la problématique des systèmes à mémoire virtuelle répartie et pour vous donner les idées des algorithmes utilisables. Il vous est demandé d'implanter un algorithme de gestion distribuée des ressources (et non centralisée). En revanche, si les algorithmes décrits dans l'article peuvent vous guider, ils ne seront pas utilisables tels quels ; à vous de les adapter à votre problème. En effet, la problématique décrite dans cet article s'intéresse à la distribution de « pages » mémoire, et le problème qui nous intéresse ici est la distribution d'« objets ».

Le principe de fonctionnement du réseau

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.

Les interfaces des applications

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 quel ordre faire les choses

Dans un premier temps, vous vous concentrerez sur une version minimaliste de l'implémentation de DiONet :

  1. gestion des accès uniquement en lecture (que des getters) ;
  2. pas de gestion des désenregistrements unbind() et rebind() ;
  3. pas de gestion du téléchargement des classes ou interfaces requises pour la création des objets. Autrement dit, vous supposerez dans un premier temps que toutes les applications disposent en local de toutes les classes et interfaces nécessaires à la sérialisation/désérialisation des objets répartis.

Bien sûr, une fois cette version minimaliste mise en œuvre, ces précédentes limitations devront être levées, dans cet ordre.

Hypothèses simplificatrices

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.

Soutenances

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.

Modalités

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 :

  1. un fichier build.xml, script de compilation ANT
  2. un répertoire src contenant les sources Java (organisées dans un ou plusieurs paquetages)
  3. un répertoire classes contenant l'ensemble des classes du projet.
  4. un répertoire lib contenant l'archive JAR dionet.jar de l'ensemble des applications.
  5. un répertoire docs contenant la documentation
  6. Un répertoire bin contenant des fichiers sh et .bat permettant de lancer les différents programmes de test de votre projet (application référente et applications « normales ») resepectivement sous linux et sous windows (XP, NT, 95 etc...). L'objectif de ces (petites) applications est de démontrer le fonctionnement de votre réseau DiONet sur un problème très simple. Elles devront être accompagnées de fichiers README décrivant de qu'elles font et ce qu'elles illustrent.

Le fichier d'archive compressé contenant ces différents documents sera envoyé par un mail, indiquant les noms et prénoms des deux membres du binôme, avec comme sujet projet DiONet, à tous les chargés de TD et au chargé de cours : Gilles.Roussel@univ-mlv.fr, Julien.Allali@univ-mlv.fr, Christophe.Deleray@univ-mlv.fr et Etienne.Duris@univ-mlv.fr


© Université de Marne-la-Vallée - Février 2005