IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java

  Chapitre 15 - Informatique distribuée

pages : 1 2 3 4 5 6 7 8 9 10 11 12 

Si l'on compile et exécute PerfectTime.java, cela ne fonctionnera pas même si rmiregistry a été correctement mis en route. Cela parce que la machinerie pour RMI n'est pas complète. Il faut d'abord créer les stubs et skeletons qui assurent les opérations de connexions réseaux et permettent de faire comme si l'objet distant était juste un objet local sur de la machine.

Ce qui se passe derrière la scène est complexe. Tous les objets envoyés à un objet distant ou reçus de celui-ci doivent implémenter Serializable (pour passer des références distantes plutôt que les objets complets, les arguments peuvent implémenter Remote), ainsi on peut considérer que les stubs et les skeletons assurent automatiquement la sérialisation et la déserialisation tout en gérant l'acheminement des arguments et du résultat au travers du réseau. Par chance, vous n'avez rien à connaître de tout cela, mais vous devez avoir créé les stubs et les skeletons. C'est une procédure simple : on invoque l'outil rmic sur le code compilé, et il crée les fichiers nécessaires. La seule chose obligatoire est donc d'ajouter cette étape à la procédure de compilation.

L'outil rmic est particulier en ce qui concerne les packages et les classpaths. PerfectTime.java est dans le package c15.rmi, et même rmic est invoqué dans le même répertoire que celui où se trouve PerfectTime.class, rmic ne trouvera pas le fichier, puisqu'il se repère grâce au classpath. Vous devez ainsi préciser la localisation du classpath, comme ceci :

rmic c15.rmi.PerfectTime

La commande ne nécessite pas d'être exécutée à partir du répertoire contenant PerfectTime.class, mais les résultats seront placés dans le répertoire courant.

Lorsque rmic a été exécuté avec succès, deux nouvelles classes sont obtenues dans le répertoire :

PerfectTime_Stub.class
PerfectTime_Skel.class

correspondant au stub et au skeleton. Dès lors, vous êtes prêt à faire communiquer le serveur et le client.

Utilisation de l'objet distant

Le but de RMI est de simplifier l'utilisation d'objets distants. La seule chose supplémentaire qui doit être réalisée dans le programme client est de rechercher et de rapatrier depuis le serveur l'interface distante. Après quoi, c'est simplement de la programmation Java classique : envoyer des messages aux objets. Voici le programme qui utilise PerfectTime:

//: c15:rmi:DisplayPerfectTime.java
// Utilise l'objet distant PerfectTime.
package c15.rmi;
import java.rmi.*;
import java.rmi.registry.*;

public class DisplayPerfectTime {
  public static void main(String[ « args) {
    System.setSecurityManager(
      new RMISecurityManager());
    try {
      PerfectTimeI t =        (PerfectTimeI)Naming.lookup(
          "//peppy:2005/PerfectTime");
      for(int i = 0; i < 10; i++)
        System.out.println("Perfect time = " +
          t.getPerfectTime());
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
} ///:~

L'identifiant alpha-numérique est le même que celui utilisé pour enregistrer l'objet avec Naming, et la première partie représente l'adresse et le numéro du port. Cette URL permet par exemple de désigner une machine sur Internet.

Ce qui est retourné par Naming.lookup( ) doit être transtypé vers l'interface distante, pas vers la classe. Si l'utilisation de la classe à la place renverrait une exception.

Vous pouvez observer dans l'appel de la méthode

t.getPerfectTime( )

qu'une fois la référence vers l'objet distant obtenue, la programmation avec celle-ci ou avec un object distant est identique (avec une différence : les méthodes distantes émettent RemoteException).

Introduction à CORBA

Dans le cadre d'importantes applications distribuées, vos besoins risquent de ne pas être satisfaits par les approches précédentes : comme par exemple, l'intégration de bases existantes, ou l'accès aux services d'un objet serveur sans se soucier de sa localisation physique. Ces situations nécessitent une certaine forme de Remote Procedure Call (RPC), et peut-être une indépendance par rapport au langage. Là, CORBA peut vous aider.

CORBA n'est pas une fonctionnalité du langage ; c'est une technologie d'integration. C'est une spécification que les fabricants peuvent suivre pour implémenter des produits supportant une intégration CORBA. CORBA fait partie du travail réalisé par OMG afin de définir une structure standard pour l'interopérabilité d'objets distribués et indépendants du langage.

CORBA fournit la possibilité de faire des appels à des procédures distantes dans des objets Java et des objets non-Java, et d'interfacer des systèmes existants sans se soucier de leur emplacement. Java ajoute un support réseau et un langage orienté-objet agréable pour construire des applications graphiques ou non. Le modèle objet de l'OMG et celui de Java vont bien ensemble ; par exemple, Java et CORBA mettent tout deux en oeuvre le concept d'interface et le modèle de référence objet.

Principes de base de CORBA

La spécification de l'interopérabilité objet est généralement désignée comme l'Object Manager Architecture (OMA). L'OMA définit deux composants : le Core Object Model et l'OMA Reference Architecture. Le Core Object Model met en place les concepts de base d'objet, d'interface, d'opération, et ainsi de suite (CORBA est un raffinement de Core Object Model). L'OMA Reference Architecture définit l'infrastructure sous-jacente des services et des mécanismes qui permettent aux objets d'inter-opérer. L'OMA Reference Architecture contient l'Object Request Broker (ORB), les Object Services (désignés aussi comme les services CORBA) et les outils communs.

L'ORB est le bus de communication par lequel les objets peuvent réclamer des services auprès des autres objets, sans rien connaître de leur emplacement physique. Cela signifie que ce qui ressemble à un appel de méthode dans le code client est vraiment une opération complexe. D'abord, une connexion avec l'objet servant doit exister et pour créer cette connexion, l'ORB doit savoir où se trouve le code implémentant cette partie serveur. Une fois que la connexion est établie, les arguments de la méthode doivent être arrangés (marshaled), c'est à dire convertis en un flux binaire pour être envoyés à travers le réseau. Les autres informations qui doivent être envoyées sont le nom de la machine serveur, le processus serveur et l'identité de l'objet servant au sein de ce processus. Finalement, l'information est envoyée par l'intermédiaire d'un protocole bas-niveau, elle est décodée du côté du serveur, l'appel est exécuté. L'ORB masque tout de cette complexité au programmeur et rend l'opération presque aussi simple que l'appel d'une méthode d'un objet local.

Cette spécification n'a pas pour but d'expliquer comment le coeur de l'ORB devrait être implémenté, mais elle permet une compatibilité fondamentale entre les différents ORBs des fournisseurs, l'OMG définit un ensemble de services qui sont accessibles par l'intermédiaire d'interfaces standards.

CORBA Interface Definition Language (IDL - Langage de Définition d'Interface)

CORBA a été mis au point pour être transparent vis-à-vis du langage : un objet client peut appeler des méthodes d'un objet serveur d'une classe différente, sans se préoccuper du langage avec lequel elles sont implémentées. Bien sûr, l'objet client doit connaître le nom et les prototypes des méthodes que l'objet servant met à disposition. C'est là que l'IDL intervient. L'IDL de CORBA est un moyen indépendant du langage qui permet de préciser les types de données, les attributs, les opérations, les interfaces, et plus encore. La syntaxe de l'IDL est similaire à celles du C++ ou de Java. La table qui suit montre la correspondance entre quelques uns des concepts communs au trois langages qui peuvent être spécifiés à travers l'IDL de CORBA :

CORBA IDL Java C++
Module Package Namespace
Interface Interface Pure abstract class
Method Method Member function

Le concept d'héritage est également supporté, en utilisant le séparateur deux-points comme en C++. Le programmeur écrit en IDL la description des attributs, des méthodes et des interfaces qui seront implémentés et utilisés par le serveur et les clients. L'IDL est ensuite compilé par un compilateur IDL/Java propre au fournisseur, qui lit le source IDL et génère le code Java.

Le compilateur IDL est un outil extrêmement utile : il ne génère pas juste le source Java équivalent à l'IDL, il génère aussi le code qui sera utilisé pour réunir les arguments des méthodes et pour réaliser les appels distants. Ce code, appelé le code stub et le code skeleton, est organisé en plusieurs fichiers source Java et fait habituellement partie d'un même package Java.

Le service de nommage (naming service)

Le service de nommage est l'un des services fondamentaux de CORBA. L'objet CORBA est accessible par l'intermédiaire d'une référence, une information qui n'a pas de sens pour un lecteur humain. Mais les références peuvent être des chaînes de caractères définies par le programmeur. Cette opération est désignée comme chaînifier la référence, et l'un des composants de l'OMA, le service de nommage, est dévoué à la conversion nom-vers-objet et objet-vers-nom et gère ces correspondances. Puisque le service de nommage joue le rôle d'un annuaire téléphonique que les serveurs et les clients peuvent consulter et manipuler, il fonctionne dans un processus séparé. Créer une correspondance objet-vers-nom est appelé lier (binding) un objet, et supprimer cette correspondance est dit délier (unbinding). Obtenir l'objet référence en passant la chaîne de caractères est appelé résoudre le nom.

Par exemple, au démarrage, une application serveur peut créer un objet servant, enregistrer l'objet auprès du service de nommage, et attendre que des clients fassent des requêtes. Un client obtient d'abord une référence vers cet objet servant en résolvant le nom et ensuite peut faire des appels auprès du serveur en utilisant cette référence.

A nouveau, la spécification du service de nommage fait partie de CORBA, mais l'application qui l'implémente est mise à disposition par le fournisseur de l'ORB. Le moyen d'accéder à ce service de nommage peut varier d'un fournisseur à l'autre.

Un exemple

Le code montré ici ne sera pas très élaboré car les différents ORBs ont des moyens d'accéder aux services CORBA qui divergent, ainsi les exemples dépendent du fournisseur. L'exemple qui suit utilise JavaIDL, un produit gratuit de Sun, qui fournit un ORB basique, un service de nommage, et un compilateur IDL-vers-Java. De plus, comme Java est encore jeune et en constante évolution, les différents produits CORBA pour Java n'incluent forcément toutes les fonctionnalités de CORBA.

Nous voulons implémenter un serveur, fonctionnant sur une machine donnée, qui soit capable de retourner l'heure exacte. Nous voulons aussi implémenter un client qui demande l'heure exacte. Dans notre cas, nous allons réalisons les deux programmes en Java, mais nous pourrions faire de même avec deux langages différents (ce qui se produit souvent dans la réalité).

Écrire le source IDL

La première étape consiste à écrire une description en IDL des services proposés. Ceci est généralement réalisé par le programmeur du serveur, qui est ensuite libre d'implémenter le serveur dans n'importe quel langage pour lequel un compilateur CORBA IDL existe. Le fichier IDL est communiqué au programme de la partie cliente et devient le pont entre les langages.

L'exemple qui suit montre la description IDL de notre serveur ExactTime :

//: c15:corba:ExactTime.idl
//# Vous devez installer idltojava.exe de
//# java.sun.com et ajuster le paramétrage pour utiliser
//# votre préprocesseur C local pour compiler
//# ce fichier. Voyez la documentation sur java.sun.com.
module remotetime {
   interface ExactTime {
      string getTime();
   };
}; ///:~

C'est donc la déclaration de l'interface ExactTime au sein de l'espace de nommage remotetime. L'interface est composée d'une seule méthode qui retourne l'heure actuelle dans une chaîne de caractères.

Création des stubs et des skeletons

La deuxième étape consiste à compiler l'IDL pour créer le code Java du stub et du skeleton que nous utiliserons pour implémenter le client et le serveur. L'outil fournit par le produit JavaIDL est idltojava :

idltojava remotetime.idl

Cela générera automatiquement à la fois le code pour le stub et celui pour le skeleton. Idltojava génère un package Java nommé selon le module IDL remotetime et les fichiers Java générés sont déposés dans ce sous-répertoire remotetime. _ExactTimeImplBase.java est le skeleton que nous allons utiliser pour implémenter l'objet servant et _ExactTimeStub.java sera utilisé pour le client. Il y a des représentations Java de l'interface IDL dans ExactTime.java et toute une série d'autres fichiers de support utilisés, par exemple, pour faciliter l'accès aux fonctions du service de nommage.

Implémentation du serveur et du client

Ci-dessous, vous pouvez voir le code pour la partie serveur. L'implémentation de l'objet servant est dans la classe ExactTimeServer. RemoteTimeServer est l'application qui créé l'objet servant, l'enregistre auprès de l'ORB, donne un nom à la référence vers l'objet, et qui ensuite s'assoit tranquillement en attendant les requêtes des clients.

//: c15:corba:RemoteTimeServer.java
import remotetime.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;
import java.util.*;
import java.text.*;

// Implémentation de l'objet servant
class ExactTimeServer extends _ExactTimeImplBase {
  public String getTime(){
    return DateFormat.
        getTimeInstance(DateFormat.FULL).
          format(new Date(
              System.currentTimeMillis()));
  }
}

// Implémentation de l'application distante
public class RemoteTimeServer {
  public static void main(String[ « args) {
    try {
      // Crée l'ORB et l'initialise:
      ORB orb = ORB.init(args, null);
      // Crée l'objet servant et l'enregistre :
      ExactTimeServer timeServerObjRef =        new ExactTimeServer();
      orb.connect(timeServerObjRef);
      // Obtient la racine du contexte de nommage :
      org.omg.CORBA.Object objRef =        orb.resolve_initial_references(
          "NameService");
      NamingContext ncRef =        NamingContextHelper.narrow(objRef);
      // Associe un nom
      // à la référence de l'objet (binding):
      NameComponent nc =        new NameComponent("ExactTime", "");
      NameComponent[ « path = { nc };
      ncRef.rebind(path, timeServerObjRef);
      // Attend les requêtes des clients :
      java.lang.Object sync =        new java.lang.Object();
      synchronized(sync){
        sync.wait();
      }
    }
    catch (Exception e) {
      System.out.println(
         "Remote Time server error: " + e);
      e.printStackTrace(System.out);
    }
  }
} ///:~

Comme vous pouvez le constater, implémenter l'objet servant est simple ; c'est une classe Java classique qui hérite du code du skeleton généré par le compilateur IDL. Les choses se complexifient un peu lorsqu'on en vient à interagir avec l'ORB et les autres services CORBA.

Quelques services CORBA

Voici une courte description de ce qui réalise le code relevant de JavaIDL (en évitant la partie de code CORBA qui dépend du fournisseur). La première ligne dans le main( ) démarre l'ORB parce que bien sûr notre objet servant aura besoin d'interagir avec celui-ci. Juste après l'initialisation de l'ORB, l'objet servant est créé. En réalité, le terme correct serait un objet servant temporaire (transient) : un objet qui reçoit les requêtes provenant des clients, et dont la durée de vie est limitée à celle du processus qui l'a créé. Une fois l'objet servant temporaire créé, il est enregistré auprès de l'ORB, ce qui signifie alors que l'ORB connaît son existence et peut rediriger les requêtes vers lui.

A partir de là, tout ce dont nous disposons est timeServerObjRef, une référence vers un objet qui n'est connu qu'à l'intérieur du processeur serveur actuel. L'étape suivante va consister à associer un nom alphanumérique à cet objet servant ; les clients utiliseront ce nom pour localiser l'objet servant. Cette opération sera réalisée grâce à l'aide du service de nommage. Tout d'abord, nous avons besoin d'une référence vers le service de nommage ; la méthode resolve_initial_references( ) utilise la référence objet « chainifiée » du service de nommage qui s'appelle NameService dans JavaIDL, et retourne la référence vers l'objet. Celle-ci est transformée en une référence spécifique à NamingContext au moyen de la méthode narrow( ). Nous pouvons maintenant utiliser les services de nommage.

Pour associer l'objet servant avec une référence objet « chainifiée », nous créons d'abord un objet NameComponent, initialisé avec la chaîne de caractères qui sera associée : face="Georgia">« ExactTime ». Ensuite, en utilisant la méthode rebind( ), la référence alphanumérique est associée à la référence vers l'objet. Nous utilisons rebind( ) pour mettre en place une référence, même si celle-ci existe déjà. Un nom est composé dans CORBA d'une séquence de NameComponents (voilà pourquoi nous utilisons un tableau pour associer le nom à la référence).

L'objet est enfin prêt à être utilisé par des clients. A ce moment-là, le serveur entre dans un état d'attente. Encore une fois, ceci est nécessaire puisqu'il s'agit d'un serveur temporaire : sa durée de vie dépend du processus serveur. JavaIDL ne supporte pas actuellement les objets persistants, qui survivent après la fin de l'exécution du processus qui les a créés.

Maintenant que nous avons une idée de ce que fait le code du serveur, regardons le code du client :

//: c15:corba:RemoteTimeClient.java
import remotetime.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;

public class RemoteTimeClient {
  public static void main(String[ « args) {
    try {
      // Création et initialisation de l'ORB :
      ORB orb = ORB.init(args, null);
      // Obtient la racine du contexte de nommage :
      org.omg.CORBA.Object objRef =        orb.resolve_initial_references(
          "NameService");
      NamingContext ncRef =        NamingContextHelper.narrow(objRef);
      // Résout la référence alphanumérique
      // du serveur d'heure :
      NameComponent nc =        new NameComponent("ExactTime", "");
      NameComponent[ « path = { nc };
      ExactTime timeObjRef =        ExactTimeHelper.narrow(
          ncRef.resolve(path));
      // Effectue une requête auprès de l'objet servant :
      String exactTime = timeObjRef.getTime();
      System.out.println(exactTime);
    } catch (Exception e) {
      System.out.println(
         "Remote Time server error: " + e);
      e.printStackTrace(System.out);
    }
  }
} ///:~

Ce livre a été écrit par Bruce Eckel ( télécharger la version anglaise : Thinking in java )
Ce chapitre a été traduit par Jean-Pierre Vidal, Alban Peignier ( groupe de traduction )
télécharger la version francaise (PDF) | Commandez le livre en version anglaise (amazon) | télécharger la version anglaise
pages : 1 2 3 4 5 6 7 8 9 10 11 12 
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java