Vous êtes sur la page 1sur 5

JAVA RMI

I. JAVA RMI (Remote Method Invocation) permet de créer des applications réseau aux

caractéristiques suivantes :

1. Les applications client/serveur sont des applications Java aux deux extrémités de la communication

2. Le client peut utiliser des objets situés sur le serveur comme s’ils étaient locaux

3. La couche réseau devient transparente : les applications n’ont pas à se soucier de la façon dont sont transportées les informations d’un point à un autre.

Le dernier point est un facteur de portabilité : si la couche réseau d’une application RMI venait à changer, l’application elle-même n’aurait pas à être réécrite. Ce sont les classes RMI du langage Java qui devront être adaptées à la nouvelle couche réseau.

II. PRINCIPE DE COMMUNICATION RMI

Le principe d’une communication RMI est le suivant :

1. Une application Java classique est écrite sur une machine A. Elle va jouer le rôle de serveur. Pour ce

faire certains de ses objets vont faire l’objet d’une « publication » sur la machine A sur laquelle l’application s’exécute et deviennent alors des services.

2. Une application Java classique est écrite sur une machine B. Elle va jouer le rôle de client. Elle aura

accès aux objets/services publiés sur la machine A, c’est à dire que via une référence distante, elle va pouvoir les manipuler comme s’ils étaient locaux. Pour cela, elle aura besoin de connaître la structure de l’objet distant auquel elle veut accéder (méthodes & propriétés).

II. Etude d’un exemple

Nous prenons une application que l’on trouve dans de nombreux ouvrages sur RMI : le client invoque une unique méthode d’un objet distant qui lui renvoie alors une chaîne de caractères. Nous présentons ici, une légère variante : le serveur fait l’écho de ce que lui envoie le client.

1. L’application serveur

Étape 1 : l’interface de l’objet/serveur

Un objet distant est une instance de classe qui doit implémenter l’interface Remote définie dans le package java.rmi. Les méthodes de l’objet qui seront accessibles à distance sont celles déclarées dans une interface dérivée de l’interface Remote :

import java.rmi.*; // l'interface distante public interface interEcho extends Remote{

public String echo(String msg) throws java.rmi.RemoteException;

}

Ici, on déclare donc une interface interEcho déclarant une méthode echo comme accessible à distance. Cette méthode est susceptible de générer une exception de la classe RemoteException, classe qui regroupe tous les erreurs liées au réseau.

Étape 2 : écriture de l’objet serveur

Dans l’étape suivante, on définit la classe qui implémente l’interface distante précédente. Cette classe doit être dérivée de la classe UnicastRemoteObject, classe qui dispose des méthodes autorisant l’invocation de méthodes à distance.

import java.rmi.*; import java.rmi.server.*; import java.net.*; // classe implémentant l’écho distant public class srvEcho extends UnicastRemoteObject implements interEcho{ // constructeur public srvEcho() throws RemoteException{ super(); }// fin constructeur // méthode réalisant l’écho public String echo(String msg) throws RemoteException{ return "[" + msg + "]"; }// fin écho }// fin classe

Dans la classe précédente, nous trouvons :

1. la méthode qui fait l’écho

2. un constructeur qui ne fait rien si ce n’est appeler le constructeur de la classe mère. Il est là pour

déclarer qu’il peut générer une exception de type RemoteException.

Nous allons créer une instance de cette classe avec une méthode main. Pour qu’un objet/service soit accessible de l’extérieur, il doit être créé et enregistré dans l’annuaire des objets accessibles de l’extérieur. Un client désirant accéder à un objet distant procède en effet de la façon suivante :

1. il s’adresse au service d’annuaire de la machine sur laquelle se trouve l’objet qu’il désire. Ce service

d’annuaire opère sur un port que le client doit connaître (1099 par défaut). Le client demande à l’annuaire, une référence d’un objet/service dont il donne le nom. Si ce nom est celui d’un objet/service de l’annuaire, celui-ci renvoie au client une référence via laquelle le client va pouvoir dialoguer avec l’objet/service distant.

2. à partir de ce moment, le client peut utiliser cet objet distant comme s’il était local

Pour en revenir à notre serveur, nous devons créer un objet de type srvEcho et l’enregistrer dans l’annuaire des objets accessibles de l’extérieur. Cet enregistrement se fait avec la méthode de classe rebind de la classe Naming :

Naming.rebind(String nom, Remote obj) avec nom le nom qui sera associé à l’objet distant obj l’objet distant

Notre classe srvEcho devient donc la suivante :

import java.rmi.*; import java.rmi.server.*; import java.net.*; // classe implémentant l’écho distant public class srvEcho extends UnicastRemoteObject implements interEcho{ // constructeur public srvEcho() throws RemoteException{ super(); }// fin constructeur // méthode réalisant l’écho public String echo(String msg) throws RemoteException{ return "[" + msg + "]"; }// fin écho // création du service public static void main (String arg[]){ try{ srvEcho serveurEcho=new srvEcho(); Naming.rebind("srvEcho",serveurEcho); System.out.println("Serveur d’écho prêt");

} catch (Exception e){ System.err.println(" Erreur " + e + " lors du lancement du serveur d’écho ");

}

}// main }// fin classe

Lorsqu’on lit le programme précédent, on a l’impression qu’il va s’arrêter aussitôt après avoir créé et enregistré le service d’écho. Ce n’est pas le cas. Parce que la classe srvEcho est dérivée de la classe UnicastRemoteObject, l’objet créé s’exécute indéfiniment : il écoute les demandes des clients sur un port anonyme c’est à dire choisi par le système selon les circonstances. La création du service est asynchrone :

dans l’exemple, la méthode main crée le service et continue son exécution : elle affichera bien « Serveur d’écho prêt ».

Étape 3 : compilation de l’application serveur

Au point où on en est, on peut compiler notre serveur. Nous compilons le fichier interEcho.java de l’interface interEcho ainsi que le fichier srvEcho.java de la classe srvEcho. Nous obtenons les fichiers .class correspondant : interEcho.class et srvEcho.class.

2. L’application Client

Étape 4 : écriture du client

On écrit un client à qui on passe en paramètre l’URL du serveur d’écho et qui

1. lit une ligne tapée au clavier

2. l’envoie au serveur d’écho

3. affiche la réponse que celui-ci envoie

4. reboucle en 1 et s’arrête lorsque la ligne tapée est « fin ». Cela donne le client suivant :

import java.rmi.*; import java.io.*; public class cltEcho { public static void main(String arg[]){ // syntaxe : cltEcho URLService // vérification des arguments

if(arg.length!=1){

System.err.println("Syntaxe : pg url_service_rmi");

System.exit(1);

}

// dialogue client-serveur String urlService=arg[0]; BufferedReader in=null; String msg=null; String reponse=null; interEcho serveur=null; try{ // ouverture du flux clavier in=new BufferedReader(new InputStreamReader(System.in)); // localisation du service serveur=(interEcho) Naming.lookup(urlService); // boucle de lecture des msg à envoyer au serveur d'écho System.out.print("Message : "); msg=in.readLine().toLowerCase().trim(); while(! msg.equals("fin")){ // envoi du msg au serveur et réception de la réponse reponse=serveur.echo(msg); // suivi System.out.println("Réponse serveur : " + reponse);

// msg suivant System.out.print("Message : "); msg=in.readLine().toLowerCase().trim(); }// while // c'est fini

System.exit(0);

// gestion des erreurs } catch (Exception e){ Rmi 307 System.err.println("Erreur : " + e);

System.exit(2);

}// try

}// main

}// classe

Il n’y a rien de bien particulier dans ce client si ce n’est l’instruction qui demande une référence du serveur

serveur=(interEcho) Naming.lookup(urlService);

On se rappelle que notre service d’écho a été enregistré dans l’annuaire des services de la machine où il se trouve avec l’instruction :

Naming.rebind("srvEcho",serveurEcho);

Le client utilise donc lui aussi une méthode de la classe Naming pour obtenir une référence du serveur

qu’il veut utiliser. La méthode lookup utilisée admet comme paramètre l’url du service demandé. Celle-ci a

la forme d’une url classique :

rmi://machine:port/nom_service avec rmi facultatif - protocole rmi machine nom ou adresse IP de la machine sur laquelle opère le serveur d’écho - facultatif, par défaut localhost. port port d’écoute du service d’annuaire de cette machine - facultatif, par défaut 1099 nom_service nom sous lequel a été enregistré le service demandé (srvEcho pour notre exemple)

Ce qui est récupéré, c’est une instance de l’interface distante interEcho. Si on suppose que le client et le serveur ne sont pas sur la même machine, lorsqu’on compile le client cltEcho.java, on doit disposer dans le même répertoire, du fichier interEcho.class, résultat de la compilation de l’interface distante interEcho, sinon on aura une erreur de compilation sur les lignes qui référencent cette interface.

Étape 5 : génération des fichiers .class nécessaires à l’application client-serveur

Afin de bien comprendre ce qui est du côté serveur et ce qui est du côté client, on mettra le serveur dans un répertoire echo\serveur et le client dans un répertoire echo\client. Le répertoire du serveur contient les fichiers source suivants :

E:\data\java\RMI\echo\serveur>dir INTERE~1 JAV 158 09/03/99 15:06 interEcho.java SRVECH~1 JAV 759 09/03/99 15:07 srvEcho.java SRVECH~1 CLA 1 129 09/03/99 15:58 srvEcho.class INTERE~1 CLA 256 09/03/99 15:58 interEcho.class

Dans le répertoire du client, on trouve le fichier source suivant :

E:\data\java\RMI\echo\client>dir CLTECH~1 JAV 1 427 09/03/99 16:08 cltEcho.java INTERE~1 CLA 256 09/03/99 15:59 interEcho.class CLTECH~1 CLA 1 506 09/03/99 16:08 cltEcho.class

Si on tente d’exécuter le client cltEcho ou le serveur srvEcho, on obtient l’erreur suivante :

E:\data\java\RMI\echo\client>j:\jdk12\bin\java cltEcho rmi://localhost/srvEcho Erreur : java.rmi.UnmarshalException: error unmarshalling return; nested exception is:

java.lang.ClassNotFoundException: srvEcho_Stub

Dans les deux cas, la machine virtuelle Java indique qu’elle n’a pas trouvé la classe srvEcho_stub. Effectivement, nous n’avons encore jamais entendu parler de cette classe. Dans le client, la localisation du serveur s’est faite avec l’instruction suivante :

serveur=(interEcho) Naming.lookup(urlService);

Ici, urlservice est la chaîne rmi://localhost/srvEcho avec

rmi protocole rmi localhost machine où opère le serveur - ici la même machine sur laquelle le client. La syntaxe est normalement machine:port. En l’absence du port, c’est le port 1099 qui sera utilisé par défaut. A l’écoute de ce port, se trouve le service d’annuaire du serveur. srvEcho c’est le nom du service particulier demandé

A la compilation, aucune erreur n’avait été signalée. Il fallait simplement que le fichier interEcho.class de

l’interface distante soit disponible. A l’exécution, la machine virtuelle réclame la présence d’un fichier srvEcho_stub.class si le service demandé est le service srvEcho, de façon générale un fichier X_stub.class pour un service X. Ce fichier n’est nécessaire qu’à l’exécution pas à la compilation du client. Il en est de même pour le serveur. Qu’est-ce donc que ce fichier ? Sur le serveur, se trouve la classe srvEcho.class qui est notre objet/service distant. Le client, s’il n’a pas besoin de cette classe, a néammoins besoin d’une sorte d’image d’elle afin de pouvoir communiquer avec. En fait, le client n’adresse pas directement ses requêtes à l’objet distant : il les adresse à son image locale srvEcho_stub.class située sur la même machine que lui. Cette image locale srvEcho_stub.class dialogue avec une image de même nature (srvEcho_stub.class) située cette fois sur le serveur. Cette image est créée

à partir du fichier .class du serveur avec un outil de Java appelé rmic. Sous Windows, la commande :

E:\data\java\RMI\echo\serveur>j:\jdk12\bin\rmic srvEcho

Il y a bien là, le fichier srvEcho_stub.class dont le client et le serveur ont besoin à l’exécution. On fait une copie du fichier srvEcho_stub.class dans le répertoire du client et du serveur. On a donc les fichiers suivants :

Du côté serveur :

E:\data\java\RMI\echo\serveur>dir *.class SRVECH~1 CLA 1 129 09/03/99 15:58 srvEcho.class INTERE~1 CLA 256 09/03/99 15:58 interEcho.class SRVECH~1 CLA 3 264 09/03/99 16:01 srvEcho_Stub.class

Du côté client :

E:\data\java\RMI\echo\client>dir *.class CLTECH~1 CLA 1 506 09/03/99 16:08 cltEcho.class INTERE~1 CLA 256 09/03/99 15:59 interEcho.class SRVECH~1 CLA 3 264 09/03/99 16:01 srvEcho_Stub.class

Étape 6 : Exécution de l’application client-serveur d’écho

Il faut tout d’abord lancer notre application serveur. On se rappelle que celle-ci :

crée le service

l’enregistre dans l’annuaire des services de la machine sur laquelle opère le serveur d’écho

Ce dernier point nécessite la présence d’un service d’annuaire. Celui-ci est lancé par la commande :

start j:\jdk12\bin\rmiregistry

rmiregistry est le service d’annuaire. Il est ici lancé en tâche de fond dans une fenêtre Dos de Windows par la commande start. L’annuaire actif, on peut créer le service d’écho et l’enregistrer dans l’annuaire des services. Là encore, il est lancé en tâche de fond par une commande start :

E:\data\java\RMI\echo\serveur>start j:\jdk12\bin\java srvEcho

Le serveur d’écho s’exécute dans une nouvelle fenêtre DOS et affiche comme on le lui avait demandé :

Serveur d’écho prêt. Il ne nous reste plus qu’à lancer et tester notre client :

E:\data\java\RMI\echo\client>j:\jdk12\bin\java cltEcho rmi://localhost/srvEcho Message : msg1 Réponse serveur : [msg1] Message : msg2 Réponse serveur : [msg2] Message : fin