Le côté serveur
Transcription
Le côté serveur
Appels de méthodes distantes avec RMI CGC Master 2 TIIR – Janvier 2012 Bilel Derbel Objectifs (1/1) Construire des applications qui utilisent des objets situés sur des machines différentes et les faire communiquer Ces objets sont sollicités par des appels de méthodes initiés à distance : ‣ Appels qui transitent via le réseau pour être appliqués sur l’objet là où il réside. Application cliente: objDist.methode(...) Application Serveur: objDist Réseau, protocole TCP/IP Objectifs (2/2) Invoquer une méthode sur un objet distant de la même façon que pour un objet local Passer un objet distant en paramètre d’une méthode d’un objet local ou distant Récupérer le résultat d’un appel de méthode sous la forme d’un objet créé sur la machine distante Utiliser un objet distant sans connaître la machine où il se trouve, en demandant son adresse à un service dédié (à la DNS) C'est quoi RMI ? API Java permettant de faire communiquer des objets Java distribués sur un réseau – Paradigme client-serveur – Appels de méthodes à distances Un objet distant (serveur) est un objet dont les méthodes sont invoquées depuis une autre JVM (client) Un objet distant est caractérisé par une (ou plusieurs) interface(s) distante(s) qui décrit l’ensemble des services (méthodes) accessibles à distance Comment ca marche ? A tout objet distant vont être associés de façon transparentes 2 autres objets : ‣ Un «stub» ou «proxy» représente l’objet distant côté client (e.g., comment contacter la machine serveur) ‣ Un «squelette» (skeleton) qui intervient côté serveur pour recevoir les requêtes envoyées par le stub Application cliente: objDist.methode(...) Application Serveur: ObjDist Stub skeleton Réseau, protocole TCP/IP Étape 1 : le contrat Établir l’interface qui représente les services proposés par l’objet distant L’objet distant doit implanter cette interface L’interface d’un objet distant doit : ‣ Étendre l’interface Remote ‣ Permettre de déclencher l’exception RemoteException public interface ObjetDistant extends Remote { public ResultObject methodeDistante(ArgObject ...) throws RemoteException; } Étape 2 : Satisfaire le contrat Implémenter l'objet distant public class ObjetDistantImpl implements ObjetDistant { public ResultObject methodeDistante(ArgObject ...) throws RemoteException { … // traitement … // traitement retrun … ; } } public void methodeNonDistante(...) { … } Étape 3 : exposer l'objet distant Rendre disponible l'objet via le réseau: ‣ Première façon : public class ObjetDistantImpl extend UnicatRemoteObject implements ObjetDistant { public ObjetDistantImpl() {super();} public ResultObject methodeDistante(ArgObject ...) ... } } ‣ Deuxième façon : après la création de l'objet distant ObjetDistant objDist = new ObjectDistantImpl(); UnicastRemoteObject.export(objDist); Étape 4 : publier l'objet distant La commande rmiregistry crée un serveur de noms (écoute sur le port 1099 par défaut) La classe java.rmi.Naming permet d’accéder aux objets serveurs de noms, de publier des objets et d’accéder aux objets publiés sur les serveurs de noms : Naming.bind(«rmi://machine:port/monObjet»,objDist); Étape 5 : côté client Le client doit récupérer un stub pour pouvoir communiquer avec l'objet distant ‣ On utilise java.rmi.Naming ‣ Méthodes lookup et list ‣ On donne l'url utilisée dans le bind ‣ On appele la méthode distante comme si c'était un objet local ObjetDistant objDist = (ObjetDistant)Naming.lookup(«rmi://machine:port/monObjet»); ResultObject result = objDist.methodeDistante(...); Résumé Côté Serveur : ‣ Déclarer une interface : implements Remote ‣ Exposer l'objet : ↳ ↳ ‣ extends UnicastRemoteObject Ou UnicastRemoteObject.export(o) Publier l'objet : Naming.bind(url) ↳ rmiregistry & Côté Client : ‣ Récupérer l'objet distant ‣ obj = Naming.lookup(url) Invoquer la méthode distante comme d'habitude ↳ Remarques 1/2 Version jdk ‣ ≤1.4 : rmic pour générer les objet stub et squeleton ‣ ≥1.5 : génération automatique à la compilation Les serveur de noms sont des objets distants ! Utiliser la classe Registry ‣ ‣ LocateRegistry.createRegistry() LocateRegistry.getRegistry() Remarques 2/2 Passage de paramêtres entrée/sortie (I/O) d'une méthode distante ‣ Tous les paramètres sont passés par recopie et non par référence ‣ Tous les paramètres I/O doivent être sérialisable pour pouvoir être transférés sur le réseau ↳ ‣ class Parameter implements Serializable Les classes des objets paramètres doivent être connu côté client et côté serveur Les paramètres peuvent eux mêmes être des objets distants ! Conculsion RMI peut être vu comme une façon de faire communiquer des objets en mode client-serveur − Un maître et des esclaves − Plus généralement des entités de calculs Avantage de RMI − Une grande flexibilité − On décrit des interfaces, le reste est cachée − Gros et moyens grains Inconvénients − Synchronisme, non tolérance aux pannes, etc. − GRID-RPC, etc. Exemple : calcul parallèle des décimales de Pi • Considérons la formule suivante pour Pi • Il est bien entendu impossible de calculer exactement une somme infini ! • On désire calculer une valeur approché de Pi – En ignorant les erreures dues aux calculs flottants, cela revient à estimer la somme précédente à un rang m fixée – Plus le rang m est grand plus la valeur obtenue se rapproche de la vraie valeur de Pi Exemple : Pi en parallèle • On va diviser la somme en sous parties de la façon suivante • On va définir un serveur (Maître) qui va distribuer à la demande les sous-sommes Si à des clients potentiels (Esclave) – Les travailleurs demandent du travail au maître – Le maître fournit des unités de travail aux travailleurs – Le serveur est implémenté à l'aide d'un objet distant avec RMI Le côté serveur : l'interface • L'interface serveur public interface PiServerI extends Remote { public WorkUnit getWorkUnit() throws RemoteException; public void updatePi() throws RemoteException; } – On utilise une classe WorkUnit pour coder la sous somme à calculer public class WorkUnit implements Serializable { private int a, b; public WorkUnit(int a, int b) {this.a=a; this.b=b;} public int getA(){return this.a;} public int getB(){return this.b;} } Le côté serveur : implémentation • On considère l'implémentation simple suivante public class PiServer implements PiServerI { int m, pas; int a; float pi_approx; public WorkUnit getWorkUnit() throws RemoteException { if(courant< m && courant+pas<=m) courant+=pas; else if (courant <=m && courant+pas > m) courant=m; } } return new WorkUnit(courant-pas,courant); public updatePi(float s) throws RemoteException { this.pi_approx+=s; } Le côté client • On suppose que le serveur est démarré et publié dans la « registry » public class PiWorker { public void startWork() throws Exception { ServerPi sp = (ServerPi)Naming.lookup(«url»); WorkUnit work = sp.getWork(); int a = work.getA(); int b = work.getB(); float s=0; for(int i = a; i<b; i++) { s+=Math.abs(Math.pow(-1,i)/(2i+1)); } sp.updatePi(s); } } Discussion • Comment détecter la terminaison ? – On a effectivement terminé le calcul de Pi • Comment gérer les cas où un client (travailleur) tombe en panne – Un client tombe en panne • Comment gérer l'équilibrage de charges ? – Des clients ayant des puissances de calcul différentes • Ce sont là des problèmes classiques en programmation parallèle et distribuée