Communication en mode connecté modèle client-serveur                  

Communication en mode connecté modèle client-serveur

     

Le système RMI permet qu'un objet d'un programme s'exécutant dans une certaine machine virtuelle puisse invoquer une méthode d'un objet d'un programme tournant dans une autre machine virtuelle distante ; autrement dit, il permet de faire communiquer des objets distants.

Nous allons expliciter un exemple, fondé sur un modèle client-serveur, dont l'objectif est qu'un client puisse demander au serveur de trier un tableau d'Object par un appel à une méthode du serveur. On définit plusieurs classes et une interface.

Une interface de communication

L'interface Trieur donne le prototype d'une méthode servant à trier un tableau de Comparable<Object> (c'est-à-dire d'instances d'une classe implémentant l'interface java.lang.Comparable<Object>) ; elle étend l'interface Remote du paquetage java.rmi. Une interface telle que notre interface Trieur existe toujours lorsqu'on fait communiquer deux programmes par RMI : elle contient les prototypes de toutes les méthodes qui pourront être invoquées à distance. Nous l'appellerons interface de communication.
public interface Trieur extends java.rmi.Remote {
  public Comparable[] trier(Comparable[] tableau) throws java.rmi.RemoteException;
}

Côté serveur

La classe ServeurTri implémente l'interface Trieur (et donc aussi l'interface Remote) ; elle est destinée à trier à distance des tableaux de Comparable ; elle étend la classe java.rmi.server.UnicastRemoteObject. Cette dernière classe apporte les fonctionnalités nécessaires pour effectuer une communication d'un site à un autre fondée sur le système de communication par sockets de RMI. La méthode main de la classe ServeurTri reçoit éventuellement, en argument, un numéro de port. Le constructeur de la classe ServeurTri fait appel au constructeur de sa superclasse ; celui-ci exporte l'instance créée, ce qui signifie qu'il la met à l'écoute d'appels distants d'un éventuel client.
import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

@SuppressWarnings("serial")
public class ServeurTri extends UnicastRemoteObject implements Trieur {
	public ServeurTri() throws RemoteException {}
    
	public Comparable[] trier(Comparable[] tableau) {
		System.out.println("je trie");
		java.util.Arrays.sort(tableau);
		return tableau;
	}

	public static void main(String[] arg) {
	    String port;

	    if (arg.length == 0) port = "";
	    else port = arg[0];
		if (System.getSecurityManager() == null)
			System.setSecurityManager(new RMISecurityManager());
		try {
		    String nom = "trieur";
		    Trieur serveurTri = new ServeurTri();
		    Registry registry = LocateRegistry.getRegistry();
		    registry.rebind(nom, serveurTri);
		    System.out.println("Enregistrement du trieur ");
		}
		catch (RemoteException e) {e.printStackTrace();
		}
	}
}


	La classe Eleve modélise un élève, ne possède qu'un attribut qui est une note (dans la pratique, un élève aurait sans doute aussi un nom) et implémente l'interface Comparablee<Object> ; elle implémente aussi l'interface Serializable.

public class Eleve implements Comparable, java.io.Serializable {
	private int note;

	public Eleve(int note) {
		this.note = note;
	}

	public int getNote() {
		return note;
	}

	public void setNote(int note) {
		this.note = note;
	}

	public int compareTo(Object objet) {
	         Eleve autreEleve = (Eleve)objet;
		if (note < autreEleve.note) return -1;
		else if (note == autreEleve.note) return 0;
		else return 1;
	}
}

	La classe ClientTri permet de trier des élèves selon leur note ; on imagine que la machine sur laquelle s'exécute la méthode main de cette classe dispose de peu de puissance de calcul et qu'en conséquence, on préfegrave;re demander à un serveur de type ServeurTri d'effectuer le tri ; il faudra transmettre à la méthode de tri invoquée à distance un tableau contenant les élèves ; c'est pour cette raison que la classe Eleve doit implémenter l'interface Serializable. La méthode main de cette classe reçoit en argument le nom de la machine sur lequel le programme serveur s'exécute suivi éventuellement du symbole "'" et du même numéro de port que celui indiqué par le programme serveur.

import java.rmi.NotBoundException;
import java.rmi.NotBoundException;
import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Random;


public class ClientTri {
	public static void main(String[] arg) {
		if (System.getSecurityManager() == null)
			System.setSecurityManager(new RMISecurityManager());
		try {
		    String nom = "trieur";
		    Registry registry = LocateRegistry.getRegistry(arg[0]);
		    Trieur serveurTri = (Trieur) registry.lookup(nom);

			Random alea = new Random();
			int nbEleves = 10;
			Eleve[] eleves = new Eleve[nbEleves];
			int i;

			for (i = 0; i < nbEleves; i++) {
				eleves[i] = new Eleve(Math.abs(alea.nextInt())%21);
				System.out.print(eleves[i].getNote ()+ " ");
			}
			System.out.println();
			eleves = (Eleve[]) serveurTri .trier(eleves);
			for  (Eleve eleve : eleves) System.out.print(eleve.getNote() + " ");
			System.out.println();
		}
		catch (NotBoundException e) {
			System.out.println("le nom du programme serveur " + "n'est pas enregistre");
		}
		catch (RemoteException e) {
			System.out.println("Probleme de connexion a distance : " + e.getMessage());
		}
	}
}
Lorsqu'on lance le serveur, on voit s'afficher :
Enregistrement du trieur
Lorsqu'on lance un client, le serveur ajoute :
je trie
pendant que le client affiche par exemple :
8 18 14 18 16 17 10 5 19 11 
5 8 10 11 14 16 17 18 18 19
Dans notre exemple, le client fait appel aux méthodes du serveur, et l'inverse ne s'y fait pas ; si on souhaite avoir des appels distants bidirectionnels, il faut que le client (resp. un objet du client) implémente aussi une interface qui étend l'interface Remote et qui déclare les méthodes qui pourront être appelées par le serveur et par ailleurs que le client (resp. l'objet du client) soit exporté grâce à la méthode statique exportObject de la classe UnicastRemoteObject. Afin de pouvoir être (resp. que l'objet puisse être) invoqué par le serveur, le client peut alors, à l'aide d'une méthode distante adéquate, indiquer au serveur sa référence (resp. la référence de l'objet).

Nous allons voir comment mettre en œuvre l'application décrite dans les paragraphes précédents.

On compile d'abord les différentes classes.

Pour exécuter les deux programmes, serveur et client, il est nécessaire de spécifier un fichier de permissions par une option au moment de l'exécution. Nous avons choisi ici de donner toute permission en utilisant le fichier de permissions ci-dessous :

grant codebase "file:/infres/mic2/charon/public_html/coursJava/reseau/RMITri/" {
	permission java.security.AllPermission; 
};
Nous supposerons que les répertoires d'où on lance d'une part le serveur et d'autre part le client possèdent chacun un exemplaire du fichier java.permissions.

Avant de lancer le serveur, il faut lancer le programme d'enregistrement sur la machine où tournera le programme serveur RMI, par la commande :

  • rmiregistry sous Solaris ou Unix,
  • start rmiregistry sous Microsoft Windows
Grâce à l'option -D qui permet de spécifier une propriété système, on indiquera, le fichier de permissions par la propriété java.security.policy ;

On lance alors le serveur, par exemple sur la machine zadig.enst.fr où tourne déjà rmiregistry, par la commande :
java -Djava.security.policy=java.permissions Serveurtri

On lance le programme client d'une machine quelconquemais d'un répertoire où se trouvent tous le bytecode nécessaire ainsi aus le fin=chier de permissions, par la commande :
java -Djava.security.policy=java.permissions ClientTri zadig.enst.fr

Par cette façon de faire, il faut que le serveur dispose aussi de la classe Eleve, puisqu'il en a besoin pour trier le tableau des élèves.

D'autres propriétés, java.rmi.server.codebase et java.rmi.server.hostname peuvent être spécifiées.

Les différents fichiers

On peut aussi consulter : ce tutorial pour plus de détails.


ServeurTri() : ce constructeur fait appel au constructeur de la classe UnicastRemoteObject (qui est la superclasse de la classe ServeurTri) qui met l'objet serveur « à l'écoute » d'éventuels appels distants sur un port anonyme de la machine.
Comparable[] trier(Comparable[] tableau) : il s'agit de la méthode qui peut être invoquée à distance ; lors d'un appel distant, le tableau ne va pas être passé par référence mais par copie du contenu ; il est donc obligatoire de renvoyer le tableau résultant du tri.
System.setSecurityManager(new RMISecurityManager()) : il est nécessaire d'utiliser un gestionnaire de sécurité.
Registry registry = LocateRegistry.getRegistry();
registry.rebind(nom, serveurTri);
:
ces deux lignes servent à enregistrer l'objet serveur sur le registre RMI, objet Java qui permet d'associer un objet (ou plus précisément la référence d'un objet) à une chaîne de caractères ; ce registre est géré par un « programme d'enregistrement RMI » qui doit tourner sur la machine serveur ; l'objet serveur sera ici associé à la chaîne de caractères indiquée par le premier argument de la méthode rebind ; cet enregistrement permet à un programme client d'obtenir (nous verrons comment) un accàs à l'objet trieur en indiquant la chaîne de caractères qui lui est associée.
Trieur serveurTri = (Trieur) registry.lookup(nom) : le nom indiqué en argument doit ê celui sous lequel on a enregistré (par la méthode rebind) le programme serveur (dans le registre RMI de la machine distante),
eleves = (Eleve[])serveur.trier(eleves); : il s'agit de l'appel à la méthode distante de tri. Il est nécessaire de récupérer le tableau renvoyé par la méthode car le tableau initial n'est pas trié puisqu'il a été transmis par recopie de son contenu.

© Irène Charon, TéléParisTech 2011