TP JAVA - RMI

Bertrand Dupouy et Frank Singhoff

SOMMAIRE

Déroulement du TP :
Il se divise en trois parties :

  1. la première partie montre le fonctionnement élémentaire de RMI, on utilisera cet environnement pour mettre en place :
  2. un modèle client-serveur dans la seconde partie,
  3. puis un modèle agents mobiles dans la troisième partie.

La seconde partie et la troisième partie comportent des exercices à rendre.

Documentation :
On donne le support du cours ici.

I. Utilisation élémentaire de RMI

RMI permet l'appel d'une méthode appartenent à un objet distant, c'est à dire géré par une autre JVM que la JVM locale.

L'objet (le serveur) qui propose l'appel de ses méthodes par des objets distants doit offrir :

  1. une interface qui dccrit l'ensemble des méthodes visibles à distance :
  2. la classe qui implémente cette interface :
  3. l'instanciation de cette classe et son enregistrement sous un nom auprès du service de désignation de RMI (rmiregistry) :

L'appelant (le client) doit obtenir un référence sur l'objet distant :

  1. appel à Naming.lookup qui interroge rmiregistry sur la machine distante. L'argument passé est du type URL avec un champ protocole positionné à : :rmi.
    La classe Naming se conduit en représentant local du serveur de noms : la méthode lookup utilise LocateRegistry qui va chercher et interroger registry sur la machine distante.
    Le stub est téléchargé à ce moment (lors du lookup).

Les souches (fichier dont le nom est terminé par _stub) destinées au client et au serveur sont produites par rmic.
L'appel à la méthode distante est, dans les faits, traduite en appel local au stub fourni par le serveur. C'est ce dernier qui fait l'appel distant.

I.1 Les sources de l'application

Recopier les fichiers se trouvant dans ~domas/TP-JAVA-RMI/EXO1 en prenant le fichier :
EXO1.tar . 
On y trouve les fichiers  : Hello.java , HelloServeur.java , HelloClient.java .

Commentaires sur le fichier Hello.java, l'interface, qui se réduit aux lignes suivantes :

    public interface Hello extends java.rmi.Remote
    {
     String lireMessage() throws java.rmi.RemoteException;
    }
  1. Remote signifie que la méthode lireMessage de cette classe Hello pourra être appelée depuis une JVM autre que la JVM locale.
  2. HelloServeur.java implémente cette interface :
    public class HelloServeur 
                 extends    UnicastRemoteObject
                 implements Hello
    
UnicastRemoteObject utilise les classes ServerSocket et Socket.

I.2 Construire l'application


Attention ! Environnement Java :
Pour éviter les problèmes de compatibilité et autres il vous FAUDRA exécuter le script java.env avant de lancer les commandes rmiregistry, javac, java, rmic.
Pour exécuter ce script, par exemple, en zsh, taper :
source java.env

Comment construire l'application ?

Remarque :
Là où se trouvent les sources, il y a un fichier Makefile qui automatise les commandes précédentes...

En cas de problème avec rmiregistry, pour tracer son activité  :

rmiregistry -J-Dsun.rmi.log.debug=true
      -J-Dsun.rmi.server.exceptionTrace=true
      -J-Dsun.rmi.loader.logLevel=verbose
      -J-Dsun.rmi.dgc.logLevel=verbose
      -J-Dsun.rmi.transport.logLevel=verbose
      -J-Dsun.rmi.transport.tcp.logLevel=verbose

I.3 Exécution répartie

Le partage de code (pour les stubs) est assuré par nfs, puisque le client et le serveur, même s'ils ne sont pas sur la même machine, partagent le même disque... Dans le cas général il faut indiquer où sont les stubs(type 1), ou bien les faire transiter par un serveur http (type 2). C'est ce qu'on va faire en déposant les fichiers .class générés par le serveur dans un répertoire public_html.
Pour ce faire il faut indiquer au compliatur Java où déposer les fichiers du type .class. On le fera en utilisant un script de l'un des deux types suivants :

Type 1 :

# rmiregistry peut etre lance depuis n'importe quel repertoire
#rmiregistry 45000&
javac -d $HOME/public_html HelloServeur.java
javac -d $HOME/public_html Hello.java
javac -d $HOME/public_html myHostname.java
java \
       -classpath $HOME/public_html \
       -Djava.security.policy=java.policy  \
       -Djava.rmi.server.codebase=file:$HOME/public_html/  \
         HelloServeur 45000 blablabla

Type 2 :

# ATTENTION : dans l'option codebase, remplacer infres5.enst.fr par le nom
#             de votre serveur http 
# rmiregistry peut etre lance depuis n'importe quel repertoire
#rmiregistry 45000&
javac -d $HOME/public_html HelloServeur.java
javac -d $HOME/public_html Hello.java
javac -d $HOME/public_html myHostname.java
java \
     -classpath $HOME/public_html \
     -Djava.rmi.server.codebase="http://infres5.enst.fr/~$USER/" \
     -Djava.rmi.server.hostname=$HOSTNAME.enst.fr \
     -Djava.security.policy=java.policy \
        HelloServeur 45000 Message_Test

I.4 Questions

Pour faciliter la résolution des exercices suivants, répondez aux questions ci-dessous en consultant le fichier produit par rmic -keepgenerated :

Voir dans le stub, exécuté localement par le client, l'appel à la méthode distante qui se fait par : ref.invoke où :

II. Utilisation du modèle client serveur

Copier les fichiers se trouvant dans ~domas/TP-JAVA-RMI/EXO2 en faisant :

cp -Ra  ~domas/TP-JAVA-RMI/EXO2 .
ou en prenant le fichier :
EXO2.tar , 
On doit trouver les fichiers suivants :
java.env
java.policy
myHostname.java
ClientMagasin.java
GerantMagasin.java
Magasin.java 
Mag1
Mag2
Mag3  
Makefile
README  

Les méthodes du fichier myHostname.java permettent de retrouver le nom qualifié d'une machine, c'est à dire de passer de machine à machine.enst.fr.

II.1 Modélisation de l'application

On se propose de simuler le scénario suivant : un cuisinier cherche des ingrédients pour préparer un repas au moindre coût.
Il va chercher les ingrédients un par un, en procédant ainsi :

Schéma du mécanisme d'interrogation :

Voici le contenu de l'interface Magasin.java :

// L'interface de l'objet doit etre publique
// sinon les clients auront des pbs !!!
//
public interface Magasin extends java.rmi.Remote
{
    float RenvoyerPrix (String Ingredient) throws java.rmi.RemoteException;
}
Remote signifie que la méthode RenvoyerPrix de cette classe Magasin peut être appelée depuis une JVM autre que la JVM locale.

GerantMagasin.java implémente cette interface :

public class GerantMagasin
    extends UnicastRemoteObject  implements Magasin
...
et ClientMagasin.java utilise la méthode publique RenvoyerPrix.

II.2 Exercice

Dans cet exercice on vous demande d'abord de compléter le fichier source ClientMagasin.java puis de lancer toute l'application.
  1. Dans ClientMagasin.java, compléter les lignes où se trouvent des ..., c'est à dire :
  2. Une fois ceci fait, on lance l'application :

III. Utilisation du modèle agents mobiles

On reprend l'exemple précédent que l'on va traiter en mettant en oeuvre des agents mobiles.

Recopier les fichiers se trouvant dans ~domas/TP-JAVA-RMI/EXO3  en faisant :

cp -Ra  ~domas/TP-JAVA-RMI/EXO3 .
ou en prenant le fichier :
EXO3.tar , 
On doit trouver les fichiers suivants :
Agent.java
Hote_implem.java
Mag1
Mag3 
Makefile  
README initiateur
java.policy 
myHostname.java  
threadAgent.java
Hote.java 
Initiateur.java
Mag2  
Magasin.java
agentIngredient.java 
java.env    
lanceHote  
mySecurity.java

Les méthodes du fichier mySecurity.java surchargent les méthodes de contrôle d'accès du Security Manager pour permettre le téléchargement de code depuis un serveur httpd qui se trouve sur un site différent de celui du serveur applicatif.

III.1 Modélisation de l'application

Le "client" va créer un agent au lieu d'interroger lui-même les sites détenteurs de magasins.
Cet agent va passer de site en site, et sur chacun de ces sites entrer dans le magasin pour y lire le prix de l'ingrédient recherché. S'il y a lieu, il met à jour le prix minimal. Une fois tous les sites consultés, l'agent donne au "client" le nom du site qui propose l'ingrédient recherché au prix le plus bas, ainsi que ce prix.

Comment mettre en oeuvre cette application ?
Le client va lancer sa demande depuis un site qui sera appelé par la suite "initiateur". Ce client va initialiser un tableau de sites à parcourir, puis exécuter sur le premier site de la liste la méthode migre, à laquelle il a passé l'agent en argument.

Le schéma suivant indique les accès aux objets distants lors du lancement initial de l'agent :

Deux objets vont modéliser ces différents acteurs : Hote et Agent.

  1. L'interface de l'objet Hote est la suivante :
    public interface Hote extends Remote {
        void migre(Agent a) throws RemoteException;
    }
    

    Remote signifie que les méthodes de cet objet Hote peuvent être appelées depuis une JVM autre que la JVM locale.

    Il y aura deux implémentations de cette interface Hote  :

    1. une pour les sites détenant un magasin, dans ce cas la méthode migre consulte le prix puis fait migrer l'agent vers le site suivant (Hote_implem.java),
    2. une pour le site initiateur, la méthode migre se contente d'afficher le nom du site proposant le prix le plus bas, ainsi que ce prix (Initiateur.java).

  2. L'interface de l'objet Agent est la suivante :
    public interface Agent extends Serializable {
        void traitement(String [] ingredient, Float [] prix, int taille);
        String hoteSuivant();
        void afficheResultat();
    }
    


    Agent doit être une interface Serializable. En effet, Serializable indique que les objets Agents utilisés seront sérialisés et normalisés lors de l'appel distant (marshalling).

    Les deux premières méthodes servent aux sites consultés, la dernière est utilisée par l'initiateur.

    On remarquera que ces deux interfaces Agent et Hote sont complétement indépendantes de l'application actuellement traitée :

    De façon plus générale, cette méthode permet d'écrire le code d'un serveur avant de savoir quelle application il va exécuter.

    III.2 Implémentation des objets

    III.2.1 Les sites consultés

    Comme on l'a dit, un site consulté va implémenter l'interface Hote en faisant :
    public class Hote_implem extends UnicastRemoteObject
                               implements Hote
    ...
    
    

    UnicastRemoteObject surcharge des méthodes telle que equals qui ne fonctionnent pas de la même façon quand les objets ne sont plus locaux, mais distants.

    Les points importants du programme (Hote_implem.java), que l'on trouvera ici sont les suivants :

    1. l'appel au constructeur, qui met l'objet en attente d'éventuels appels distants,
    2. la méthode main, qui appelle en particulier :
      • le RMISecurityManager
      • Naming.rebind pour enregistrer les objets proposés par ce site auprès de rmiregistry,
      • la méthode migre est implantée sous forme de thread pour que le traitement effectué par cet agent ne bloque pas le fonctionnement de l'hôte.

    III.2.2 Le site initiateur

    Le site initiateur est celui sur lequel se trouve le "client" qui déclenche le fonctionnement de l'agent mobile.
    Ce site va, comme le précédent, implémenter de façon spécifique l'interface Hote :
    public class Initiateur extends UnicastRemoteObject
                               implements Hote
    
    

    Les points importants du code, que l'on trouvera ici sont les suivants :
    1. l'appel au constructeur qui met l'objet en attente d'éventuels appels distants,
    2. la méthode main qui appelle en particulier :
      • le RMISecurityManager
      • l'appel à Naming.lookup pour retrouver la référence du site auquel on s'adresse en premier. Le stub est téléchargé à ce moment (lors du lookup).
      • la méthode migre se contente d'afficher un résultat. Elle sera appelée par le site sur lequel se trouve le dernier magasin à consulter.

      Remarque importante : le stub permettant l'accès aux méthodes proposées par un objet distant est téléchargé lors de la recherche de cet objet par Naming.lookup. Le téléchargement est fait par le serveur web donné par le paramètre server.codebase défini au lancement du serveur qui implémente l'objet distant.
      Dans notre cas, Hote_implem_stub sera téléchargé lors de l'exécution de la ligne suivante :

        Hote hote  = (Hote) Naming.lookup(name);
      
      
      ce stub sera utilisé pour accéder à la méthode distante migre pendant l'exécution de :
         hote.migre(agent);
      
      

      III.2.3 L'agent

      L'agent de notre application, appelé agentIngredient, est implémenté à partir de l'interface Agent, comme indiqué ci-dessous :
      public class agentIngredient implements Agent {
      
      public Float monPrix = new Float(Float.MAX_VALUE);
      
      public String monIngredient="";
      
      public String site="";
      ...
      
      
      
      et dans le code de l'initiateur, on trouve :
      agentIngredient agent = new agentIngredient
                       (Integer.parseInt(args[0])args[0], 
                        hotes, args[1], args.length-2);
      hote.migre(agent);
      
      

      Le site qui exporte la méthode migre va créer un thread pour gérer l'agent, comme on l'indique ici :

      public void migre(Agent a)
        {
        threadAgent monThread = new threadAgent(a, monMagasin);
        monThread.start();
        }
      
      
      la classe threadAgent hérite de thread :
      class threadAgent extends Thread 
      
      

      III.3 Lancement de l'application

      Conditions de bon fonctionnement :
      • Le security manager impose que le serveur web (le démon httpd) qui permet de télécharger les stubs se trouve sur la même machine que le serveur détenant l'objet auquel fait accès ce stub. Si ce n'est pas le cas, il faut désactiver la sécurité, ce qui est fait ici dans le fichier mySecurity.java en surchargeant les méthodes de contrôle d'accès checkConnect.
      • rmiregistry ne doit pas être lancé dans un répertoire qui contient les classes à exporter, en effet si on le lance depuis un répertoire qui contient ces classes, il n'associe pas server.codebase à la référence de l'objet.

      Pour lancer un objet du type Hote, on exécute le script lanceHote.
      Attention :

      1. si $HOSTNAME est un nom de machine complet, il ne faut pas lui concaténer .enst.fr .
      2. www.infres.enst.fr doit être remplacé par le nom de la machine qui accueille votre "public_html".
      3. ce qui se trouve dans ce répertoire doit être accessible à group et others. Il faut leur attribuer le droit x, si ce n'est pas déjà fait. (chmod 711).
      4. le nom du serveur web ne doit pas être un alias, mais le nom qualifié (par exmple  : autan.enst.fr) d'un site qui détient réellement ce répertoire.

      Voici le contenu de ce script lanceHote qui démarre un objet du type Hote, c'est-à-dire un serveur :

      java -Djava.rmi.server.codebase=http://www.infres.enst.fr\
                                          /~$USER/tp-rmi/hosts/\
      	   -Djava.rmi.server.hostname=$HOSTNAME.enst.fr\
      	   -Djava.security.policy=java.policy \
                    Hote_implem Mag1
      
      

      server.codebase donne le nom du serveur web et du répertoire qui permettent aux appelants des méthodes distantes de télécharger les stubs correspondants (ici, il s'agit de Hote_implem_Stub.class). Cette information est associée à la référence de l'objet distant lors du bind par rmiregistry.

      De la même façon, pour lancer l'initiateur, on exécute le script initiateur dont le contenu est rappellé ci-dessous :

      java -Djava.rmi.server.codebase=...
           -Djava.security.policy=java.policy
           -Djava.rmi.server.hostname=$HOSTNAME.enst.fr
                    Initiateur $*
      
      

      ici server.codebase a le rôle suivant :

      • il indique d'où le dernier site va télécharger le stub de l'initiateur lors du retour l'agent vers le "client" (Initiateur_Stub.class) : rmiregistry associe cette information à la référence de l'objet distant.
      • il permet au site Hote de savoir depuis quelle machine il peut télécharger le code de l'agent.

      III.4 Exercice

      On vous demande :
      1. de compléter trois fichiers,
      2. de lancer l'application,
      3. de suivre son fonctionnement.

      Si problèmes de droits d'accès, voir :
      ici
      ici

      III.4.1 Première partie

      Fichiers à compléter :

      1. Dans le fichier threadAgent.java compléter les lignes où se trouvent des ..., c'est à dire :
        
           String leSuivant = .... ;
        
           // On fait migrer l'agent vers l'hote suivant
           System.out.println("L'agent migre vers " + leSuivant);
           Hote hote = .....
           hote.migre(monAgent);
        
        

      2. Dans le fichier Hote_implem.java compléter la ligne où se trouvent des ..., c'est à dire :
           String nom = "//" + machine.QualifiedHost() + ":" + args[0] + "/Hote";
           ....
           System.out.println("Hote_implem enregistre : " + nom);
        
        
      3. Dans le fichier Initiateur.java compléter la ligne où se trouvent des ..., c'est à dire :
           String nom = "//" + machine.QualifiedHost() + ":" + args[0] + "/Initiateur";
           ....
           System.out.println("Initiateur enregistre : " + nom);
        
      4. Dans le script shell initiateur compléter la ligne où se trouvent des ..., c'est à dire :
        java -Djava.rmi.server.codebase="..."
        

      III.4.2 Deuxième partie

      Pour lancer l'application, il faut faire :
      gmake clean
      gmake 
      gmake install
      
      
      Ces commandes compilent les sources et recopient les fichiers .class concernant le "client" et les "Hote" dans ~/public_html/tp-rmi/initiateur  et ~/public_html/tp-rmi/hosts :, respectivement.
      Il faut alors :
      1. lancer rmiregistry sur les machines où vous lancerez un "Hote". Utilisez le même numéro de port pour les 3 rmiregistry. (rmiregistry ne doit pas être lancé dans un répertoire qui contient les classes à exporter.).
      2. sur 3 machines différentes : aller dans le répertoire ~/public_html/tp-rmi/hosts  et exécuter la commande lanceHote. Celle-ci prend en paramètre le numéro de port pour rmiregistry.
      3. depuis une autre machine, aller dans le répertoire ~/public_html/tp-rmi/initiateur  puis lancer l'initiateur avec le script initiateur. Ce dernier prend en paramètre le numéro de port de rmiregistry, puis le nom de l'ingrédient à traiter et, enfin, la liste des "Hote" à visiter.

      III.4.2 Troisième partie

      Lorsque l'application fonctionnera, vous suivrez les accès aux serveurs httpd concernés en consultant le fichier access_log associé au serveur, en faisant :

      Remarque : cet accès est peut être interdit ... auquel cas vous ne pourrez pas faire tout ce qui suit.

      telnet votre_serveur_http  
      cd /home/www/httpd/logs/
      tail access_log 
      
      
      Exemple de trace des accès au serveur httpd par un utilisateur dont le nom est tp :
      Les serveurs ("Hote") tournent sur les machines roxane, emma, quasimodo.
      L'initiateur tourne sur la machine javert et a été lancé ainsi :
      initiateur 51000 sel roxane emma quasimodo
      
      
      On voit tout d'abord les lancements successifs des serveurs ("Hote") :
      roxane.enst.fr ...  "GET /~tp/hosts/Hote_implem_Stub.class HTTP/1.0" 200 1678
      roxane.enst.fr ...  "GET /~tp/hosts/Hote.class HTTP/1.0" 200 252
      
      emma.enst.fr ...  "GET /~tp/hosts/Hote_implem_Stub.class HTTP/1.0" 200 1678
      emma.enst.fr ...  "GET /~tp/hosts/Hote.class HTTP/1.0" 200 252
      
      quasimodo.enst.fr ...  "GET /~tp/hosts/Hote_implem_Stub.class HTTP/1.0" 200 1678
      quasimodo.enst.fr ...  "GET /~tp/hosts/Hote.class HTTP/1.0" 200 252
      
      
      Puis on suit le lancement de l'initiateur :
      javert.enst.fr ...  "GET /~tp/initiateur/Initiateur_Stub.class HTTP/1.0" 200 1676
      javert.enst.fr ...  "GET /~tp/initiateur/Hote.class HTTP/1.0" 200 252
      javert.enst.fr ...  "GET /~tp/hosts/Hote_implem_Stub.class HTTP/1.0" 200 1678
      
      
      On suit é les accès des serveurs à l'agent, puis au stub de l'initiateur :
      
      roxane.enst.fr ...  "GET /~tp/initiateur/agentIngredient.class HTTP/1.0" 200 1848
      
      emma.enst.fr ...  "GET /~tp/initiateur/agentIngredient.class HTTP/1.0" 200 1848
      
      
      quasimodo.enst.fr ...  "GET /~tp/initiateur/agentIngredient.class HTTP/1.0" 200 1848
      quasimodo.enst.fr ...  "GET /~tp/initiateur/Initiateur_Stub.class HTTP/1.0" 200 1676
      
      
      ©(Copyright) dupouy@enst.fr singhoff@enst.fr