Programmation Client / Serveur Licence Professionnelle CMSII Cours 4 Luiz Angelo Steffenel luiz-angelo.steffenel@univ-reims.fr
Plan du Cours Rappel des mécanismes utilisés Présentation de Java RMI
Client/Serveur les sockets Connexion directe entre client/serveur Conversion des données à charge du développeur
Client/Serveur RPC Connexion entre client/serveur via un proxy Conversion des données à charge du proxy
Objets Distribués Évolution du mécanisme RPC Communication avec des objets distants Localisation des objets distants Transmission d'objets et chargement dynamique de classes
Objets Distribués Java RMI
Java RMI Remote Method Invocation introduit dans Java 1.1 objectifs : moyens : facilité le développement d'application distribuées utilisation de la même syntaxe qu'une application non distribuée (locale) ensemble de classes (API) java.rmi ensemble d'outils de développement ensemble de services réseau Attention : utilisation restreinte à Java
RPC vs RMI RPC entités actives : processus interaction : appel de procédure distante serveur : processus distant les processus supportent des programmes RMI entités actives : objets distribués interaction : invocation de méthode distante serveur : processus distant les objets ont des interfaces distantes plusieurs objets par processus
L'Architecture RMI Utilisation d'interfaces définition de l'interface du côté client (stub) définition de l'implémentation serveur (skeleton) Service de registre des classes
Caractéristiques Masquage des encodages / dés-encodages et des communications méta-éléments (int, float...) locaux transmis par valeur objets locaux transmis par valeur paramètre objets distants transmis par référence Services chargement dynamique des souches garbage collector réparti objets serveurs activable
RMI côté Client Le stub (souche) Mise en forme des requêtes offre la même interface que l'objet serveur lié à un seul serveur génération d'un flot d'octets identification de la méthode Liaison de l'objet client avec l'objet distant gestion de la référence sur le serveur invocation de la méthode
Exemple : un catalogue Nous voulons créer un catalogue de produits, disponible dans le serveur Le client peut demander le prix des produits Pour cela, les objets du côte client (stub) et du côté serveur (squeleton) doivent partager la même interface Java
Interface Catalogue import java.rmi.*; public interface Catalogue extends Remote { } double getprix(string description) throws RemoteException; Les interfaces doivent toujours étendre l'interface Remote Tous les méthodes doivent lancer l'exception RemoteException Les paramètres doivent être sérialisables
Implémentation Côté Serveur import java.rmi.*; import java.rmi.server.*; import java.util.*; public class CatalogueImpl extends UnicastRemoteObject implements Catalogue { private Map<String, Double> lesprix; public CatalogueImpl() throws RemoteException { lesprix = new HashMap<String, Double>(); lesprix.put("barrette memoire 2Go", 24.95); lesprix.put("disque dur 1 To", 49.95); } public double getprix(string description) throws RemoteException { Double prix = lesprix.get(description); return prix == null? 0 : prix; } }
Explication Cette classe est la "cible" de l'appel car elle étend UnicastRemoteObject UnicastRemoteObject indique que l'objet est accessible via une adresse IP unique Si on ne peut pas étendre UnicastRemoteObject, il faut : 1.Créer une instance de l'objet distant 2.Faire appel à UnicastRemoteObject.exportObject(this, 0);
Le service RMI Registry Jusqu'à présent (Sockets, XML-RPC) on savait toujours l'adresse du serveur Codé "en dur" dans le code client Peu pratique si le serveur tombe en panne Empêche le client d'utiliser autre serveur qui offre les mêmes objets Solution : un annuaire d'objets (RMI Registry) On contacte l'annuaire pour avoir la référence à l'objet recherché La référence est un objet (stub) qui se charge de toute communication/conversion de données
Le RMI Registry Programme exécutable fourni pour toutes les plates-formes S'exécute sur un port (1099 par défaut) sur la machine serveur Pour des raisons de sécurité, seuls les objets résidant sur la même machine sont autorisés à lier/délier des références Un service de nommage est lui-même localisé à l'aide d'une URL
RMI Registry vu par le serveur Le serveur doit enregistrer au moins un objet dans l'annuaire RMI Registry Objet avec nom et adresse uniques Ex : rmi://regserv.maboite.com:99/catalogue_central Attention : Le RMI Registry est peu efficient lorsqu'on a trop d'objets enregistrés Il est recommandé d'enregistrer un objet "racine" qui après fait appel à d'autres objets Java se charge de la transmission des stubs au client
La classe Context Du package javax.naming permet de manipuler le RMIRegistry supporte des méthodes statiques permettant de lier des références d'objets serveur Délier des références d'objets serveur Context.unbind(...) Lister le contenu du Naming Context.bind(...) et Context.rebind(...) NamingEnumeratewNameClassPair> Context.list(String nom) Obtenir une référence vers un objet distant Context.lookup(String nom)
import java.rmi.*; import javax.naming.*; public class CatalogueServer { public static void main(string[] args) throws RemoteException, NamingException { System.out.println("Constructing server implementation..."); CatalogueImpl centralcatalogue = new CatalogueImpl(); System.out.println("Binding server implementation to registry..."); Context namingcontext = new InitialContext(); namingcontext.bind("rmi:central_catalogue", centralcatalogue); } } System.out.println("Waiting for invocations from clients...");
RMI Registry vu par le client Le client contacte l'annuaire RMI Registry à la recherche de l'objet initial (bootstrap) Utilisation de l'adresse de l'objet : rmi://regserv.maboite.com:99/catalogue_central Le client peut aussi rechercher la liste des objets enregistrés Utilisation de la méthode list Enumeration<NameClassPair> e = namingcontext.list("rmi://localhost/"); while (e.hasmoreelements()) System.out.println(e.nextElement().getName());
import java.rmi.*; import java.util.*; import javax.naming.*; public class CatalogueClient { public static void main(string[] args) throws NamingException, RemoteException { Context namingcontext = new InitialContext(); System.out.print("RMI registry bindings: "); Enumeration<NameClassPair> e = namingcontext.list("rmi://localhost/"); while (e.hasmoreelements()) System.out.println(e.nextElement().getName()); String url = "rmi://localhost/central_catalogue"; Catalogue centralcatalogue = (Catalogue) namingcontext.lookup(url); String descr = "disque dur 1 To"; double price = centralcatalogue.getprix(descr); System.out.println(descr + ": " + price); } }
Compiler et Déployer le code La méthode "complète" : Séparer le code client du code serveur Utiliser un serveur Web pour fournir le code serveur Lancer RMIRegistry Lancer le client Option ".jar" Au lieu d'utiliser un serveur Web, on peut tout mettre dans un.jar et indiquer ça lors du lancement
La méthode complète Note : il est fortement recommandé de faire ceci en ligne de commande (sans NetBeans) Compiler le code comme d'habitude Séparer les codes client et serveur Rendre l'interface disponible dans le serveur web Serveur/ CatalogueServer.class Catalogue.class CatalogueImpl.class Client/ CatalogueClient.class Catalogue.class Download/ Catalogue.class
La méthode complète Utiliser un serveur web(apache?) ou télécharger NanoHTTPD http://elonen.iki.fi/code/nanohttpd un serveur web dans une seule classe java Compiler NanoHTTPD et mettre le.class dans le répertoire Download Lancer le serveur Web Ouvrir un terminal (invité de commande) java NanoHTTPD 8080
La méthode complète Lancer RMI Registry Ouvrir un terminal (invité de commande) S'assurer que la variable CLASSPATH ne contient aucune entrée S'assurer que le répertoire ne contient aucune classe java rmiregistry (linux) start rmiregistry (Windows) ou javaw rmiregistry
La méthode complète Lancer le serveur Dans une troisième fenêtre de terminal, lancer : java -Djava.rmi.server.codebase=http://localhost:8080/ CatalogueServer Cette commande indique l'adresse du serveur web Le serveur web passera ce lien à rmiregistry Lancer le client Dans une quatrième fenêtre, lancer java CatalogueClient
L'option.jar Le tutoriel de Sun nous donne une autre possibilité qui n'utilise pas un serveur web Compiler l'interface Catalogue.java Créer un.jar à partir de Catalogue.class jar cvf Catalogue.jar Catalogue Compiler le serveur et le client faisant référence à ce.jar javac -cp Catalogue.jar CatalogueServer.java javac -cp Catalogue.jar CatalogueClient.java
L'option.jar Lancer rmiregistry Compiler le serveur et le client faisant référence à ce.jar Lancer le serveur javac -cp Catalogue.jar CatalogueServer.java javac -cp Catalogue.jar CatalogueClient.java java.;catalogue.jar -Djava.rmi.server.codebase= file:/c:/classes/catalogue.jar CatalogueServer Lancer le client java.;catalogue.jar -Djava.rmi.server.codebase= file:/c:/classes/ CatalogueClient
Et la suite? Ça peut paraître beaucoup trop complexe par rapport à XML-RPC mais RMI permet Le chargement dynamique de classes Si le serveur retourne une classe que le client ignore, celui-ci télécharge les classes nécessaires Excellente option pour éviter la surcharge de rmiregistry Il faut modifier les politiques de sécurité de Java L'activation automatique À présent, chaque serveur doit instancier les objets afin de les enregistrer Avec l'activation automatique, le serveur n'enregistre qu'un activateur L'activateur instancie les objets selon le besoin
Autour de RMI CORBA Common Object Request Broker Architecture Architecture qui permet l'appel de méthodes entre objets dans n'importe quel langage Utilise le protocole binaire IIOP (Internet Inter-ORB Protocol) pour la communication Architecture "lourde", de moins en moins utilisée Web Services Collection de protocoles (parfois peu compatibles) basés sur XML Utilise le Simple Object Access Protocol (SOAP) pour le transport d'objets
Références Core Java Volume 2 Par Cay S. Horstmann & Gary Cornell Editions CampusPress Une référence pour les développeurs Java Bonne section sur RMI, servi de base pour ce cours Le tutoriel de Sun http://java.sun.com/docs/books/tutorial/rmi/overview.html
Le projet Modifier le serveur de messagerie Utiliser Java RMI pour la communication entre le client et le serveur Modifier le protocole de manière à établir une session Le mot de passe n'est envoyé qu'une fois Une clé de session est utilisée pour toute autre communication