I. 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 obligatoires :
- être à 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ée 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/technotes/guides/idl/corba.html
II. Introduction▲
CORBA, acronyme de Common Object Request Broker Architecture, est une architecture logicielle, pour le développement de composants et d'Object Request Broker ou ORB. Ces composants, qui sont assemblés afin de construire des applications complètes, peuvent être écrits dans des langages de programmation distincts, être exécutés dans des processus séparés, voire être déployés sur des machines distinctes.
Source : Wikipédia
Pour qu'il y ait une communication entre un client et un serveur CORBA, il faut que le client et le serveur possèdent chacun un ORB : une application client-serveur possède au minimum deux ORB.
Un ORB est un ensemble de fonctions (classes Java, bibliothèques C++…) qui implémentent un « bus logiciel » par lequel des objets envoient et reçoivent des requêtes et des réponses, de manière transparente et portable : il s'agit de l'activation ou de l'invocation à distance par un objet, d'une méthode d'un autre objet distribué, en pratique les objets invoqués sont souvent des services.
Source : Wikipédia
Étudions le schéma suivant :
|
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-il 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 deux 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 et 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.
III. Définitions▲
GIOP |
La spécification du protocole réseau utilisé par CORBA s'appelle GIOP (General Inter ORB Protocol).
|
Stub |
Le Stub est une portion de code côté 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 |
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 (côté serveur). La classe d'implémentation devra hériter des classes POA. |
Objet Proxy |
Un objet proxy est un objet qui est le représentant du servant (côté client). |
IOR |
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 |
Le « narrowing » est une opération consistant à retourner un « Objet Proxy » à partir d'une IOR. |
POA |
Le POA ou « Adaptateur d'objet » en français, est l'entité côté serveur qui gère les IOR et les servants. |
AOM |
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 ORB dans le cas de CORBA) :
=> 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 deux 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.
IV. Informations complémentaires▲
Il existe 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éée 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 côté client pour envoyer des requêtes.
V. 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é côté 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 deux méthodes ayant le même nom, même si leurs signatures (nom et paramètres) sont différentes.
- 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électionnez 1. 2. 3. 4. 5. 6. 7.
|
Code Java Sélectionnez 1. 2. 3. 4. 5.
Code Java Sélectionnez 1. 2. 3. 4. 5. 6. 7.
|
VI. Les types IDL/Java▲
IDL |
Java (mode in) |
Java (mode out ou inout) |
---|---|---|
|
|
|
|
|
org.omg.CORBA.BooleanHolder |
|
|
org.omg.CORBA.CharHolder |
|
|
org.omg.CORBA.CharHolder |
|
|
org.omg.CORBA.ByteHolder |
|
|
org.omg.CORBA.ShortHolder |
|
|
org.omg.CORBA.ShortHolder |
|
|
org.omg.CORBA.IntHolder |
|
|
org.omg.CORBA.IntHolder |
|
|
org.omg.CORBA.LongHolder |
|
|
org.omg.CORBA.LongHolder |
|
|
org.omg.CORBA.FloatHolder |
|
|
org.omg.CORBA.DoubleHolder |
|
Incompatibilité en Java |
Incompatibilité en Java |
|
java.lang.String |
org.omg.CORBA.StringHolder |
|
java.lang.String |
org.omg.CORBA.StringHolder |
|
org.omg.CORBA.Object |
org.omg.CORBA.ObjectHolder |
Exemple de prototype de méthode IDL :
2.
3.
void
meth1
(
);
long
meth2
(
in
long
param1);
string
meth3
(
in
long
param1, out
octet
param2, inout
string
param3);
Les mots-clés in , out et inout sont obligatoires pour les paramètres de méthode
-
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.
VII. Les modules▲
L'équivalent du package Java est module en CORBA :
IDL |
Extrait du code Java généré à partir de l'IDL |
---|---|
Code IDL Sélectionnez 1. 2. 3. 4. 5.
|
Code Java Sélectionnez 1. 2. 3.
|
À savoir :
- l'exemple IDL ci-dessus ne fonctionnera pas avec idlj, car le programme s'attend à ce qu'on ajoute au moins une interface, struct, ou enum dans le module ;
- Javadoc de l'interface IDLEntity.
VIII. 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électionnez 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24.
|
Code Java Sélectionnez 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12.
Code Java Sélectionnez 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11.
Code Java Sélectionnez 1. 2. 3.
Code Java Sélectionnez 1. 2. 3. 4. 5. 6. 7.
|
À 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 deux méthodes ayant le même nom, même si leurs signatures (nom et paramètres) sont différentes ;
- 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 douze 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.
IX. 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électionnez 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.
|
Code Java Sélectionnez 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19.
|
À 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.
X. Les values type▲
IDL |
Extrait du code Java généré à partir de l'IDL |
---|---|
Code IDL Sélectionnez 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12.
|
Code Java Sélectionnez 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.
Code Java Sélectionnez 1. 2. 3. 4. 5. 6. 7. 8.
|
À savoir :
- les valuetype ont été introduits 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 huit fichiers :
- ParentValue.java, ParentValueDefaultFactory.java, ParentValueHelper.java, ParentValueHolder.java ;
- ChildValue.java, ChildValueDefaultFactory.java, ChildValueHelper.java, ChildValueHolder.java.
XI. Les alias et les tableaux▲
Pour créer un attribut de type tableau dans une struct IDL :
IDL |
Extrait du code Java généré à partir de l'IDL |
---|---|
|
|
La taille 10 est gérée dans la classe xxxHelper Pour créer un attribut de type tableau sans une limite prédéfinie dans une struct IDL il faut utiliser le mot-clé sequence :
IDL |
Extrait du code Java généré à partir de l'IDL |
---|---|
sequence |
|
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 :
IDL |
Extrait du code Java généré à partir de l'IDL |
---|---|
Sélectionnez 1. 2.
|
|
Sélectionnez 1. 2.
|
|
Sélectionnez 1. 2.
|
|
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.
XII. Les énumérations▲
IDL |
Extrait du code Java généré à partir de l'IDL |
---|---|
Sélectionnez 1. 2. 3. 4. 5. 6. 7.
|
Sélectionnez 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.
|
La commande idlj.exe -fall myfile.idl va générer trois fichiers :
- Toto.java ;
- TotoHolder.java ;
- TotoHelper.java.
XIII. Les exceptions▲
IDL |
Extrait du code Java généré à partir de l'IDL |
---|---|
Sélectionnez 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21.
|
Sélectionnez 1. 2. 3. 4. 5. 6. 7.
Sélectionnez 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14.
|
XIV. Les opérations asynchrones▲
À savoir :
- une exception peut contenir des paramètres :
2.
3.
4.
exception
MyException{
string
reason;
string
sentence;
}
;
- un prototype de méthode peut indiquer qu'il est possible de lancer plusieurs exceptions :
void
method
(
) raises
(
MyException1, MyException2, MyException3);
La commande idlj -fall myfile.idl va générer neuf fichiers :
- DistantTransaction.java, DistantTransactionHolder.java, DistantTransactionHelper.java, DistantTransactionOperations.java, DistantTransactionPOA.java, _DistantTransactionStub.java ;
- TransactionException.java, TransactionExceptionHolder.java, TransactionExceptionHelper.java.
XV. 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 deux secondes pour exécuter une méthode, le client devra attendre ces deux 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 :
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.
Si vous êtes familier avec les environnements multithreads et avez connaissance des problèmes de concurrences, vous savez que les méthodes asynchrones sont à manipuler avec prudence.
XVI. 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.idl. Avec le paramètre -fall on va générer plusieurs fichiers de code Java pour la partie client et serveur.
Le nombre de fichiers générés 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 trois 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'informations 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électionnez 1. 2. 3. 4. 5. 6. 7.
|
Code Java Sélectionnez 1. 2. 3. 4. 5. 6. 7.
|
Six 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 finale 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. |
XVII. 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 :
À savoir :
- CalculationServiceImpl a été créée à la main et n'a pas été générée 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 ».
XVIII. TP CORBA Chapitre 3 : Le modèle POA (partie serveur seulement)▲
Il existe deux 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 deux 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 a 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ée à chaque appel de méthode distante.
You might want to use the Tie model instead of the typical Inheritance model if your implementation must inherit from some other implementation. Java allows any number of interface inheritance, but there is only one slot for class inheritance. If you use the inheritance model, that slot is used up. By using the Tie Model, that slot is freed up for your own use. The drawback is that it introduces a level of indirection: one extra method call occurs when invoking a method.
Source : Sun/Oracle
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
La différence entre ces deux modèles de POA au niveau de l'implémentation du serveur est relativement faible, et se résume à deux lignes de codes chacune :
The Inheritance Model |
---|
Code Java Sélectionnez 1. 2. 3.
|
The Tie Delegation Model |
---|
Code Java Sélectionnez 1. 2.
|
J'utiliserai 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 |
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 choisi :
- The Inheritance Model : côté serveur, exécuter la commande idlj -fall calcul.idl pour générer CalculationServiceHelper :
- The Tie Delegation Model : côté 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é, un tutoriel sur ce modèle appelé « ImplBase » est toujours disponible pour ceux qui utilisent Java 1.3 (lien).
XIX. TP CORBA Chapitre 4 : Implémentation (partie serveur)▲
XIX-A. Soit l'IDL créée dans le chapitre 1 du TP ▲
2.
3.
4.
5.
6.
7.
module
fr {
module
ekinci {
interface
CalculationService {
long
factorial
(
in
long
n);
}
;
}
;
}
;
XIX-B. 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 les deux commandes pour le Tie Delegation Model :
Because it is not possible to generate ties and skeletons at the same time, they must be generated separately. Source : Sun/Oracle
Si vous ne souhaitez pas utiliser le Tie Delegation Model, la seconde commande n'est bien évidemment pas nécessaire.
XIX-C. 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 :
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;
}
}
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.
XIX-D. Créer une classe MainServer pour l'initialisation du serveur▲
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);
}
}
}
XIX-E. 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 |
Windows |
start orbd -ORBInitialPort 1050 -ORBInitialHost localhost |
À 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 réservé (donc supérieur à 1024). En effet, si un pirate souhaite exploiter une faille connue 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, faites 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 :
Pour quitter ordb faire Ctrl+C (si vous quittez les clients ne pourront plus accéder au service).
XIX-F. 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.
XX. TP CORBA Chapitre 5 : Implémentation (partie cliente)▲
Créer une classe MainClient pour l'initialisation du client :
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.
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
(
);
}
}
}
Lancez le programme client à partir de votre IDE, OU BIEN compilez le programme avec javac et lancez le programme java, OU BIEN utilisez la commande suivante si vous avez transformé votre projet en JAR :
java -
jar MainClient.jar -
ORBInitialPort 1050
-
ORBInitialHost localhost
Il est nécessaire de lancer le programme Serveur (MainServer) avant le programme Client (MainClient).
XXI. 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) :
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 :
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
(
);
}
}
CalculationServiceImpl hérite de CalculationServicePOA (cf. : utilisez le Inheritance Model, PAS le Tie Delegation Model).
Soit la classe CorbaClient :
Créez un projet client et utilisez la classe CorbaClient :
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
}
}
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 ?
XXII. 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 deux 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 deux 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 :
WARNING: "IOP00410201: (COMMFAILURE) Connection failure: socketType:
IIOPCLEAR_TEXT; hostname: 127.0.0.1;
… 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.
XXIII. 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és sur les textes (ex. : les web services, cf. benchmark lire §3.4). |
À 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). À l'époque CORBA était considéré comme un bon substitut au DCOM de Microsoft, mais depuis d'autres technologies comme les services Web ont supplanté 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). |
Écrire 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.
XXIV. Remerciements ▲
Nous tenons à remercier Claude Leloup pour la relecture orthographique, et Malick SECK pour la mise au gabarit.