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