Master 2 TI - Conception d'Applications Hétérogènes Distribuées- TP 3
Middlewares objets distribués
Objectifs pédagogiques
Comprendre et mettre en œuvre les principes fondamentaux de la communication entre objets dans des infrastructures distribuées.
Introduction
Dans ce tp, vous allez apprendre à utiliser les infrastructures middlewares réparties CORBA et RMI. Même si CORBA est conçue pour permettre de communiquer à des objets hétérogènes, nous nous limiterons à de la programmation en Java, sachant que les mêmes techniques que celles décrites ici existent dans les autres langages de programmation. Ensuite, vous réutiliserez les concepts de ces technologies pour mettre en oeuvre des objets transactionnels distribués (EJB 3.0) et les services associés.
CORBA
Mise en route
Pour vous exercer, voici un premier exemple d’application
client
serveur implémentant les principes de communication avec CORBA, tiré de
celui proposé par SUN (http://java.sun.com/j2se/1.4.2/docs/guide/idl/GShome.html).
Un fichier zippé contenant un fichier IDL et les sources du
serveur/servant et du client est disponible localement sur :
http://www710.univ-lyon1.fr/~lmedini/TI1/Bonjour.zip.
- A partir de ce fichier, générez les fichiers correspondant
à l’interface exposée par le serveur à l’aide de l’outil idlj.
Ce code a été testé avec les deux modèles d’implémentation du serveur :
par héritage et par délégation (TIE). En d’autres termes, vous pouvez
indifféremment générer les fichiers avec l’option –fall ou –fallTIE. Pour info,
vous pouvez également utiliser les options –fserver / –fserverTIE pour
générer les fichiers serveur et –fclient
/ –fclientTIE
pour les fichiers client.
- Copiez ensuite les fichiers source Java dans le répertoire
« bonjour » créé par idlj et compilez l’ensemble des sources.
- Lancez ensuite le serveur de noms tnameserv, le
serveur et le client, dans cet ordre (voir cours 2) depuis 3 fenêtres
DOS différentes :
- tnameserv -ORBInitialPort 1050
- java bonjour/BonjourServer -ORBInitialPort 1050 -ORBInitialHost localhost (depuis le répertoire contenant le fichier .idl et le répertoire "bonjour")
- java bonjour/BonjourClient -ORBInitialPort 1050 -ORBInitialHost localhost (idem)
Remarques :
- Les manipulations ci-dessus doivent être faites à l’aide une ligne
de commande dans une console (DOS / Linux). Sous DOS, les trois applications (serveur de noms, serveur, client) doivent être lancées dans cet ordre et dans des fenêtres différentes.
- Si plusieurs versions de Java sont installées sur votre machine, assurez-vous que tous les utilitaires (compilateur Java et IDL, machine virtuelle Java) que vous utilisez sont bien issus de la même installation.
Conception
Vous allez maintenant intégrer la communication CORBA à l’application développée au TP précédent. Pour cela :
- précisez tout d’abord la définition IDL des interfaces exposées par le servant, et générez les différents fichiers (stub, skeleton, ORB) à l’aide du compilateur idlj,
- à l'aide d'un nouveau projet, créez un jar avec l'ensemble de ces fichiers que vous importerez dans votre projet Repertoire,
- dans votre projet Repertoire du TP 1 :
- supprimez le main de l'application
- faites deux projets séparés pour le serveur et le client
- dans le serveur, modifiez le code source pour qu'il récupère une référence sur le serveur de noms et qu'il y enregistre trois objets, représentant les composants qui implémentent les méthodes add, remove et list après les avoir instanciés dans le conteneur,
- modifiez le source du client pour qu’il envoie ses requêtes au serveur de noms,
- faites tourner séparément client et serveur, après avoir lancé le serveur de noms.
Remarques :
- Bien entendu, la structure de données HashMap n'existe pas
en IDL. A vous d'en définir une de manière aussi proche que possible, de façon à
pouvoir faire facilement un "mapping" entre les requêtes qui arriveront
au serveur et l'instance de HashMap utilisée en interne. Aide : http://www-itec.uni-klu.ac.at/~harald/corba/idldatattypes.html.
- Attention : normalement, il existe deux interfaces "Repertoire", l'une
dans votre projet initial, et l'autre dans les fichiers générés par idlj ; à
vous de voir comment éviter les confusions.
- Votre classe RepertoireImpl, en plus des interfaces du TP précédent, va maintenant dépendre de deux éléments de ce package CORBA :
- elle implémentera l'interface Repertoire générée par idlj
- elle étendra une classe portant un nom ressemblant à RepertoirePOA (en fonction du nom de votre interface IDL).
Cela implique de lui adjoindre des méthodes de service (et notamment une méthode
shutdown).
Exemples de méthodes du serveur :
public class Serveur {
static RepertoireImpl adder, remover, lister;
static ORB orb;
static POA rootpoa;
static NamingContextExt ncRef;
public static void main (String[] args) {
startConteneur(); //à vous de programmer
startServer(args);
referenceRepertoire(adder, "add");
referenceRepertoire(remover, "remove");
referenceRepertoire(lister, "list");
run();
}
private static void startServer(String[] args) {
try{
// initialize the ORB
orb = ORB.init(args, null);
System.out.println("ORB initialized...");
// get reference to rootpoa & activate the POAManager
rootpoa = POAHelper.narrow(orb.resolve_initial_references("RootPOA"));
Policy p[] = new Policy[7];
p[0] = rootpoa.create_thread_policy(ThreadPolicyValue.ORB_CTRL_MODEL);
p[1] = rootpoa.create_lifespan_policy(LifespanPolicyValue.PERSISTENT);
p[2] = rootpoa.create_id_uniqueness_policy(IdUniquenessPolicyValue.MULTIPLE_ID);
p[3] = rootpoa.create_id_assignment_policy(IdAssignmentPolicyValue.USER_ID);
p[4] = rootpoa.create_servant_retention_policy(ServantRetentionPolicyValue.NON_RETAIN);
p[5] = rootpoa.create_request_processing_policy(RequestProcessingPolicyValue.USE_DEFAULT_SERVANT);
p[6] = rootpoa.create_implicit_activation_policy(ImplicitActivationPolicyValue.NO_IMPLICIT_ACTIVATION);
rootpoa.the_POAManager().activate();
System.out.println("POA initialized...");
// get the root naming context
org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
// Use NamingContextExt which is part of the Interoperable
// Naming Service (INS) specification.
ncRef = NamingContextExtHelper.narrow(objRef);
System.out.println("NamingContext found...");
}
catch (Exception e) {
System.err.println("ERROR: " + e);
e.printStackTrace(System.out);
}
}
private static void referenceRepertoire(RepertoireImpl repertoire, String name) {
try{
// register servant with the ORB
repertoire.setORB(orb);
// get object reference from the servant
org.omg.CORBA.Object ref = rootpoa.servant_to_reference(repertoire);
repertoire.corba.Repertoire href = (repertoire.corba.Repertoire) RepertoireHelper.narrow(ref);
System.out.print("Servant " + name + " initialized...");
// bind the Object Reference in Naming
NameComponent path[] = ncRef.to_name( name );
ncRef.rebind(path, href);
System.out.println(" and bound.");
}
catch (Exception e) {
System.err.println("ERROR: " + e);
e.printStackTrace(System.out);
}
}
private static void run() {
System.out.println("Server ready and waiting...");
// wait for invocations from clients
orb.run();
System.out.println("Server Exiting ...");
}
RMI
Mise en route
Lancement du client
Pour commencer, vous allez démarrer sur une application de type "Hello World" toute simple dont vous récupérerez le client et l'interface distante ici.
- Compilez-les en ligne de commande avec javac.
Rappel : vous devrez faire cela depuis le répertoire où vous avez décompressé le package et utiliser des slash entre nom de package et nom de fichiers java.
- Le fichier client.policy doit être dans le répertoire au dessus de votre package client (Projet_client)
- Lancez le client (le serveur doit déjà tourner sur une machine dont l'adresse IP vous a été indiquée) et vérifiez qu'il est capable d'interroger ce serveur.
La syntaxe est la suivante :
java -Djava.security.policy=client.policy client.Client 192.168.1.38,
où le dernier argument est passé au Main et est l'adresse IP de la machine où tourne le serveur de noms JNDI.
TP du 24/09/2015 : remplacez l'adresse IP de l'annuaire JNDI par la suivante : 134.214.90.240. Récupérez le stub ici et collez-le dans le répertoire serveur de votre client.
Lancement du serveur
Récupérez ensuite le code du serveur là et décompressez-le dans un autre répertoire de votre machine.
- De la même façon, compilez en ligne de commande et assurez-vous que le fichier serveur.policy est bien localisé dans le répertoire au-dessus du package serveur (Projet_serveur).
- Générez le stub avec RMIC (remarque : pour rmic, vous devrez utiliser des points entre nom de package et nom de fichiers java...).
- Faites ensuite tourner le serveur avec la commande suivante :
java -Djava.rmi.server.codebase=file:/D:/MesProjets/RMI/Projet_serveur/
-Djava.rmi.server.hostname=127.0.0.1
-Djava.security.policy=server.policy serveur.Server,
où le codebase est le répertoire dans lequel est situé le package Java que vous avez compilé (ici : Projet_serveur) et l'option hostname permet d'indiquer au serveur de votre application l'adresse IP de la machine hébergeant le serveur JNDI que le stub doit interroger pour faire le bind (port par défaut : 1099).
Important:
- l'annuaire JNDI rmiregistry doit être lancé depuis le répertoire de votre projet serveur
- il doit impérativement y avoir un slash à la fin de l'argument de l'option -Djava.rmi.server.codebase
De la même manière, testez le client pour qu'il appelle le serveur sur votre machine locale.
Sécurisation
Utilisez les informations situées ici pour faire en sorte que seuls les fichiers situés dans vos répertoires client et serveur soient accessibles par le serveur JNDI.
Modification de l'interface
Enfin, modifiez le servant (l'interface Hello et sa classe d'implémentation) pour que la méthode sayHello prenne en entrée deux paramètres (un nom et un entier), et qu'elle dise autant de fois bonjour que l'entier le lui indique.
Utilisation dans Eclipse
Il peut arriver qu'un client ou un serveur tourne correctement dans Eclipse. Pour cela, une méthode qui a fonctionné est de créer le projet et la structure des packages à la main et de glisser-déposer les classes et interfaces dans l'explorateur de projet. Une fois que le code Java est correctement intégré dans le projet, il faut créer une Run Configuration :
- Onglet "Main" : sélectionner le projet et cliquer ensuite sur "Search" pour sélectionner la classe contenant le Main. Si Eclipse ne vous propose rien, essayez l'une des 3 solutions suivantes : redémarrez Eclipse / recréez un nouveau projet / abandonnez.
- Si vous avez réussi à sélectionner votre classe Main, passez à l'onglet "Arguments" :
- le champ "Program arguments" correspond aux arguments passés au Main (i.e. l'adresse IP du serveur JNDI pour le client)
- le champ "VM arguments" correspond à ceux passés à l'exécutable Java (i.e. les options qui commencent par "-D").
Une fois la configuration créée et appliquée, testez-la. Si Eclipse est incapable de trouver votre classe Main, essayez l'une des 3 solutions ci-dessus... Remarque : vous n'aurez pas tout perdu, Eclipse est au moins capable de compiler votre code Java.
N'oubliez pas que de toutes façons, il faut compiler les classes d'implémentation du servant avec rmic.
Conception
Reprenez une version fonctionnelle de votre application de répertoire téléphonique, décomposée en 4 parties (Main, Annuaire, Serveur, Client). Par exemple, celle issue de la question 0, mais si vous avez la version de la question 2.2 (uniformisation, cela vous simplifiera la tâche).
Dans votre projet Repertoire du TP 1 :
- supprimez le main de l'application
- faites deux projets séparés pour le serveur et le client (ne tenez plus compte de votre annuaire)
- si vous ne l'avez pas, faites en sorte d'extraire une interface commune aux quatre objets répertoires
- dans le serveur, modifiez le code source pour qu'il récupère une référence sur le serveur de noms et qu'il y enregistre quatre objets, représentant les composants qui implémentent les méthodes add, remove, list et init,
- modifiez le source du client pour qu'il envoie ses requêtes au serveur de noms,
- faites tourner séparément client et serveur, après avoir lancé le serveur de noms.