Agents mobiles vs client/serveur

Bertrand Dupouy et Frank Singhoff

SOMMAIRE

Déroulement du TP :
Après avoir vu le fonctionnement élémentaire de RMI on utilisera cet environnement pour mettre en place un modèle client-serveur dans la première partie, puis un modèle agents mobiles dans la deuxième partie.



Les fichiers nécessaires à ce TP se trouvant ici. Pour les extraire, tapez la commande :

tar xvf tp-agents.tar 

Environnement Java :
Pour éviter les problèmes de comptabilité entre différentes versions de Java, il faut exécuter un des scripts agents.bash ou agents.tcsh. Cette opération doit être effectuée avant de lancer javac, java, rmiregistry ou rmic.

Par exemple, en tcsh, taper :
source agents.tcsh


(remarque : le fichier agents.bash est à l'intention des utilisateurs de zsh et de bash.)


I. Utilisation du modèle client serveur

Pour cet exercice; placez vous dans le répertoire EXO1. On doit trouver les fichiers suivants :

ClientMagasin.java GerantMagasin.java Magasin.java 
Mag1  Mag2  Mag3  
Makefile README  
MyHostName.java


I.1 Application à réaliser

On se propose d'écrire une application permettant à un client de rechercher des produits dans divers magasin.
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 :

public interface Magasin extends java.rmi.Remote
{
    float RenvoyerPrix (String Ingredient) throws java.rmi.RemoteException;
}


GerantMagasin.java implémente cette interface :
public class GerantMagasin
    extends UnicastRemoteObject  implements Magasin
...
et ClientMagasin.java utilise la méthode publique RenvoyerPrix.

I.2 Travail à faire

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 étoiles afin d'intéroger les différents magasins.
  2. Une fois ceci fait, on lance l'application :



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

Les sources de ce dernier exercice se situent dans le répertoire EXO2. 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. En effet, cette fois les fichiers .class (souches et agentIngredient.class) seront récupérés par le client à l'aide d'un serveur httpd qui est installé sur la machine 11de049.univ-brest.fr

II.1 Mise en oeuvre 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.
    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 : l'agent peut effectuer un traitement quelconque, et l'hote suivant peut être déterminé par n'importe quel algorithme.
    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.

    II.2 Implémentation des objets

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

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

      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 
      
      

      II.3 Lancement de l'application

      Attention aux conditions de bon fonctionnement :
      • Le security manager impose que le serveur web (le 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 qui contient l'instruction suivante:
      java -Djava.rmi.server.codebase="http://11de049.univ-brest.fr:8080\
                                          /$USER/tp-rmi/hosts/"\
      	   -Djava.rmi.server.hostname=$HOST\
      	   -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.

      Attention :
      le nom du serveur web ne doit pas être un alias, mais le nom qualifié (par exemple  : 011de049.univ-brest.fr) d'un site qui détient réellement le répertoire.

      De la même façon, pour lancer l'initiateur, on exécute le script initiatieur:

      java -Djava.rmi.server.codebase=...
           -Djava.security.policy=java.policy
           -Djava.rmi.server.hostname=$HOST
                    Initiateur $*
      
      

      ici server.codebase

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

      II.4 Travail à faire

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

      Complétez les fichiers suivants :

      1. Le fichier java Initiateur.java.
      2. Le fichier java Hote_implem.java.
      3. Le fichier java threadAgent.java .
      4. Le script shell initiateur.

      Les zones à compléter sont indiquées par des étoiles.

      • Pour lancer l'application, il faut faire :
        make clean
        make 
        make install
        
        
        Ces commandes compilent les sources et recopient les fichiers .class concernant le "client" et les "Hotes" dans le répertoire du serveur web. Les fichiers du serveur web sont dans le répertoire /home/commun_depinfo/enseignants/singhoff/APACHE/WEBPAGES/VOTRE_LOGIN ou VOTRE_LOGIN est le login Unix de la personne qui lance l'application.
      • Il faut alors lancer l'application:
        1. Si votre application de se lance pas correctement, vérifiez l'origine du problème en regardant le log du serveur httpd (dans le répertoire /home/commun_depinfo/enseignants/singhoff/APACHE/LOGS ).
        2. Dans le répertoire /home/commun_depinfo/enseignants/singhoff/APACHE/WEBPAGES/VOTRE_LOGIN/tp-rmi, lancer rmiregistry sur 4 machines différentes où vous lancerez un programme Java-RMI. Utilisez le même numéro de port pour les 4 rmiregistry. Le fait de lancer rmiregistry dans un répertoire d'où le ClassLoader ne pourra pas récupérer les fichiers .class force la JVM à utiliser le démon httpd pour télécharger ces fichiers .class.
        3. Sur 3 des machines, allez dans le répertoire /home/commun_depinfo/enseignants/singhoff/APACHE/WEBPAGES/VOTRE_LOGIN/tp-rmi/hosts  et exécuter la commande lanceHote. Celle-ci prend en paramètre le numéro de port pour rmiregistry ainsi que le nom du fichier qui contient les ingrédients.
        4. Depuis la 4ème machine, aller dans le répertoire /home/commun_depinfo/enseignants/singhoff/APACHE/WEBPAGES/VOTRE_LOGIN/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.

      II.5 Troisième partie : suivre son fonctionnement

      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 :
      cd  /home/commun_depinfo/enseignants/singhoff/APACHE/LOGS
      
      more 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.univ-brest.fr ...  "GET /singhoff/tp-rmi/hosts/Hote_implem_Stub.class HTTP/1.0" 200 1678
      roxane.univ-brest.fr ...  "GET /singhoff/tp-rmi/hosts/Hote.class HTTP/1.0" 200 252
      
      emma.univ-brest.fr ...  "GET /singhoff/tp-rmi/hosts/Hote_implem_Stub.class HTTP/1.0" 200 1678
      emma.univ-brest.fr ...  "GET /singhoff/hosts/Hote.class HTTP/1.0" 200 252
      
      quasimodo.univ-brest.fr ...  "GET /singhoff/tp-rmi/hosts/Hote_implem_Stub.class HTTP/1.0" 200 1678
      quasimodo.univ-brest.fr ...  "GET /singhoff/tp-rmi/hosts/Hote.class HTTP/1.0" 200 252
      
      
      Puis on suit le lancement de l'initiateur :
      javert.univ-brest.fr ...  "GET /singhoff/tp-rmi/initiateur/Initiateur_Stub.class HTTP/1.0" 200 1676
      javert.univ-brest.fr ...  "GET /singhoff/tp-rmi/initiateur/Hote.class HTTP/1.0" 200 252
      javert.univ-brest.fr ...  "GET /singhoff/tp-rmi/hosts/Hote_implem_Stub.class HTTP/1.0" 200 1678
      
      
      On suit également les accès des serveurs à l'agent, puis au stub de l'initiateur :
      
      roxane.univ-brest.fr ...  "GET /singhoff/tp-rmi/initiateur/agentIngredient.class HTTP/1.0" 200 1848
      
      emma.univ-brest.fr ...  "GET /singhoff/tp-rmi/initiateur/agentIngredient.class HTTP/1.0" 200 1848
      
      
      quasimodo.univ-brest.fr ...  "GET /singhoff/tp-rmi/initiateur/agentIngredient.class HTTP/1.0" 200 1848
      quasimodo.univ-brest.fr ...  "GET /singhoff/tp-rmi/initiateur/Initiateur_Stub.class HTTP/1.0" 200 1676
      
      





      ©(Copyright) dupouy@inf.enst.fr singhoff@univ-brest.fr