RMI - Remote method invocation Sites sur le même sujet Télécharger des documentations

Introduction à RMI

RMI (Remote Method Invocation) est une API Java permettant de manipuler des objets distants (c'est-à-dire un objet instancié sur une autre machine virtuelle, éventuellement sur une autre machine du réseau) de mani&egrav;re transparente pour l'utilisateur, c'est-à-dire de la même façon que si l'objet était sur la machine virtuelle (JVM) de la machine locale.

Ainsi un serveur permet à un client d'invoquer des méthodes à distance sur un objet qu'il instancie. Deux machines virtuelles sont donc nécessaires (une sur le serveur et une sur le client) et l'ensemble des communications se fait en Java.

On dit généralement que RMI est une solution "tout Java", contrairement à la norme Corba de l'OMG (Object Management Group) permettant de manipuler des objets à distance avec n'importe quel langage. Corba est toutefois beaucoup plus compliqué à mettre en oeuvre, c'est la raison pour laquelle de nombreux développeurs se tournent généralement vers RMI.

Caractéristiques de RMI

Structure des couches RMI

Les connexions et les transferts de données dans RMI sont effectuées par Java sur TCP/IP grâce à un protocole propriétaire (JRMP, Java remote Method Control) sur le port 1099.
A partir de Java 2 version 1.3, les communications entre client et serveur s'effectuent grâce au protocole RMI-IIOP (Internet Inter-Orb Protocol), un protocole normalisé par l'OMG (Object Management Group) et utilisé dans l'architecture CORBA.

La transmission de données se fait à travers un système de couches, basées sur le modèle OSI afin de garantir un interopérailité entre les programmes et les versions de Java.

structure des couches RMI

Ainsi, une application client-serveur basé sur RMI met ainsi en oeuvre trois composantes :

Architecture de RMI

L'architecture de RMI est schématisé ci-dessous :

Lorsqu'un objet instancié sur une machine cliente désire accèder à des méthodes d'un objet distant, il effectue les opérations suivantes :

  1. il localise l'objet distant grâce à un service de désignation : le registre RMI
  2. il obtient dynamiquement une image virtuelle de l'objet distant (appelée stub ou souche en français). Le stub possède exactement la même interface que l'objet distant.
  3. Le stub transforme l'appel de la méthode distante en une suite d'octets, c'est ce que l'on appelle la sérialisation, puis les transmet au serveur instanciant l'objet sous forme de flot de données. On dit que le stub "marshalise" les arguments de la méthode distante.
  4. Le squelette instancié sur le serveur "désérialise" les données envoyées par le stub (on dit qu'il les "démarshalise"), puis appelle la méthode en local
  5. Le squelette récupère les données renvoyées par la méthode (type de base, objet ou exception) puis les marshalise
  6. le stub démarshalise les données provenant du squelette et les transmet à l'objet faisant l'appel de méthode à distance

    Mise en oeuvre de RMI

    Pour créer une application avec RMI il suffit de procéder comme suit :

    1. définir la classe distante. Celle-ci doit dériver de java.rmi.server.UnicastRemoteObject (utilisant elle-même les classes Socket et SocketServer, permettant la communication par protocole TCP)
    2. définir l'interface pour la classe distante. Celle-ci doit implémenter l'interface java.rmi.Remote et déclarer les méthodes publiques globales de l'objet, c'est-à-dire les méthodes partageables. De plus ces méthodes doivent pouvoir lancer une exception de type java.rmi.RemoteException.
    3. créer les classes pour le stub et le squelette grâce à la commande rmic
    4. Lancer le registre RMI et lancer l'application serveur, c'est-à-dire instancier l'objet distant. Celui-ci lors de l'instanciation créera un lien avec le registre
    5. Créer un programme client capable d'accèder à aux méthodes d'un objet sur le serveur grâce à la méthode Naming.lookup()
    6. Compiler l'application cliente
    7. Instancier le client

    Spécifier l'interface de l'objet distant

    La première chose à faire consiste à identifier les méthodes de l'objet qui seront accessibles à distance (on parle d'interface), celles-ci devant être publiques et retourner un type primaire ou bien un objet sérialisable. De plus ces méthodes doivent pouvoir lever l'exception java.rmi.RemoteException.

    Voici l'interface d'un objet possèdant une seule méthode retournant une chaˆne de caractères (objet String) :

    import java.rmi.* ; 
    
    public interface CCMInterface extends Remote {
      public String CCMDistant(String valeur) throws RemoteException ;
    }

    Implémentation de l'objet distant

    Il s'agit désormais de définir la classe implémentant l'interface définie précédemment. cette classe une fois instanciée sera l'objet distant.

    La classe distante doit donc

    • implémenter l'interface définie antérieurement
    • hériter de java.rmi.server.UnicastRemoteObject afin de pouvoir communiquer (elle doit donc importer java.rmi.server.*)
      public class CCM extends UnicastRemoteObject implements CCMInterface { }
    • posséder un constructeur sans arguments et pouvant renvoyer une exception de type RemoteException
      public CCM() throws RemoteException {
      	super();
      }
    • implémenter les méthodes définies dans l'interface, pouvant renvoyer une exception de type RemoteException
      public String CCMDistant(String valeur) throws RemoteException {
      	// Operations a effectuer...
      }
    • Enregistrer l'objet distant dans le registre RMI. Pour cela une solution consiste à l'enregistrer dans la méthode main() de l'objet, en effectuant les opérations suivantes:
      • déclarer un gestionnaire de sécurité :
        System.setSecurityManager(new RMISecurityManager());
      • Instancier l'objet distant grâce à son constructeur par défaut :
        ServeurRMIInterface sRI = new ServeurRMI();
      • Lier l'objet au registre RMI grâce à l'interface java.rmi.Naming.
        Naming.rebind("//NomDuServeur/Objet",sRI);
    Voici un exemple montrant l'implémentation de l'objet CCM :
    import java.rmi.*;
    import java.rmi.server.*;
    import CCMInterface;
    
    public class CCM extends UnicastRemoteObject implements ServeurRMIInterface {
    	public CCM() throws RemoteException {
    		super();
    	}
    
    	public String CCMDistant(String valeur) throws RemoteException {
    	return "Ca marche !!";
    	}
    
    	public static void main(String[] args) {
    		System.setSecurityManager(new RMISecurityManager());
    		try {
    			ServeurRMIInterface sRI = new ServeurRMI();
    			Naming.rebind("//NomDuServeur/CCMDistant",sRI);
    		}
    		catch (Exception e) {
    			// Gerer l'exception
    			System.out.println("Erreur lors du Bind:"+e.toString);
    		}
    	}
    }

    Générer stub et skeleton

    L'étape suivante consiste à créer le stub et le squelette.

    Le JDK fournit l'outil rmic permettant de générer le squelette et la souche à partir de la classe de l'objet distant. La syntaxe de rmic est très simple :

    rmic classObjetDistant
    dans notre cas :
    rmic CCM

    Implémentation du client

    L'implémentation du client doit se faire de telle façon que l'application obtienne une référence vers l'objet distant. Le reste des opérations (marshalling, récupération du stub, ...) est fait de manière transparente.

    Le client doit donc obtenir le stub auprès du registre RMI. Pour cela il s'agit d'utiliser la méthode lookup() de l'objet javax.rmi.Naming premettant de récupérer le stub en lui passant en paramètre le nom du serveur et de l'objet "partagé" sous la forme :

    //serveur/objet
    D'autre part, la méthode lookup() de l'objet Naming renvoie un objet, il faut ainsi convertir la référence générée en le type de l'interface de l'objet distant (qu'il faut importer dans la classe cliente) :
    InterfaceDistante ID = (InterfaceDistante)Naming.lookup("//serveur/objet")
    Il est ensuite possible d'accèder localement aux méthodes de l'objet distant par l'intermédiaire de son interface :
    Type resultat = (Type)ID.methodeDistante(parametre);

    Voici un exemple de client pouvant accèder à la méthode partagée de l'objet distant implémenté précédemment :

    import java.rmi.*
    import CCMInterface;
    
    public class CCMClient {
    
    	public static void main(String[] args) {
    		try {
    			CCMInterface CCMI = (CCMInterface)Naming.lookup("//serveur/objet");
    			String Valeur = CCMI.getValeur();
    		}
    		catch(Exception e) {
    			// Gerer les exceptions
    			System.out.println("Erreur lors du lookup:"+e.toString);
    		}
    	}
    }
    
    InterfaceDistante ID = (InterfaceDistante)Naming.lookup("//serveur/objet")

    Serveur de désignation (rmiregistry)

    Le registre RMI permet au client d'obtenir les références vers les objets distants. La syntaxe pour lancer rmiregistry est tout simplement :

    rmiregistry
    ou
    start rmiregistry
    Le processus tourne alors sur le
    port par défaut (1099) à l'écoute des requêtes du client.

    Il est également possible de spécifier un autre port (les ports de 1 à 1023 sont réservés, il faut donc choisir un port entre 1024 et 65535). La syntaxe de rmiregistry est :

    start rmiregistry numero_de_port

    Définir un fichier de stratégie

    Java par nature propose une sécurité renforcé ne lui permettant pas de communiquer avec des éléments extérieurs à la Java Virtual Machine. Ainsi le JDK1.1 empêcher la communication hors de la JVM (baptisée ironiquement "bac à sable). Le Java2 SDK propose un système permettant de définir des autorisations personnalisées grâce à un fichier appelé fichier de stratégie (en anglais policy file). Ce fichier (dont l'extension est .policy contient des lignes accordant des permissions sur les classes. Ainsi pour permettre à des classes de communiquer avec des éléments extérieurs il faut définir un fichier de stratégie contenant les lignes suivantes :

    grant {
    	permission java.net.SocketPermission "*:1024-65535","connect,accept,listen";
    	permission java.net.SocketPermission "*:80","connect";
    }
    Pour définir un accès restreint, il suffit de remplacer l'astérisque par le nom de domaine des ordinateurs autorisés à se connecter :
    grant {
    	permission java.net.SocketPermission ".commentcamarche.net:1024-65535","connect,accept,listen";
    	permission java.net.SocketPermission ".commentcamarche.net:80","connect";
    }

    Compilation et exécution du client

    Une fois le client implémenté, il suffit de le compiler : Puis de l'exécuter :

    Compilation et exécution

    L'étape suivante consiste à compiler le serveur :

    javac CCM.java
    Puis suit l'exécution du serveur. Toutefois cette étape est critique pour plusieurs raisons :
    • Le stub doit être accessible, c'est-à-dire qu'il doit être dans le CLASSPATH ou bien chargeable par HTTP ou par URL locale grâce à un paramètre en ligne de commande :
      java [...] -Djava.rmi.server.codebase=URL
    • Le serveur doit être associé à un fichier de stratégie (portant l'extension .policy) indiquant les ressources pouvant accèder à l'objet distant, puis spécifié sur la ligne de commande :
      java [...] -Djava.security.policy=emplacement
    Ainsi l'exécution de l'objet se fera par une ligne de commande de la forme :
    java 
    -Djava.security.policy=./monfichier.policy
    -Djava.rmi.server.codebase=file:/c:\jdk1.2.2\lib\
    CCM
    en supposant que le stub se situe dans le répertoire c:\jdk1.2.2\lib\ et que le fichier de stratégie se trouve dans le même répertoire que les classes. Si le stub est placé sur un serveur HTTP, la ligne de commande pourra être :
    java 
    -Djava.security.policy=./monfichier.policy
    -Djava.rmi.server.codebase=http://www.commentcamarche.net/stub/
    CCM

    Conclusion sur RMI


    © Copyright 2001 Jean-François Pillou
    Ce document issu de CommentCaMarche.net est soumis à la licence GNU FDL.Permission vous est donnée de distribuer, modifier des copies de cette page tant que cette note apparaît clairement.