JNI (Java Native Interface) doit permettre à du code Java de s'interfacer avec d'autres langages (cibles principales le C/C++) mais son emploi semble délicat et peu convaincant.
Passer des objets est un mécanisme plus puissant que les RPC habituels qui sont limités à des types primitifs (ou structures de type primitifs, e.g. CORBA).
L'objet distant possède un représentant (proxy) sur la machine locale; ceci permet d'invoquer une méthode de l'objet B comme si celui-ci était local. C'est la souche (stub) qui matérialise ce représentant.
Avant la version 5 du JDK, les souches sont des fichiers stub.classgénérés avec rmic.
Exemple Simple: les arguments de la méthode invoquée sont des types primitifs (scalaires, e.g. int, float, ..) ou tableaux (e.g. String) mais sérialisables (java.io.Serializable).
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Operation extends Remote {
public int addition(int a, int b) throws RemoteException ;
}
Remarque 1. En étendant l'interface java.rmi.Remote, les méthodes de l'interface sont automatiquement appelables depuis d'autres JVM. Tout objet qui implémentera cette interface deviendra "remote".
Remarque 2. On pourrait bien sûr définir d'autres methodes à côté de addition.
import java.rmi.server.UnicastRemoteObject ;
import java.rmi.RemoteException ;
import java.net.InetAddress.* ;
import java.net.* ;
public class OperationImpl extends UnicastRemoteObject
implements Operation {
public OperationImpl () throws RemoteException {
super();
};
public int addition(int a, int b) throws RemoteException {
return( a + b) ;
}
}
Le service hérite de la classe java.rmi.UnicastRemoteObject pour activer l'objet distribué automatiquement.
Remarque. On pourrait définir directement l'enregistrement de l'objet en ajoutant, par exemple dans main :
public static void main(String args[]) {
try {
OperationImpl une_op = new OperationImpl ();
Naming.rebind("rmi://localhost/Operation",une_op) ;
System.out.println("Service Operation enregistré");
}
catch(Exception e) {
System.out.println("Err. enreg. registry");
}
}
import java.rmi.registry.Registry; import java.rmi.registry.LocateRegistry; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; public class Serveur { public static void main( String [] args) { try { OperationImpl une_op = new OperationImpl (); Registry registry = LocateRegistry.getRegistry(); registry.bind("Operation",une_op); System.out.println("Serveur pret"); } catch (Exception e) { System.out.println(e) ; } } }
Il y a possibilité de créer le registry dynamiquement (au lieu de lancer
rmiregistry sur la ligne de commande) avec
Registry registry = LocateRegistry.createRegistry(1099);
LocateRegistry.getRegistry() instancie une souche qui implémente l'interface Remote java.rmi.registry.Registry en demandant le registre sur localhost et le port 1099 (défauts). Utiliser getRegistry(String, int) pour spécifier hà´te et port. bind() est ensuite invoquée sur le stub pour que le registre attache le code de une_op au nom arbitraire "Operation".
Exemples :
Registry registry = LocateRegistry.getRegistry("foo.services.com");
SomeService service = (SomeService)registry.lookup("toto");
service.requestService(...);
import java.rmi.*;
import java.net.*;
public class Serveur {
public static void main(String [] args) {
try {
OperationImpl une_op = new OperationImpl ();
Naming.rebind("rmi://localhost/Operation",une_op) ;
System.out.println("Serveur pret");
}
catch (RemoteException re) { System.out.println(re) ; }
catch (MalformedURLException e) { System.out.println(e) ; }
}
}
grant {
permission java.net.SocketPermission
"*:80-65535","connect,accept,listen,resolve";
permission java.security.AllPermission;
};
Client.java : le code utilisant l'interface Operation.
Avant d'invoquer une méthode de l'interface, il faut obtenir une référence. Comme pour le serveur, deux classes permettent de le faire :
import java.rmi.* ; import java.net.MalformedURLException ; import java.io.*; public class Client { public static void main(String [] args) { if (args.length != 1) System.out.println("Usage : java Client Serveur"); else { try { Operation o = (Operation) Naming.lookup("//"+args[0]+"/Operation"); System.out.println("Client : 33 + 45 = ? "); int r = o.addition( 33 , 45 ); System.out.println("Le serveur a calcule : 33+45="+ r ); } catch (NotBoundException re) { System.out.println(re) ; } catch (RemoteException re) { System.out.println(re) ; } catch (MalformedURLException e) { System.out.println(e) ; } } } }
cd serveur
rmiregistry & # (si pas lance par programme serveur)
java -Djava.security.policy=java.policy Serveur
cd client
java Client localhost
Lancer "start rmiregistry" sous windows, ou monopoliser un terminal pour lui.
Lancer le rmiregistry dans le màªme répertoire que le serveur (o๠se trouve OperationImpl_Stub.class) est bien commode car le CLASSPATH contient le répertoire courant par défaut. Si vous lancez rmiregistry ailleurs, il faut que le CLASSPATH pointe vers o๠se trouve OperationImpl_Stub.class.
RMI permet de passer en paramètres des méthodes, des types simples, mais aussi des objets construits.
Comme précédemment, les arguments passés doivent être serializable.
La sérialisation écrit la structure de l'objet dans un flux transmissible sur le réseau, mais n'incorpore pas au flux les méthodes.
|
|
Comme précédemment, on doit spécifier au client l'interface offerte par l'objet distant. On la nomme Compute.java.
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Compute extends Remote {
Object executeTask(Task t) throws RemoteException;
}
L'unique méthode accepte un objet de classe Task.
import java.rmi.server.UnicastRemoteObject ; import java.rmi.RemoteException ; import java.net.* ; public class ComputeImpl extends UnicastRemoteObject implements Compute { public ComputeImpl() throws RemoteException { /* constructeur de la classe mere UnicastRemoteObject */ super(); } public Object executeTask(Task t) { /* les objets passes en arg. doivent fournir une methode execute() */ return t.execute(); } }
L'interface Task.java : spécifie
import java.io.Serializable;
public interface Task extends Serializable {
Object execute();
}
L'implémentation TaskPi.java:
import java.rmi.*; import java.math.*; /* Un code qui utilise l'objet remote Compute doit (1) obtenir une reference vers cet objet, (2) creer un objet Task, (3) appeler l'execution de la tache en passant la tache a Compute */ public class Client { public static void main(String args[]) { if (System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager()); } try { String name = "rmi://" + args[0] + "/Compute"; /* obtenir une ref. sur un objet Compute */ Compute engine = (Compute) Naming.lookup(name); /* instancie un objet de calcul */ TaskPi ma_tache = new TaskPi(Integer.parseInt(args[1])); /* faire calculer la tache envoyee et recuperer */ BigDecimal pi = (BigDecimal) (engine.executeTask(ma_tache)); System.out.println(pi); } catch (Exception e) { System.err.println("Exception du client : " + e.getMessage()); e.printStackTrace(); } } }
import java.rmi.*;
import java.net.*;
public class Server {
public static void main(String [] args) {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
try {
ComputeImpl engine1 = new ComputeImpl ();
Naming.rebind("rmi://localhost/Compute",engine1) ;
System.out.println("Objet Compute enregistré dans le registry.");
System.out.println("Serveur pret.");
}
catch (RemoteException re) { System.out.println(re) ; }
catch (MalformedURLException e) { System.out.println(e) ; }
}
}
Comme dans l'exemple de base, le serveur instancie un objet ComputeImpl et l'enregistre dans le registry sous l'identifiant Compute.
L'une des principales difficultés de RMI est la gestion de la sécurité.
-Djava.rmi.server.codebase=
http://icps.u-strasbg.fr/~genaud/codebase/
| client | serveur |
|---|---|
| Sources | |
| Client.java | Server.java |
| Task.java | Task.java |
| TaskPi.java | |
| Operation.java | Operation.java |
| OperationImpl.java | |
| javac *.java | |
| TaskPi.class | |
| OperationImpl.class | |
| OperationImpl_Skel.class | |