Java:RMI
Sommaire
But
RMI est une API permettant d'appeler des objets distants sur le principe des ORB.
Création d'objets distants
La mise en oeuvre de RMI commence par la création d'objets distants au sein d'une application serveur. Ces objets auront:
- une partie spécification sous forme d'interface héritant de java.rmi.Remote,
- et d'une partie implémentation.
Par exemple:
import java.rmi.Remote; import java.rmi.RemoteException; public interface FileInterface extends Remote { byte[] downloadFile (String fileName) throws RemoteException; }
import java.io.*; import java.rmi.*; public class FileImpl implements FileInterface, Serializable { private static final long serialVersionUID = -1150718288555769992L; private String name; public FileImpl (String s) throws RemoteException { super(); name = s; } public byte[] downloadFile (String fileName) { byte[] buffer; try { File file = new File(fileName); buffer = new byte[(int)file.length()]; BufferedInputStream input = new BufferedInputStream(new FileInputStream(fileName)); input.read(buffer,0,buffer.length); input.close(); } catch(Exception e){ System.out.println("FileImpl: "+e.getMessage()); e.printStackTrace(); buffer = null; } return(buffer); } }
Publication d'un objet
Une fois les objets créés, il ne reste plus qu'à les publier auprès d'un service appelé RMI Registry. Pour cela, on aura besoin des classes java.rmi.server.UnicastRemoteObject et java.rmi.Naming:
final Remote obj = new FileImpl("FileServer"); final Remote remoteStub = UnicastRemoteObject.exportObject (obj); Naming.rebind ("//localhost/FileServer", remoteStub);
Ici, le RMI Registry va assigner un numéro de port TCP à notre objet. Chaque objet aura son numéro de port. L'assignation automatique de ce numéro peut devenir problématique si l'on veut traverser des pare-feux. Dans ce cas, il sera préférable de fixer le numéro de port (par exemple 19001):
final Remote remoteStub = UnicastRemoteObject.exportObject (obj, 19001);
Pour ne plus publier un objet distant:
Naming.unbind ("//localhost/FileServer");
RMI Registry
Le RMI Registry est une application (livrée avec Java) permettant de fournir un service réseau (sur le port TCP 1099) chargé d'aiguiller chaque requête RMI vers le bon objet distant (en donnant notamment le numéro de port TCP assigné à cet objet - dans notre exemple précédent 19001). Avant de lancer notre application serveur (ou toutes applications publiant des objets distants via RMI), il faudra s'assurer que le RMI registry soit bien lancé:
$JAVA_HOME/bin/rmiregistry
Il est possible de lancer le RMI Registry sur un autre numéro de port:
$JAVA_HOME/bin/rmiregistry 18001
Dans ce cas précis, il faudra indiquer le bon numéro de port dans les URL lors de chaque connexion au RMI Registry:
Naming.rebind ("//localhost:18001/FileServer", remoteStub);
Dans un système avec une seule application serveur, il serait peut-être intéressant d'embarquer le RMI Registry dans notre application. Pour cela, il suffit de créer une instance de java.rmi.registry.Registry à l'aide de la classe java.rmi.registry.LocateRegistry:
final Registry registry = LocateRegistry.createRegistry (18001);
Pour arrêter le RMI Registry:
UnicastRemoteObject.unexportObject (registry, true);
Accès à un objet distant
Une application cliente pourra obtenir une instance de notre objet distant:
final FileInterface obj = (FileInterface) Naming.lookup ("//localhost/FileServer");
et exécuter des méthodes de cet objet:
final byte[] filedata = obj.downloadFile (fileName);
Pour que cela fonctionne, notre application cliente devra avoir dans son classpath un JAR contenant l'interface FileInterface sans son implémentation (classe FileImpl).
Compatibilité IIOP
En principe RMI ne marche qu'avec Java. Mais si l'on veut que nos objets soient accessibles à partir d'autres langages, il faut rendre nos accès compatible IIOP. Pour cela, au moment de la publication:
- au lieu d'utiliser un java.rmi.server.UnicastRemoteObject, on utilisera un javax.rmi.PortableRemoteObject,
- au lieu d'utiliser un java.rmi.Naming, on utilisera un javax.naming.InitialContext.
Avec IIOP, il est impératif de générer le skeleton et le stub:
$JAVA_HOME/bin/rmic -iiop -verbose FileImpl
Dans notre exemple, cette commande va générer les classes suivantes:
- _FileInterface_Stub.class pour le stub à insérer dans le JAR de l'application cliente et serveur,
- _FileImpl_Tie.class pour le skeleton à insérer dans le JAR de l'application serveur.
Si l'on veut développer des applications clientes dans d'autres langages, il faut générer les IDL de toutes les classes utilisées dans l'interface:
$JAVA_HOME/bin/rmic -idl FileImpl
Au lieu d'utiliser le RMI Registry, on utilisera un démon ORB comme par exemple orbd:
$JAVA_HOME/bin/orbd -ORBInitialPort 1050 -ORBInitialHost localhost
Evénements
Avec RMI, il est possible d'implémenter un système d'événements. Par exemple, une application serveur peut avoir besoin d'envoyer des événements aux applications clientes.
Pour cela, on utilisera le pattern observateur. Ainsi dans les applications clientes, on instanciera un objet, appelé observateur, chargé de collecter les événements. Cet objet sera rendu distant via l'instruction:
Remote remoteObserver = UnicastRemoteObject.exportObject (observer);
ou avec IIOP:
PortableRemoteObject.exportObject(observer);
Ensuite, l'observateur sera enregistré auprès de l'application serveur. Pour cela, le serveur va publier auprès du RMI Registry (ou un démon ORB pour IIOP) un objet chargé d'enregistrer et de désenregistrer les observateurs. Chaque client va donc enregistrer son observateur à travers cet objet publié.
ATTENTION: Les observateurs qui auront été rendu distant, ne sont pas publiés auprès du RMI Registry ou du démon ORB.
Ainsi lorsque le serveur aura besoin de notifier un événement aux applications clientes, il parcourera la liste des observateurs et appelera chacun ou une partie d'entre-eux.