Objectif/Contenu :
La première étape va consister à écrire une interface distante et l'objet l'implémentant
Pour écrire le client il n'est nécessaire de connaitre que l'interface distante. Il ne devra donc y avoir dans votre code que des références à Distante et jamais à ObjetDistant
Nous allons maintenant tester ce petit programme. Pour simplifier un peu, tout se fera sur une unique machine. Ouvrez trois terminaux différents pour effectuer les manipulations suivantes en vous positionnant chaque fois dans le répertoire contenant les .class (ou restez dans l'IDE si vous voulez) :
Les communications (invocations de méthodes) en RMI sont dites synchrones et ce même sans retour (donc même si void). C'est ce que nous allons mettre en évidence.
Nous allons maintenant tester ce petit programme en utilisant deux machines. On fait l'hypothèse que ces 2 machines peuvent communiquer librement (sont sur le meme réseau) et que java est disponible sur cette autre machine (meme si pas forcément dans la meme version). Cet exercice ne doit pas (encore!) utiliser de SecurityManager, car ce sera l'objet du prochain TP. Attention, dans la suite, on parle du répertoire où se trouvent les .class, mais, si vous avez des packages, il faut comprendre "le répertoire correspondant au bon CLASSPATH, qui n'est pas forcément égal au répertoire où sont les fichiers .class";=)
Si l'expérience ne réussit pas à cause d'une résolution d'adresse IP erronée, (cad, l'adresse IP indiquée dans le stub reçu par le client est localhost et non l'adresse IP sur le réseau..., il semble possible, d'après des forums, de réparer le problème en démarrant la JVM qui fabrique ce stub (la JVM du serveur) avec l'option -Djava.rmi.server.hostname=xxx.xxx.xxx.xxx
[fbaude@bego]~/Cours/AppRep/HelloWorld% The RMI naming service is listening at 2001
cd bin
[fbaude@bego]~/Cours/AppRep/HelloWorld/bin% java HelloWorldServer
Creation de l'objet serveur
Referencement dans le registry..
. On a l'acces au registry ecoutant sur 2001
java.rmi.UnexpectedException: undeclared checked exception; nested exception is:
java.lang.ClassNotFoundException: HelloWorld not found in gnu.gcj.runtime.SystemClassLoader{urls=[file:./], parent=gnu.gcj.runtime.ExtensionClassLoader{urls=[], parent=null}}
at gnu.java.rmi.registry.RegistryImpl_Stub.rebind(libgcj.so.8rh)
at HelloWorldServer.main(HelloWorldServer.java:23)
Caused by: java.lang.ClassNotFoundException: HelloWorld not found in gnu.gcj.runtime.SystemClassLoader{urls=[file:./], parent=gnu.gcj.runtime.ExtensionClassLoader{urls=[], parent=null}}
Explication: le rmi registry râle lors du (re)bind, car, ne réussit pas à charger le .class décrivant l'interface remote à partir de laquelle il pourra générer le stub
Avant Java2, les classes correspondant aux stubs doivent etre pre-générées et le skeleton est obligatoire. Il peut etre utile de savoir comment les générer si une des JVMs que vous utilisez correspond à cette ancienne version de Java (en effet, vous n'avez peut-etre pas envie de reinstaller java sur chaque machine utilisée par votre déploiement). En tout cas, pédagogiquement parlant, c'est intéressant de comprendre comment et pourquoi l'utilisateur (aujourd'hui, la JVM automatiquement) génère un stub.
Quelques explications : Quand le rmiregistry exécute l'opération de binding, il s'aperçoit qu'il y a dans son CLASSPATH un fichier .class correspondant au stub, et s'en sert (après chargement), indiquant dans l'objet stub généré que celui-ci a été généré grâce à ce fichier. Par symétrie, quand le client désérialise l'objet stub reçu, il recherche le fichier .class correspondant dans son propre CLASSPATH (donc localement pour lui). Si il le trouve, il est capable de le charger (via le class loader).
Les méthodes de l'interface Remote doivent pouvoir prendre et rendre des paramètres...
On va maintenant declencher l'appel de la methode distante sayResultat renvoyant un objet Resultat.En effet, le serveur envoie vers la JVM cliente une copie de l'objet instance de Resultat. Pour que cela fonctionne, cad pour que coté client on récupère bien une copie de cette instance, il faut que l'objet puisse avoir été mis à plat sous forme d'un flot d'octets puis reconstitué (re-instanciated) dans la JVM de réception de ce flot; et donc, la classe dont il est instance doit être sérializable. Si tel n'est pas encore le cas, rendez Resultat serializable, recompilez et re-testez.
java.rmi.UnmarshalException: error unmarshalling return; nested exception is:
java.io.InvalidClassException: Resultat; Resultat; class invalid for deserialization
at sun.rmi.server.UnicastRef.invoke(Unknown Source)
at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(Unknown Source)
at java.rmi.server.RemoteObjectInvocationHandler.invoke(Unknown Source)
at $Proxy0.sayResultat(Unknown Source) // sayResultat est le nom de ma méthode Remote utilisé par mon HelloWorldClient
at HelloWorldClient.main(HelloWorldClient.java:36)
Caused by: java.io.InvalidClassException: Resultat; Resultat; class invalid for deserialization
at java.io.ObjectStreamClass.checkDeserialize(Unknown Source)
at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
Page maintenue par Francoise Baude @2011-