IN328 : RMI - Corrig´e SI
http://personnel.supaero.fr/garion-christophe/IN328
Ce TP va vous permettre de manipuler RMI, l’API d’appel distant fournie par Sun.
1 Contenu
Ce corrig´e succinct contient l’essentiel des explications n´ecessaires pour r´ealiser le TP sur RMI. Les
sources des classes sont disponibles sur le site. Vous trouverez un fichier de construction build.xml pour
Ant qui permet de construire et de lancer les applications facilement. Il faudra bien sˆur personnaliser les
variables que j’ai utilis´ees (chemin d’acc`es, machines utilis´ees etc.).
Les ex´ecutables que j’ai cr´e´es prennent tous en param`etre le nom d’une ou deux machines suivant les
questions (serveur RMI, serveur de fichiers).
J’ai choisi d’avoir quatres r´epertoires distincts contenant le bytecode : un pour le client, un pour le
serveur, un pour le registre et un pour le serveur de fichiers quand on l’utilise. Pour chaque question du
TP, j’ai cr´e un paquetage sp´ecifique.
Je n’ai pas utilis´e Ant pour les «ex´ecutions »du serveur, car on «forke »la machine virtuelle et on
n’a plus l’affichage.
Vous remarquerez ´egalement que le fichier Ant est construit maladroitement : je compile les interfaces
distantes et je les copie dans le bon r´epertoire pour le client. Dans un vrai projet, il vaut mieux cr´eer
toutes les interfaces distantes, les mettre dans un JAR et distribuer ce JAR.
Enfin, vous trouverez une archive contenant toutes les politiques de s´ecurit´e utilis´ees. Par d´efaut, j’ai
utilis´e clervoy comme machine sur laquelle tourne le registre et guerin comme machine sur laquelle
tourne le serveur de fichiers. Vous pouvez avoir quelques probl`emes si vous utilisez localhost, dans ce
cas, mettez l’adresse de la machine `a la place.
2 Cr´eation d’un objet distant et appel d’une m´ethode
Nous allons dans un premier temps cr´eer un objet qui fournit des services distants. Un client va appeler
ces services. Dans un premier temps, ce client se trouvera sur la mˆeme machine que l’objet distant, mais
mˆeme dans ce cas, RMI utilise des sockets. On a donc le mˆeme comportement que pour un objet se
situant physiquement sur une machine distante. Dans un second temps, nous utiliserons deux machines
diff´erentes (vous vous connecterez via ssh sur une machine Sun du CI, comme clervoy par exemple).
Important : il faudra placer les bytecodes des applications client et serveur (et pour le registre) dans
des r´epertoires diff´erents pour bien comprendre ce qu’il est n´ecessaire d’avoir de chaque cˆot´e (classes,
stubs, interfaces, positionnement du classpath).
1. d´efinir une interface distante InterfaceBonjour qui d´efinit une m´ethode afficher(String s) ;
2. d´efinir une classe BonjourDistant qui r´ealise cette interface et qui ´etend la classe
java.rmi.server.UnicastRemoteObject ;
3. cr´eer une classe Enregistrement qui poss`ede une m´ethode main qui enregistre l’objet dans un
registre ;
Ces trois questions permettaient de r´ealiser la partie serveur de l’application. Rien de bien particulier,
il suffisait de cr´eer une interface ´etendant java.rmi.Remote et une classe r´ealisant cette interface.
L’«application »serveur liant l’objet dans le registre utilisait bien sˆur la m´ethode Naming.rebind().
1
Cette m´ethode est pratique en d´eveloppement1, mais ne doit pas ˆetre utilis´ee syst´ematiquement dans
le cadre d’un d´eveloppement final.
Le nom de l’objet dans la base de registre est un nom long, comme par exemple rmi://localhost/
objetDistant. On aurait tr`es bien pu l’appeler simplement objetDistant. On peut remarquer
qu’ici on a omis le num´ero de port, donc par d´efaut on consid`ere le port 1099. On peut ´egalement
d´efinir le num´ero de port du serveur en utilisant l’argument en ligne de commande de java.
L’interface d´evelopp´ee fait partie du paquetage fr.supaero.rmi.serveur. Les classes d´evelopp´ees
font partie du paquetage fr.supaero.rmi.serveur.base et les bytecodes correspondants sont sto-
ces dans un r´epertoire classesServeur.
4. du cˆot´e client, implanter une classe AppelDistant qui poss`ede une m´ethode main qui cherche une
r´ef´erence sur l’objet distant et appelle sa m´ethode afficher ;
Rien de particulier. Il ne fallait pas oublier de transtyper la r´ef´erence obtenue apr`es le lookup,
car on obtient une r´ef´erence de type java.rmi.Remote. De mˆeme, il faut poss´eder l’interface
InterfaceBonjour dans son classpath pour pouvoir compiler correctement le client.
La classe fait partie du paquetage fr.supaero.rmi.client.base et le bytecode correspondant est
stoce dans le r´epertoire classesClient.
NB : vous remarquerez que j’ai d´etaill´e les exceptions qui peuvent ˆetre lanc´ees pour que vous voyez
bien quelles sont ces exceptions. On aurait bien sˆur pu rattraper toutes les exceptions avec un
catch (Exception e).
5. g´en´erer le stub de la classe BonjourDistant en utilisant l’option -v1.2 de rmic pour ne pas g´en´erer
de skeleton ;
6. lancer une base de registre grˆace `a rmiregistry et le serveur. On lancera le registre en prenant de
garde de bien positionner le classpath pour que le stub soit visible.
7. lancer le client. O`u s’ex´ecute la m´ethode de l’objet distant ?
Rien de bien particulier pour ces trois questions. Voici le d´etail des op´erations utilis´ees :
(a) lancement du registre. J’ai utilis´e la commande suivante :
CLASSPATH=/chemin_TP/classesRegistre/ rmiregistry
Je positionne le classpath pour que le registre puisse «voir »l’interface et le stub du serveur.
(b) «ex´ecution »de la classe fr.supaero.rmi.serveur.base.Enregistrement. Rien de bien
particulier, une erreur peut se produire si on n’a pas g´en´er´e le stub.
(c) «ex´ecution »de la classe fr.supaero.rmi.client.base.AppelDistant. Comme on n’utilise
pas de SecurityManager, on n’a pas acc`es au chargement dynamique des classes. Il faut donc
que le stub se trouve dans le classpath du client, sinon une exception est lev´ee (ce qui n’est pas
n´ecessaire si on peut le charger dynamiquement, cf. section 4).
On voit bien ici que la m´ethode afficher de l’objet distant s’ex´ecute du cˆot´e serveur.
A partir de la version 5.0 du JDK, lorsque l’on exporte un stub dans le registre, si le bytecode du
stub n’est pas disponible, on g´en´ere un objet de type java.lang.reflect.Proxy `a la place du
stub. Le client et le registre n’ont donc plus besoin d’avoir le bytecode du stub dans le classpath
(il faut bien sˆur que le client utilise du code et une JVM compatibles avec le JDK 5.0). J’ai
affice dans AppelDistant l’objet r´ecup´er´e par l’appel `a Naming.lookup. Si les stubs ont ´et´e
utilis´es par le serveur, le registre et le client, on obtiendra `a l’affichage :
Objet distant recupere : BonjourDistant_Stub[UnicastRef [liveRef:
1Elle est mˆeme n´ecessaire si on ne veut pas relancer le registre `a chaque fois.
2
[endpoint:[134.212.136.180:35046](remote),objID:[705be52f:117e92e1b19:-8000, 0]]]]
ce qui montre bien que l’on utilise le stub (on voit ´egalement quel est le port utilis´e par le stub
pour communiquer, ici le port 35046 de la machine dont l’adresse IP est 134.212.136.180).
Si on ne dispose pas des stubs (il suffit de ne pas l’avoir dans le CLASSPATH du serveur !), on
obtient alors :
Objet distant recupere : Proxy[InterfaceBonjour,RemoteObjectInvocationHandler
[UnicastRef [liveRef: [endpoint:[134.212.136.180:35058](remote),objID:
ce qui montre bien que l’on utilise un objet de type Proxy et non pas un stub.
Attention toutefois, cette solution suppose que le serveur et le client utilisent une version du
JDK sup´erieure `a la version 5.0.
Dans toute la suite du TP, j’utiliserai les stubs et non pas la classe Proxy. On devra donc avoir
le bytecode des stubs ecessaires dans le classpath.
3 Passage d’un objet en param`etre
Dans cette section, nous allons utiliser une m´ethode d’un objet distant qui prend en param`etre un
objet se situant chez le client. On cr´eera un nouveau paquetage pour pouvoir r´eutiliser le code ´ecrit
pr´ec´edemment facilement.
Important : comme pr´ec´edemment, nous n’utiliserons pas le chargement dynamique des classes n´e-
cessaires. Il faudra donc s’assurer que les classes et stubs n´ecessaires sont bien dans les classpaths (en
particulier, le serveur va avoir besoin de classes dont se sert le client).
1. cr´eer une interface InterfaceMessage dans le paquetage du serveur qui d´efinit une m´ethode
getTexte qui renvoie une chaˆıne de caract`eres ;
2. cr´eer une classe Message dans le paquetage du client qui r´ealise cette interface et qui a pour attribut
la chaˆıne de caract`eres `a renvoyer ;
Rien de bien particulier ici. J’ai choisi d’utiliser deux nouveaux paquetages,
fr.supaero.rmi.client.objet et fr.supaero.rmi.serveur.objet.
3. modifier les classes pr´ec´edentes pour que la m´ethode afficher de InterfaceBonjour prenne un
objet de type InterfaceMessage en param`etre. Que se passe-t-il ?
La modification de InterfaceBonjour et de BonjourDistant ´etait triviale. Lorsque l’on essaye
d’ex´ecuter l’appel client `a afficher, une erreur de marshalling est trouv´ee : la JVM nous indique
que la classe Message n’est pas s´erialisable. En effet, nous avons vu en cours qu’un objet pass´e en
param`etre d’une m´ethode distante devait soit ˆetre s´erialisable, soit lui-mˆeme distant.
4. ´ecrire une classe MessageSerialisable s´erialisable et r´ealisant InterfaceMessage et l’utiliser dans
AppelDistant. Que se passe-t-il maintenant ?
Cette fois-ci tout fonctionne, `a condition que le serveur puisse reconstruire l’objet de type
MessageSerialisable2. Comme nous n’utilisons pas le chargement dynamique, cela revient `a copier
le bytecode de MessageSerialisable dans le CLASSPATH du serveur.
Vous remarquerez que j’ai fait afficher un petit texte dans la m´ethode getTexte de
MessageSerialisable. Ce texte «apparaˆıt »du cˆot´e serveur, ce qui est normal car l’objet est
s´erialis´e et envoe au serveur.
2Sinon une exception d’unmarshalling est lev´ee dans le serveur.
3
Si on avait utilis´e un objet distant, ce texte serait apparu du cˆot´e client. J’ai implane cette so-
lution, les classes correspondantes sont dans les paquetages fr.supaero.rmi.client.distant et
fr.supaero.rmi.serveur.distant. On voit alors qu’il n’y a pas besoin d’enregistrer l’objet distant
de type Message dans un registre, tout se fait «automatiquement »(il faut bien sˆur que le serveur
poss`ede le bytecode du stub de Message).
4 Chargement dynamique des classes
Dans les sections pr´ec´edentes, nous avons suppos´e que le serveur, le client et le registre disposaient
des stubs et des bytecodes ecessaires `a leur bon fonctionnement. Nous allons maintenant utiliser le
chargement dynamique des classes. De cette fa¸con, le serveur et le client ne disposeront que des interfaces
distantes n´ecessaires `a leur compilation.
1. r´ecup´erer les classes d´evelopp´ees dans la section 2 dans deux nouveaux paquetages,
fr.supaero.rmi.client.dyn et fr.supaero.rmi.serveur.dyn. Modifier la classe cliente pour que
celle-ci puisse utiliser le chargement dynamique des classes ;
Il n’y avait pas grand chose `a faire. Comme la classe client ne disposera pas du stub, mais devra le
charger dynamiquement, il ne fallait pas oublier de mettre en place un RMISecurityManager dans
l’application cliente.
J’ai ´egalement chang´e l’application serveur pour utiliser le constructeur de UnicastRemoteObject
permettant de pr´eciser le num´ero de port sur lequel le stub attend les connexions. Ceci me permet
de d´efinir pr´ecisement la politique de s´ecurit´e dont j’ai besoin. J’ai choisi ici le port 1200.
2. lancer un registre sans classpath. On a besoin d’un serveur Web pour servir les fichiers. On va utiliser
un serveur de fichier l´eger, disponible sur le site sous l’onglet ressources, la classe ClassFileServer.
Pour l’utiliser : java ClassFileServer numPort CHEMIN_VERS_FICHIERS. Lancer ensuite le serveur
en pr´ecisant comme codebase "http://nomMachineServeurFichier:numPort/" (ne pas oublier le
«/»final !). Enfin, lancer le client avec un fichier de politique de s´ecurit´e ad´equat ;
L`a encore rien de particulier si on effectuait bien les op´erations demand´ees (ne pas mettre de
classpath pour le registre, lancer le serveur de fichier etc.). Voici le fichier de politique de s´ecurit´e
que j’ai utilis´e personnellement pour le client :
grant {
// connexions vers le serveur de fichiers
permission java.net.SocketPermission
"guerin.supaero.fr:2000", "connect";
// connexions vers le registre
permission java.net.SocketPermission
"clervoy.supaero.fr:1099", "connect";
// connexions vers le stub
permission java.net.SocketPermission
"clervoy.supaero.fr:1200", "connect";
};
Il ne fallait pas oubli´e que l”on a ´egalement besoin de l’interface InterfaceDistant sur le serveur
de fichiers pour pouvoir reconstruire le stub.
On remarquera que l’on voit bien les appels au serveur de fichier dans les traces de ce dernier lorsque
le registre et le client vont charger les interfaces et les classes dont ils ont besoin.
4
On remarquera enfin que si le client utilise des objets en param`etre de la m´ethode distante, il peut
´egalement pr´eciser que le bytecode des classes et interfaces correspondantes se trouvent sur le serveur
de fichier (pour que l’objet serveur puisse les reconstruire) via java.rmi.server.codebase.
3. nous allons essayer de «bootstrapper »le client et le serveur. Pour cela, cr´eer un r´epertoire qui
contiendra les classes de l’application (mˆeme celles du client. Il faudra donc modifier la classe
AppelDistant pour qu’elle n’ait plus de m´ethode main, mais qu’elle appelle la m´ethode afficher
`a sa cr´eation), et deux applications qui chargent dynamiquement la classe applicative du serveur et
la classe applicative du client.
C’est le plus gros morceau du TP. Il faut rester m´ethodique et ne pas se pr´ecipiter.
Les classes sont disponibles sur le site dans les paquetages fr.supaero.rmi.client.boot et
fr.supaero.rmi.serveur.boot pour les classes applicatives et dans fr.supaero.rmi.boot pour
les classes de d´emarrage.
J’ai choisi les machines suivantes :
le serveur de fichiers tourne sur guerin:2000
le serveur et le registre tournent sur clervoy
le client tourne sur dortie
Les classes «applicatives »ne n´ecessitaient pas de modifications importantes. Seule la classe
AppelDistant devait ˆetre modifi´ee : le traitement qu’elle faisait (appel de la m´ethode afficher)
devait ˆetre encapsul´e dans son constructeur qui ne devait pas poss´eder d’argument (appel `a
newInstance). Ceci peut poser probl`eme si l’on veut param´etrer le nom du serveur o`u se trouve le
registre par exemple.
On pouvait alors lancer un serveur de fichier pointant sur le r´epertoire contenant ces classes et lancer
un registre sans classpath sur la machine servant de serveur.
Le serveur «bootstrapp´e »lui se contente de r´ecup´erer l’objet serveur dynamiquement via la classe
RMIClassLoader et cr´ee un objet. On remarquera l’utilisation de la classe java.util.Property
qui permet de r´ecup´erer les propri´et´es du syst`eme (codebase ici). Il faut pr´eciser dans le fichier de
politique de s´ecurit´e que l’on est autoris´e `a le faire :
grant {
// on autorise les connexions par socket sur le port 2000 du serveur de
// fichiers
permission java.net.SocketPermission "guerin:2000", "connect";
// on autorise les connexions par socket sur le port 1099 de la machine
// pour le registre
permission java.net.SocketPermission "clervoy:1099", "connect";
// on autorise les connexions par socket sur le stub
permission java.net.SocketPermission "clervoy:1200", "accept";
// on autorise les connexions par socket sur la machine appelante
// pour le retour
permission java.net.SocketPermission "dortie:1024-", "accept";
// on autorise la lecture de la propriete du systeme java.rmi.server.codebase
permission java.util.PropertyPermission "java.rmi.server.codebase", "read";
};
On remarquera que je suis oblig´e d’autoriser les connexions sur dortie (sur laquelle tourne le client)
sur tous les ports utilisateurs, car la socket factory utilis´e (ici RMISocketFactory, la factory par
5
1 / 8 100%
La catégorie de ce document est-elle correcte?
Merci pour votre participation!

Faire une suggestion

Avez-vous trouvé des erreurs dans linterface ou les textes ? Ou savez-vous comment améliorer linterface utilisateur de StudyLib ? Nhésitez pas à envoyer vos suggestions. Cest très important pour nous !