[Tutoriel] CORBA en Java
par
, 12/06/2016 à 20h23 (2075 Affichages)
Auteur : Gokan EKINCI
Date de 1ère publication : 2015-01-12
Date de mise à jour : 2016-06-19
Licence : CC BY-NC-SA
Source d'origine : http://gokan-ekinci.appspot.com/fr/t...ure/java-corba
Sommaire :
Avant de démarrer ce tutoriel…
Introduction
Définitions
Informations complémentaires
Qu’est-ce que l’IDL ?
Les types IDL/Java
Les modules
Les interfaces, les prototypes de méthodes, les constantes, et l’héritage
Les structures
Les values type
Les alias et les tableaux
Les énumérations
Les exceptions
Les opérations asynchrones
TP CORBA Chapitre 1 : Génération du fichier IDL
TP CORBA Chapitre 2 : Schéma UML
TP CORBA Chapitre 3 : Le modèle POA (partie serveur seulement)
TP CORBA Chapitre 4 : Implémentation (partie serveur)
TP CORBA Chapitre 5 : Implémentation (partie cliente)
TP CORBA Chapitre 6 : Bonus
Les erreurs récurrentes
Pour ou contre CORBA ?
Avant de démarrer ce tutoriel…
Avant de démarrer ce tutoriel :
- Objectif à la fin de ce tutoriel : connaître l’IDL et savoir manipuler une implémentation CORBA en Java.
- Ce qu’il faut connaître :
Le langage Java.
- Connaissances appréciées mais pas obligatoire :
Etre à l’aise avec la généricité si vous souhaitez comprendre le code de la partie "Bonus".
L’architecture SOA (cf : RMI et autres protocoles réseaux de type RPC, les web services SOAP en particulier pour le fichier WSDL).
Un langage comme PL/SQL, PL/pgSQL, SQL/PSM ou T-SQL pour mieux comprendre le concept des modes IN, OUT et INOUT.
Le C++ pour la syntaxe de l’IDL.
- Outil nécessaire : Un ordinateur avec le JDK de Sun/Oracle (le JDK contient une implémentation CORBA).
Pour télécharger le JDK 8 d’Oracle cliquez ici.
- Norme CORBA utilisé dans ce cours : CORBA 2.3.1 datant d’octobre 1999 (source).
Pour consulter l’évolution de la norme CORBA voir ce lien : http://www.omg.org/spec/CORBA/.
Pour consulter l’évolution de l’implémentation CORBA dans Java voir ce lien : http://docs.oracle.com/javase/8/docs...idl/corba.html
Introduction
Pour qu’il y ait une communication entre un client et un serveur CORBA, il faut que le client et le serveur possède chacun 1 ORB : une application client-serveur possède au minimum 2 ORB.Envoyé par Wikipédia -> https://fr.wikipedia.org/wiki/Common_Object_Request_Broker_Architecture
Étudions le schéma suivant :Envoyé par Wikipédia -> https://fr.wikipedia.org/wiki/Object_request_broker
Les étapes :
- Le client CORBA invoque une méthode distante.
- Le Stub du client CORBA marshalise l’invocation de la méthode distante en requête CORBA.
- La requête CORBA est envoyée à travers le réseau à partir l’ORB du client.
- La requête CORBA est réceptionnée par l’ORB du serveur.
- Le Skeleton du serveur CORBA démarshalise la requête CORBA.
- Le serveur CORBA exécute le service lié à la méthode invoquée.
- Le Skeleton du serveur CORBA marshalise la réponse CORBA.
- La réponse CORBA est envoyée à travers le réseau à partir de l’ORB du serveur.
- La réponse CORBA est réceptionnée par l’ORB du client.
- Le Stub du client CORBA démarshalise la réponse CORBA.
Ce que l’on peut remarquer dans ce schéma, c’est qu’il ressemble plus ou moins à celui du protocole HTTP (avec un navigateur qui joue le rôle de client et un serveur web)... donc si on fait l’analogie entre HTTP et CORBA, un client HTTP connaît le serveur grâce à une URL mais comment un client CORBA connaît le serveur CORBA dans ce cas ? La réponse est l’IOR (voir sa définition dans la partie suivante)... pour faire court l’IOR est un objet contenant plusieurs informations permettant d’identifier un "servant" (la méthode distante). Comment obtenir cet IOR ? Il existe 2 moyens standards :
- Le serveur stocke l’IOR dans un fichier et le client trouve un moyen de récupérer ce fichier pour lire son contenu. Il est possible de générer un objet proxy à partir de l’IOR grâce à une opération appelée "narrowing".
- Le serveur possède un service appelé "NameService", c’est un programme qui tourne en daemon sur le serveur, il est plus connu sous le nom de "orbd" ou "tnameserv". En utilisant le host & port du serveur et un nom attribué au service le client peut récupèrer l’IOR et génère un objet proxy.
Dans notre cas nous utiliserons la deuxième solution car c’est la plus propre.
Définitions
GIOP
(General Inter ORB Protocol)La spécification du protocole réseau utilisé par CORBA s’appelle GIOP (General Inter ORB Protocol).
- Une implémentation de GIOP est IIOP (Internet Inter ORB Protocol). IIOP utilise la couche de transport TCP/IP.
- Une autre implémentation de GIOP est MIOP (Multicast Inter ORB Protocol).
Stub
(Souche en français)Le Stub est une portion de code coté client qui réalise l’emballage des opérations (requêtes CORBA à envoyer au serveur) et le déballage des résultats (réponse du serveur). Skeleton
(Squelette en français)Le Skeleton est une portion de code côté serveur qui réalise le déballage des opérations (requêtes CORBA envoyées par le client) et l’emballage des résultats (réponse à retourner au client). Servant Le Servant est une instance de la classe d’implémentation du service (coté serveur). La classe d’implémentation devra hériter des classes POA. Objet Proxy Une objet proxy est unobjet qui est le représentant du servant (coté client) IOR
(Interoperable Object Reference)L’IOR est un objet CORBA que s’échangent le client et le serveur afin d’identifier le « servant ». Concrètement l’IOR est une référence créée par le serveur, cela permet au client de localiser le servant à partir d’un host, port, identifiant de méthode. Narrowing
(méthode narrow(ior) )Le "narrowing" est une opération consistant à retourner un "Objet Proxy" à partir d’une IOR. POA
(Portable Object Adapter)Le POA ou "Adaptateur d’objet" en français, est l'entité coté serveur qui gère les IOR et les servants. AOM
(Active Object Map)L’AOM est une table qui enregistre les couples IOR/Servant. L’AOM est géré par le POA. Bus logiciel CORBA Le bus logiciel CORBA représente l’ensemble des composants logiciels qui permettent à un client et serveur CORBA de communiquer ensemble. Interopérabilité L'interopérabilité représente la possibilité pour deux composants (des ORBs dans le cas de CORBA) :
- ...développés avec des langages différents (C++, Java etc),
- ...utilisant des implémentations différentes (MICO, TAO, omniORB etc),
- ...exécutés dans des environnements différents (Windows, Linux, Mac etc)
=> de pouvoir travailler ensemble. En gros l’interopérabilité signifie que si un client n’utilise pas la même implémentation que le serveur, ce n’est pas grave, car les 2 entités pourront communiquer sans problème.
Glossaire Oracle pour CORBA (version détaillée ici) :
- idlj : est un utilitaire inclus dans le JDK pour générer des classes Java à partir d’un IDL.
- orbd (ou tnameserv) : est un utilitaire inclus dans le JDK, il permet d’initialiser le service de nommage appelé NameService. Il faut lancer cette commande avant de démarrer le programme du serveur.
Informations complémentaires
Ils existent de nombreuses implémentations de CORBA, ces implémentations sont interopérables :
- Java : ORB de Sun/Oracle inclus dans Java, JacORB.
- C++ : TAO, omniORB, MICO, ORBit.
- Python : omniORBpy.
Vous devez bien savoir faire les différences entre :
- Skeleton et Servant : Le Skeleton est le code généré par idlj, c’est la classe xxxPOA.java (ou xxxPOATie.java si on souhaite appliquer le pattern TIE), son objectif est de démarshaliser les requêtes CORBA envoyées par le Client. Le Servant est une instance de la classe d’implémentation créé par le développeur.
- Stub et Objet Proxy : Le Stub est le code généré par idlj, c’est la classe _xxxStub.java, son objectif est de marshaliser les requêtes envoyées au Serveur. L’objet Proxy est un wrapper de l’interface IDL retourné par la méthode narrow(ior), c’est l’objet dont on va se servir coté client pour envoyer des requêtes.
Qu’est-ce que l’IDL ?
L’IDL (Interface Definition Language) est un langage dont la syntaxe est proche de celle du C++, le principe d’un fichier IDL est le même qu’un fichier XSD (XML Schema Definition) ou WSDL : générer du code (Java, C++, Python etc).
Le fichier IDL représente un contrat entre le serveur et ses clients : le serveur et ses clients doivent utiliser le même fichier de contrat pour générer leur code.
L’IDL est indépendant du langage d'implémentation utilisé coté client ou serveur. On peut avoir un client en C++ et un serveur en Java par exemple.
Quelques limitations :
- La surcharge de méthode n’existe pas, il n’est donc pas possible de définir 2 méthodes ayant le même nom même si leur signature (nom et paramètres) sont différents.
- Il n’existe pas de moyen de représenter les templates ou la généricité avec l’IDL.
- L’IDL n’est pas sensible à la casse : il n’est donc pas possible d’avoir un nom de paramètre avec une casse différente que son type, de même que pour les noms de module et d’interface.
Exemple d'IDL :
IDL Extrait du code Java généré à partir de l’IDL
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 module fr { module ekinci { interface CalculationService { long factorial(in long num); }; }; };
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 package fr.ekinci; public interface CalculationServiceOperations { int factorial(int num); }
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 package fr.ekinci; public interface CalculationService extends CalculationServiceOperations, org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity { }
Les types IDL / Java
Les types :
IDL Java (mode in) Java (mode out ou inout) void (type de retour) void void boolean boolean org.omg.CORBA.BooleanHolder char char org.omg.CORBA.CharHolder wchar char org.omg.CORBA.CharHolder octet byte org.omg.CORBA.ByteHolder short short org.omg.CORBA.ShortHolder unsigned short short org.omg.CORBA.ShortHolder long int org.omg.CORBA.IntHolder unsigned long int org.omg.CORBA.IntHolder long long long org.omg.CORBA.LongHolder unsigned long long long org.omg.CORBA.LongHolder float float org.omg.CORBA.FloatHolder double double org.omg.CORBA.DoubleHolder long double Incompatibilité en Java Incompatibilité en Java string java.lang.String org.omg.CORBA.StringHolder wstring java.lang.String org.omg.CORBA.StringHolder Object (différent de java.lang.Object ! Sert à représenter les IOR). org.omg.CORBA.Object org.omg.CORBA.ObjectHolder
Exemple de prototype de méthode IDL :
Les mots-clés in, out et inout sont obligatoires pour les paramètres de méthode :
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 void meth1(); long meth2(in long param1); string meth3(in long param1, out octet param2, inout string param3);
- in : Passage par copie de valeur.
Au niveau serveur, on va récupérer le paramètre entré par le client et s’en servir dans l’implémentation du service. Si le serveur affecte une nouvelle valeur à ce paramètre le client n’en sera pas au courant. C’est le mode le plus simple.
- out : Passage par référence.
Le serveur ne lit pas la valeur de ce paramètre mais peut y affecter une nouvelle valeur. Le client pourra lire la valeur affectée par le serveur.
- inout : Passage par référence.
Le serveur peut lire la valeur de ce paramètre et peut y affecter une nouvelle valeur. Le client pourra lire la valeur affectée par le serveur.
Les modules
L’équivalent du package Java est module en CORBA :
A savoir :
IDL Extrait du code Java généré à partir de l’IDL
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 module fr { module ekinci { // définir une interface }; };
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 package fr.ekinci; // interface Java implémentant `IDLEntity`
- L’exemple IDL ci-dessus ne fonctionnera pas avec idlj car le programme attend à ce qu’on ajoute au moins une interface, struct, ou enum dans le module.
- Javadoc de l’interface IDLEntity.
Les interfaces, les prototypes de méthodes, les constantes, et l’héritage
IDL Extrait du code Java généré à partir de l’IDL
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 module fr { module ekinci { interface Parent{ // Prototypes de méthode void meth1(); long meth2(in long param1); string meth3( in long param1, out octet param2, inout string param3 ); // Constantes const long MAX = 10000; const float FACTOR = (10.0 - 6.5) * 3.91; }; // Héritage interface Child : Parent{}; }; };
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12 package fr.ekinci; public interface ParentOperations { void meth1 (); int meth2 (int param1); String meth3 ( int param1, org.omg.CORBA.ByteHolder param2, org.omg.CORBA.StringHolder param3 ); }
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11 package fr.ekinci; public interface Parent extends ParentOperations, org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity { public static final int MAX = (int)(10000); public static final float FACTOR = (float)((double)((double)(10.0 - 6.5) * 3.91)); }
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 public interface ChildOperations extends fr.ekinci.ParentOperations { }
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 package fr.ekinci; public interface Child extends ChildOperations, fr.ekinci.Parent, org.omg.CORBA.portable.IDLEntity { }
A savoir :
- Une interface ne peut pas contenir d’attribut, seulement des prototypes de méthode et des constantes.
- La surcharge de méthode n’existe pas, il n’est donc pas possible de définir 2 méthodes ayant le même nom même si leur signature (nom et paramètres) sont différents.
- Une interface ne peut pas contenir une autre interface (cf : "nested interface" ou "interface imbriquée").
La commande idlj.exe -fall myfile.idl va générer 12 fichiers :
- Parent.java, ParentHolder.java, ParentHelper.java, ParentOperations.java, ParentPOA.java, _ParentStub.java
- Child.java, ChildHolder.java, ChildHelper.java, ChildOperations.java, ChildPOA.java, _ChildStub.java
Note : L’interface ChildOperations hérite de ParentOperations, ce sont ces interfaces qui contiennent les prototypes de méthode en Java.
Les structures
Les structures et valuetypes permettent de réaliser des types complexes.
IDL Extrait du code Java généré à partir de l’IDL
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10 module fr { module ekinci { struct Classe { short a; long b; double c; string d; }; }; };
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package fr.ekinci; public final class Classe implements org.omg.CORBA.portable.IDLEntity { public short a = (short)0; public int b = (int)0; public double c = (double)0; public String d = null; public Classe (){ } // ctor public Classe (short _a, int _b, double _c, String _d) { a = _a; b = _b; c = _c; d = _d; } // ctor } // class Classe
A savoir :
- Une structure ne peut pas contenir de prototype de méthode, seulement des attributs.
- Une structure ne peut pas hériter d’une autre structure.
- Une structure ne peut pas contenir de constante.
Les values type
IDL Extrait du code Java généré à partir de l’IDL
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12 module fr { module ekinci { valuetype ParentValue { public long a; string getFoo(in long param1); }; valuetype ChildValue : ParentValue { private double b; }; }; };
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10 package fr.ekinci; public abstract class ParentValue implements org.omg.CORBA.portable.StreamableValue { public int a = (int)0; public abstract String getFoo (int param1); // Voir fichier Java pour le reste }
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8 package fr.ekinci; public abstract class ChildValue extends fr.ekinci.ParentValue { protected double b = (double)0; // Voir fichier Java pour le reste }
A savoir :
- Les valuetype ont étés introduit après interface et struct.
- Contrairement aux interfaces et structures, un valuetype peut à la fois contenir des attributs et des prototypes de méthode.
- Un valuetype peut hériter d’un autre valuetype.
- Il faut obligatoirement définir un type d’accès aux attributs IDL (private ou public ; protected n’existe pas).
Une fois le code généré, on remarque que le type d’accès private en IDL devient protected en Java.
- Il est possible de précéder le mot-clé valuetype de abstract, il faudra alors enlever les attributs ainsi que le type d’accès pour les prototypes de méthode. Un abstract valuetype d'IDL est généré en tant qu’interface Java.
La commande idlj.exe -fall myfile.idl va générer 8 fichiers :
- ParentValue.java, ParentValueDefaultFactory.java, ParentValueHelper.java, ParentValueHolder.java
- ChildValue.java, ChildValueDefaultFactory.java, ChildValueHelper.java, ChildValueHolder.java
Les alias et les tableaux
Pour créer un attribut de type tableau dans une struct IDL :
/!\ : La taille 10 est gérée dans la classe xxxHelperPour créer un attribut de type tableau sans une limite prédéfini dans une struct IDL il faut utiliser le mot clé sequence :
IDL Extrait du code Java généré à partir de l’IDL
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part long tab[10];
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part public int tab[] = null;
IDL Extrait du code Java généré à partir de l’IDL
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part sequence<long> integerList;
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part public int integerList[] = null;
Pour utiliser un tableau ou sequence en tant que paramètre dans un prototype de méthode il faut obligatoirement passer par un alias grâce au mot-clé typedef :
/!\ Attention : Il est possible de placer l’instruction typedef à l’extérieur ou à l’intérieur de l’interface contenant le prototype de méthode avec le paramètre de type tableau ou sequence, cependant cette instruction doit être déclarée avant l’utilisation de l’alias.
IDL Extrait du code Java généré à partir de l’IDL
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2 typedef long TabInt[10][10]; TabInt method(in TabInt param);
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part int[][] method(int[][] param);
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2 typedef sequence<long> TabInt; TabInt method(in TabInt param);
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part int[] method(int[] param);
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2 typedef sequence<long> TabInt; TabInt method(inout TabInt param);
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part int[] method(fr.ekinci.TabIntHolder param);
Les enumerations
IDL Extrait du code Java généré à partir de l’IDL
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 module fr { module ekinci { enum Toto { UN, DEUX, TROIS }; }; };
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 package fr.ekinci; public class Toto implements org.omg.CORBA.portable.IDLEntity { private int __value; private static int __size = 3; private static fr.ekinci.Toto[] __array = new fr.ekinci.Toto [__size]; public static final int _UN = 0; public static final fr.ekinci.Toto UN = new fr.ekinci.Toto(_UN); public static final int _DEUX = 1; public static final fr.ekinci.Toto DEUX = new fr.ekinci.Toto(_DEUX); public static final int _TROIS = 2; public static final fr.ekinci.Toto TROIS = new fr.ekinci.Toto(_TROIS); public int value () { return __value; } public static fr.ekinci.Toto from_int (int value) { if (value >= 0 && value < __size) return __array[value]; else throw new org.omg.CORBA.BAD_PARAM (); } protected Toto (int value) { __value = value; __array[__value] = this; } } // class Toto
La commande idlj.exe -fall myfile.idl va générer 3 fichiers :
- Toto.java
- TotoHolder.java
- TotoHelper.java
Les exceptions
IDL Extrait du code Java généré à partir de l’IDL
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 module fr { module ekinci { // Création de lexception exception TransactionException{}; // raises IDL = throws Java interface DistantTransaction { void prepare() raises (TransactionException); void commit() raises(TransactionException); void rollback() raises(TransactionException); }; }; };
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 package fr.ekinci; public interface DistantTransaction extends DistantTransactionOperations, org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity { } // interface DistantTransaction
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14 package fr.ekinci; public final class TransactionException extends org.omg.CORBA.UserException { public TransactionException () { super(TransactionExceptionHelper.id()); } // ctor public TransactionException (String $reason) { super(TransactionExceptionHelper.id() + " " + $reason); } // ctor } // class TransactionException
A savoir :
- Une exception peut contenir des paramètres :
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 exception MyException{ string reason; string sentence; };- Un prototype de méthode peut indiquer qu'il est possible de lancer plusieurs exceptions :
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part void method() raises(MyException1, MyException2, MyException3);- Javadoc de la classe UserException.
La commande idlj -fall myfile.idl va générer 9 fichiers :
- DistantTransaction.java, DistantTransactionHolder.java, DistantTransactionHelper.java, DistantTransactionOperations.java, DistantTransactionPOA.java, _DistantTransactionStub.java
- TransactionException.java, TransactionExceptionHolder.java, TransactionExceptionHelper.java
Les opérations asynchrones
Les méthodes CORBA sont exécutées de manière synchrone (comme pour les méthodes RMI en Java), cela signifie que si le serveur prend 2 secondes pour exécuter une méthode, le client devra attendre ces 2 secondes + le temps réseau que peut prendre l’échange de requête entre le client et le serveur. Cependant il existe un mot-clé oneway permettant de rendre une méthode asynchrone : le client n’attend pas que le serveur ait terminé d’exécuter la méthode oneway et passe directement à l’instruction qui suit.
Pour mettre en place une méthode asynchrone il faut respecter quelques contraintes :
- Une méthode oneway ne peut pas avoir de valeur de retour (utiliser void).
- Une méthode oneway ne peut qu’accepter des paramètres en mode in.
- Une méthode oneway ne peut pas lancer d’exception.
Exemple :
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9 module fr { module ekinci { interface Asynchronous { oneway void farewell(); }; }; };
Liste des fichiers générés suite à l’exécution de idlj -fall myfile.idl : _AsynchronousStub.java, Asynchronous.java, AsynchronousHelper.java, AsynchronousHolder.java, AsynchronousOperations.java, AsynchronousPOA.java
/!\ Attention : Si vous êtes familier avec les environnements multithread et avez connaissance des problèmes de concurrences, vous savez que les méthodes asynchrones sont à manipuler avec prudence.
TP CORBA Chapitre 1 : Génération du fichier IDL
Pour générer des classes Java à partir du fichier IDL, nous allons utiliser le programme "idlj" présent dans le JDK, celui-ci porte l’extension ".exe" sous Windows. Ce programme se trouve dans le répertoire d’installation du JDK "jdkxxx/bin/".
Nous allons utiliser la commande suivante : idlj -fall <Insérer nom fichier idl> exemple : idlj -fall calcul.idlAvec le paramètre -fall on va générer plusieurs fichiers de code Java pour la partie client et serveur.
Le nombre de fichier généré dépendra des éléments utilisés dans le fichier idl. Par exemple pour chaque énumération (enum) ajoutée dans le fichier idl, idlj va générer 3 fichiers Java supplémentaires.
Autrement :
- La commande idlj calcul.idl est équivalente à idlj -fclient calcul.idl et permet de générer uniquement les fichiers liés à la partie cliente.
- La commande idlj -fserver calcul.idl permet de générer uniquement les fichiers liés à la partie serveur.
- La commande idlj -fclient -fserver calcul.idl est équivalente à la commande idlj -fall calcul.idl et permet de générer tous les fichiers (partie cliente et serveur).
Pour plus d’information sur le programme idlj, lire la documentation Sun/Oracle.
Créez un fichier IDL avec le contenu suivant :
IDL Extrait du code Java généré à partir de l’IDL
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 module fr { module ekinci { interface CalculationService { long factorial(in long num); }; }; };
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 package fr.ekinci; public interface CalculationService extends CalculationServiceOperations, org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity { } // interface CalculationService
6 fichiers seront générés si on exécute la commande suivante idlj -fall calcul.idl :
Fichier Description CalculationServicePOA.java Ce fichier contient une classe abstraite représentant le skeleton du serveur _CalculationServiceStub.java Ce fichier contient une classe représentant le stub du client CalculationService.java Ce fichier contient notre interface Java CalculationService. L’interface CalculationService hérite de CalculationServiceOperations, org.omg.CORBA.Object et org.omg.CORBA.portable.IDLEntity CalculationServiceHelper.java Ce fichier contient une classe abstraite permettant de faire du narrowing et de gérer le type org.omg.CORBA.Any CalculationServiceHolder.java Ce fichier contient une classe final permettant de gérer les paramètres de méthode en mode out ou inout CalculationServiceOperations.java Cette interface contient les prototypes de méthode de CalculationService, dans notre cas il contient juste la méthode factorial()
TP CORBA Chapitre 2 : Schéma UML
Voici un schéma UML pour avoir une meilleure vision sur la hiérarchie des classes générées :
A savoir :
- CalculationServiceImpl a été créée à la main et n’a pas été généré avec idlj. Si on décide d’utiliser le "Inheritance Model", CalculationServiceImpl doit hériter de CalculationServicePOA; si on décide d’utiliser le "Tie Delegation Model", CalculationServiceImpl ne doit pas hériter de CalculationServicePOA mais doit implémenter l'interface CalculationServiceOperations (voir le chapitre suivant pour comprendre ces modèles).
- La classe CalculationServicePOATie n'est générée que si la commande suivante est exécutée : idlj -fallTIE calcul.idl. Cette classe n'est utile que si on souhaite appliquer le "Tie Delegation Model".
TP CORBA Chapitre 3 : Le modèle POA (partie serveur seulement)
Il existe 2 variantes d’implémentation pour le modèle POA pour la partie serveur :
- L’approche par héritage (The Inheritance Model)
- L’approche par délégation (The Tie Delegation Model)
La différence entre ces 2 modèles ?
Si on choisit le Inheritance Model, la classe d’implémentation doit obligatoirement hériter de la classe CalculationServicePOA, tandis que si on choisit le Tie Delegation Model, la classe d’implémentation doit juste implémenter l’interface CalculationServiceOperations. Comme il n’y pas d’héritage multiple Java, le Tie Delegation Model sert à libérer un emplacement pour que la classe d’implémentation puisse hériter d’une autre classe, cependant ce modèle possède aussi un inconvénient : une méthode supplémentaire est appelé à chaque appel de méthode distante.
/!\ : L’héritage multiple n’a jamais été une solution incontournable, il est tout à fait possible d’utiliser un design pattern de comportement avec la composition pour s’en passer.Envoyé par Sun/Oracle -> http://docs.oracle.com/javase/7/docs/technotes/guides/idl/jidlTieServer.html
La différence entre ces 2 modèles de POA au niveau de l’implémentation du serveur est relativement faible, et se résume à 2 lignes de codes chacune :
The Inheritance Model The Tie Delegation Model
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 org.omg.CORBA.Object ref = rootpoa.servant_to_reference(calculImpl); CalculationService href = CalculationServiceHelper.narrow(ref);
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 CalculationServicePOATie tie = new CalculationServicePOATie(calculImpl, rootpoa); CalculationService href = tie._this(orb);
J'utiliserais le Inheritance Model dans le prochain chapitre.
Activation implicite du servant :
- Dans l’approche par héritage (Inheritance Model), c’est la méthode servant_to_reference(servant) qui active implicitement notre servant.
- Dans l’approche par délégation (Tie Delegation Model), c’est la méthode _this(orb) qui active implicitement notre servant.
Les classes utiles pour :
Le client (option -fclient) Le serveur (option -fserver) CalculationService, CalculationServiceOperations, CalculationServiceHelper, CalculationServiceHolder, _CalculationServiceStub CalculationService, CalculationServiceOperations, CalculationServicePOA
/!\ Attention : Si on utilise le Inheritance Model le serveur nécessite l’utilisation de la classe CalculationServiceHelper, si on utilise le Tie Delegation Model le serveur nécessite l’utilisation de la classe CalculationServiceHelper et CalculationServicePOATie, or la commande idlj -fserver calcul.idl ne génère ni CalculationServiceHelper ni CalculationServicePOATie, il faudra au choix selon le modèle choisit :
- The Inheritance Model : coté serveur, exécuter la commande idlj -fall calcul.idl pour générer CalculationServiceHelper.
- The Tie Delegation Model : coté serveur, exécuter la commande idlj -fall calcul.idl pour générer CalculationServiceHelper puis idlj -fallTIE calcul.idl pour générer CalculationServicePOATie.
/!\ : Il existe un autre modèle appelé BOA, qui est l’ancêtre du modèle POA pour implémenter le code du serveur CORBA, bien que dépréciée, un tutoriel sur ce modèle appelé "ImplBase" est toujours disponible pour ceux qui utilisent Java 1.3 (lien).
TP CORBA Chapitre 4 : Implémentation (partie serveur)
I. Soit l’IDL créée dans le chapitre 1 du TP :
Code IDL : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 module fr { module ekinci { interface CalculationService { long factorial(in long n); }; }; };
II. Générer les fichiers nécessaires avec le programme idlj
Pour utiliser le Inheritance Model, exécuter la commande suivante idlj -fall calcul.idl.
Pour utiliser le Tie Delegation Model, exécuter la commande suivante idlj -fall calcul.idl, puis la commande suivante idlj -fallTIE calcul.idl.
La raison pour laquelle il faut exécuter la 2 commandes pour le Tie Delegation Model :
/!\ : Si vous ne souhaitez pas utiliser le Tie Delegation Model, la seconde commande n’est bien évidemment pas nécessaire.Envoyé par Sun/Oracle -> http://docs.oracle.com/javase/7/docs/technotes/tools/share/idlj.html#tie
III. Créer la classe d’implémentation CalculationServiceImpl.java
Le fichier xxxPOA.java (ou xxxPOATie.java pour une implémentation selon le pattern Tie) représente le skeleton du serveur.
Nous allons créer la classe d'implémentation CalculationServiceImpl qui hérite de CalculationServicePOA et implémente toutes les méthodes définies dans l'interface CalculationServiceOperations :
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 import fr.ekinci.CalculationServicePOA; public class CalculationServiceImpl extends CalculationServicePOA { @Override public int factorial(int n){ if (n < 2) { return 1; } int c = n; while(c != 1){ n *= --c; } return n; } }
/!\ Attention : Vous remarquerez que pour faire simple notre implémentation de factorielle ne gère pas les cas où n est inférieur à zéro et retourne 1.
IV. Créer une classe MainServer pour l’initialisation du serveur
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51 import org.omg.CORBA.*; import org.omg.PortableServer.*; import org.omg.CosNaming.*; import fr.ekinci.*; /** * Classe pour lancer le programme du serveur * @author Gokan EKINCI */ public class MainServer { public static void main(String args[]){ try { // Initialisation de l'ORB ORB orb = ORB.init(args, null); // Récupérer la référence du RootPOA et activer le POAManager POA rootpoa = POAHelper.narrow(orb.resolve_initial_references("RootPOA")); rootpoa.the_POAManager().activate(); // Créer un servant (instance de classe d'implémentation) et l'enregistrer avec l'ORB CalculationServiceImpl calculImpl = new CalculationServiceImpl(); /* *** DEBUT INHERITANCE MODEL (vous pouvez vous référer aux chapitres précédents pour utiliser le modèle Tie Delegation Model) *** */ // Récupérer une référence du servant org.omg.CORBA.Object servantRef = rootpoa.servant_to_reference(calculImpl); CalculationService service = CalculationServiceHelper.narrow(servantRef); /* *** FIN INHERITANCE MODEL *** */ // Récupérer la référence du service de nommage org.omg.CORBA.Object nsRef = orb.resolve_initial_references("NameService"); NamingContextExt nce = NamingContextExtHelper.narrow(nsRef); // Créer un nom pour le service et ajouter le service String serviceName = "MathServices"; NameComponent nc[] = nce.to_name(serviceName); nce.rebind(nc, service); // Démarrer le service et attendre les requêtes des clients System.out.println("On traite les requêtes des clients ..."); orb.run(); // En attente de nouveaux clients CORBA } catch (Exception e){ System.err.println(e); } } }
V. Démarrer le serveur : Lancer orbd (processus daemon)
Ce programme se trouve dans le même répertoire que le programme idlj, soit "/jdkxxx/bin".
Commande pour exécuter ordb :
OS Ligne de commande Linux orbd -ORBInitialPort 1050 -ORBInitialHost localhost& Windows start orbd -ORBInitialPort 1050 -ORBInitialHost localhost
A savoir :
- En CORBA, le numéro de port serveur par défaut est 1050 (UDP & TCP | Corba Management Agent source).
Dans notre exemple, le service de nommage va utiliser le port 1050 et fonctionner sous notre machine (cf: localhost). Dans un environnement de production il est déconseillé d’utiliser un port par défaut, il est conseillé d’utiliser un numéro de port non-reservé (donc supérieur à 1024). En effet, si un pirate souhaite exploiter une faille connu d’une technologie, la première chose qu’il fera sera de s’attaquer à son port par défaut (ex : 1099 pour RMI, 3306 pour MySQL, 5432 pour PostgreSQL, 1433 pour SQL Server etc).
- Pour les utilisateurs Windows : Le port peut être utilisé par une autre instance d’orbd, utilisez la commande netstat -na pour en savoir plus. Pour arrêter orbd il suffira de quitter la console avec le raccourci Ctrl+C.
- Pour les utilisateurs Linux : Le port peut être déjà utilisé par une autre instance d’orbd, faite un ps aux pour le voir, puis récupérer son PID pour terminer le processus avec un kill.
Aperçu sous Windows :
Un message d’avertissement Windows peut alors se lancer, autoriser l’accès :
Une nouvelle console se lance automatiquement :
Rappel : Pour quitter ordb faire Ctrl+C (si vous quittez les clients ne pourront plus accéder au service).
VI. Démarrer le programme serveur MainServer
Démarrer le programme du serveur avec la commande suivante s’il s’agit d’un Runnable JAR :
start java -jar MainServer.jar -ORBInitialPort 1050 -ORBInitialHost localhost
Si vous lancez le programme MainServer à partir de l’IDE Eclipse, allez dans Run Configurations… > Onglet Arguments > Program Arguments et copier/coller ceci :
-ORBInitialPort 1050 -ORBInitialHost localhost
TP CORBA Chapitre 5 : Implémentation (partie cliente)
Créer une classe MainClient pour l’initialisation du client :
Lancez le programme client à partir de votre IDE, OU BIEN compilez le programme avec javac et lancer le programme java, OU BIEN utilisez la commande suivante si vous avez transformé votre projet en JAR :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 import org.omg.CORBA.*; import org.omg.CosNaming.*; import fr.ekinci.*; /** * Classe pour lancer le programme du client * @author Gokan EKINCI */ public class MainClient { public static void main(String args[]){ try { // Initialisation de l'ORB ORB orb = ORB.init(args, null); // Récupérer la référence du service de nommage org.omg.CORBA.Object nsRef = orb.resolve_initial_references("NameService"); NamingContextExt nce = NamingContextExtHelper.narrow(nsRef); // Générer un objet proxy String serviceName = "MathServices"; CalculationService service = CalculationServiceHelper.narrow(nce.resolve_str(serviceName)); System.out.println("Réponse du serveur : " + service.factorial(5)); // 120 } catch (Exception e) { e.printStackTrace(); } } }
java -jar MainClient.jar -ORBInitialPort 1050 -ORBInitialHost localhost
Rappel : Il est nécessaire de lancer le programme Serveur (MainServer) avant le programme Client (MainClient).
TP CORBA Chapitre 6 : Bonus
Dans les chapitres précédents nous avons vu à quel point le code d’une implémentation CORBA peut-être long à écrire... et ce procédé fastidieux sera toujours aussi répétitif. Si la mise en place du code n’était pas aussi difficile CORBA n’aurait pas perdu sa popularité, qui sait ?
Dans cette partie nous allons réaliser le même exercice avec seulement quelques instructions.
Importer le projet corba-wrapper (lien Github) grâce à Maven (ou autres outils de gestion de dépendances) :
Code XML : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 <dependency> <groupId>com.github.gokan-ekinci</groupId> <artifactId>corba-wrapper</artifactId> <version>1.0</version> </dependency>
Soit la classe CorbaServer :
Créez un projet serveur et utilisez la classe CorbaServer :
/!\ Attention : CalculationServiceImpl hérite de CalculationServicePOA (cf : utilisez le Inheritance Model, PAS le Tie Delegation Model).
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13 import fr.ekinci.corbawrapper.CorbaServer; public class MainServer { public static void main(String args[]) throws Exception { CorbaServer server = new CorbaServer("127.0.0.1", 1050); // Ajouter un service server.addService("MathServices", new CalculationServiceImpl(), CalculationServiceHelper.class); // Démarrer le service server.run(); } }
Soit la classe CorbaClient :
Créez un projet client et utilisez la classe CorbaClient :
Lancer orbd en premier, puis le projet du serveur, puis le projet du client, le résultat obtenu sera le même que dans les chapitres précédents. Simple à mettre en place, n’est-ce pas ?
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13 import fr.ekinci.corbawrapper.CorbaClient; public class MainClient { public static void main(String args[]) throws Exception { CorbaClient client = new CorbaClient("127.0.0.1", 1050); // Récupérer l'objet proxy CalculationService calcul = client.<CalculationService, CalculationServiceHelper>lookup("MathServices", CalculationServiceHelper.class); // Invoquer des méthodes distantes System.out.println("Réponse du serveur : " + calcul.factorial(5)); // 120 } }
Les erreurs récurrentes
Il y a fort à parier que le programme client/serveur CORBA que nous avons mis en place fonctionne en localhost sans aucun problème, mais ce ne sera pas forcément le cas entre 2 machines distinctes :
- Un problème de firewall ou routeur : CORBA utilise un protocole adapté au LAN (Local Area Network). Les firewalls ou routeurs n’aiment pas forcément que 2 entités qui communiquent ensemble changent dynamiquement leur numéro de port... mais c’est le comportement par défaut des protocoles utilisés par CORBA et RMI. Il existe des solutions pour fixer le numéro de port mais nous ne les verrons pas dans ce tutoriel.
- Si vous rencontrez ce type d’exception au lancement de votre programme client sous Linux :
... il suffit d’aller sur la machine du serveur et de remplacer l’adresse du localhost par l’adresse IP réelle de la machine dans le fichier /etc/hosts, enfin redémarrez votre machine pour que les modifications soient prises en compte. Notez que ce type de problème est aussi rencontré par ceux qui font du RMI sous Linux, la solution au problème est identique.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2 WARNING: "IOP00410201: (COMMFAILURE) Connection failure: socketType: IIOPCLEAR_TEXT; hostname: 127.0.0.1;
Conclusion : pour ou contre CORBA ?
Avantages Inconvénients CORBA utilise un protocole binaire (GIOP/IIOP), de ce fait c’est un système plus performant (léger et rapide) que les protocoles basé sur les textes (ex : les web services, cf benchmark lire §3.4). A l’heure où j’écris ce tutoriel, la dernière spécification de la norme CORBA est 3.3 (novembre 2012), mais la plupart des dernières implémentations dans les langages informatiques reposent sur la norme 2.3.1 (octobre 1999). A l’époque CORBA était considéré comme une bonne alternative au DCOM de Microsoft, mais depuis d’autres technologies comme les services Web ont supplantés CORBA. Contrairement à RMI, CORBA a été conçu pour être une solution hétérogène, il est possible d’avoir un serveur en Java et un client en C++ par exemple. Contrairement aux services Web qui utilisent le protocole HTTP adapté au WAN (Wide Area Network), CORBA peut être bloqué par les pare-feu à cause du numéro de port qui change dynamiquement par le serveur CORBA. On limitera l’utilisation de CORBA au LAN. La spécification 1.0 de CORBA date d’août 1991, c’est une norme mature. La volonté de l’OMG est de rendre CORBA hétérogène, cependant le fichier IDL peut contenir des incompatibilités avec les langages d'implémentations (Java, C++ etc). Un même IDL générant du C++ peut rencontrer des problèmes pour générer du Java par exemple (ex : impossible de retranscrire le type long double IDL en Java). Ecrire le code d’une implémentation client/serveur CORBA est très long, très répétitif, très "old-school", mais nous avons vu qu’il suffit d’un simple wrapper comme le projet corba-wrapper pour outrepasser ces difficultés.
Aujourd’hui il existe un autre projet open source concurrent de CORBA : gRPC. Ce dernier est un protocole créé par Google, interopérable, possède une implémentation officielle pour de nombreux langages (C++, Java, Python, C# etc), protocole binaire (HTTP/2)... son défaut ? Qu’il ne soit pas assez connu pour le moment.
Aimeriez-vous avoir un tutoriel gRPC ? N’hésitez pas à me le dire dans les commentaires.