.NET Remoting vs Java RMI

 

Tableau de Synthèse  

.NET Remoting

RMI

Proxy

Dynamique

Statique (rmic) ou dynamique

Squelettes

Intégré au Framework

Intégré au Framework

Objet distribué

Classes

Interfaces Remote

Configuration

Fichier XML

System Property

Annuaire distribué

Aucun (système interne à base de tables d'objRef)

RmiRegistry

Ajout de protocoles

Channels

SocketFactoryImpl

Ajout de formats

Formatters

Serialization

Activation

SingleCall, Singleton ou Client activated

API coté serveur Activable objects

CustomProxy

Custom RealProxy

Dynamic Proxy

Protocoles existants

HTTP, TCP, SOAP

JRMP, IIOP, ORMI (Orion), T3 (WebLogic), ...

Gestion d'erreur

Exceptions Remote

Exceptions Remote

 

 

Introduction

 

La distribution a toujours été le talon d'Achille de COM avec Distributed COM pour diverses raisons. Tout d'abord, DCOM a toujours été très complexe à mettre en oeuvre, son intégration étroite avec le Framework COM contraint les développeurs à utiliser le langage C++ pour profiter pleinement des APIs avancées (Marshalling, Securité, ..). De plus, DCOM fonctionne dans un environnement non managé rendant complexe son intégration pérenne dans .NET. Il n'en fallait pas plus à Microsoft pour proposer .NET Remoting, remplaçant officiel de DCOM et destiné à combler l'ensemble des lacunes de ce dernier.

 

Le but de cet article est de mieux comprendre ce qu'est .NET Remoting au travers d'un exemple pratique, mais aussi d'analyser les différences entre le Framework Java RMI et les API Remoting. Nous étudierons dans les deux systèmes les étapes nécessaires à la création et à l'invocation d'un objet distribué. Ainsi nous verrons :

 

  1. la création et implémentation des objets distribués

  2. la génération des proxy et squelettes

  3. la recherche et l'invocation de méthode à partir d'un client

 

Enfin pour finir, nous exploiterons des fonctionnalités avancées de .NET Remoting à travers les Custom Proxy équivalent  des dynamics Proxy de Java.

 

 

 

La création des objets distribués

La première étape nécessaire pour la mise en place d'un objet distribué consiste à coder l' interface et l'implémentation du serveur. D'ores et déjà, une différence fondamentale oppose les deux Framework : RMI impose la création d'interfaces distribuées alors que Remoting permet d'implémenter directement un serveur dans une classe. Voyons un exemple concret avec le fameux Hello.

 

 

 


using
System;

using System.Runtime.Remoting;

using System.Runtime.Remoting.Channels;

using System.Runtime.Remoting.Channels.Tcp;

 

namespace NETRemotingSamples

{

      /// <summary>

      /// Objet distribué

      /// </summary>

 

      public class HelloServer : MarshalByRefObject

      {

        public HelloServer()

        {

           // activé à chaque appel en "SingleCall"    
         
// activé une fois en "Singleton" (état partagé)

            Console.WriteLine("Objet distribué activé !");

        }

 

        public string HelloMethod(string name)

        {

            Console.WriteLine("HelloMethod : {0} ", name);

            return "Salut " + name  ;

        }

      }

}

 

 

  

 

 

 


HelloServer.cs


package
hello;

 

/**

      Interface définissant le service distant sayHello

*/

 

public interface Hello extends java.rmi.Remote

 

      /**

      Remarquez l'exception levée : RemoteException

      */

      public String helloMethod(String name) throws 

                               java.rmi.RemoteException;

}


Hello.java

____________________ Implémentation ______________________


package
hello;
import java.rmi.server.* ;

 // Class that implements the RMI interface Hello.

 

public class HelloImpl implements Hello, UnicastRemoteObject

{

    // You have to implement the method(s) as promised in

    // the corresponding RMI interface:

 

    public String helloMethod(String name) throws

                              java.rmi.RemoteException {

            System.out.println("Hello.helloMethod called ");

            return  "Salut " + name;

    }

}

 

 

HelloImpl.java

 

 

Remoting utilise la classe MarshalByRefObject, classe de base pour l'ensemble des objets sérialisés par référence. C'est à dire dont la référence sera sauvée sous la forme d'un flux et transportée à travers le réseau. Ces objets ont les caractéristiques suivantes :

  •  Ils sont confinés dans le domaine d'application dans lequel ils ont été créés mais sont accessibles directement depuis n'importe quel contexte résidant dans le même domaine. Un domaine d'application est l'équivalent d'un sous-process dans le Runtime. Plus précisément, c'est un moyen de compartimenter un processus en plusieurs espaces logiques. Plusieurs classes peuvent être chargées dans différents domaines d'application à l'intérieur du même processus.

  •  Si un MarshalByRefObject est passé à l'extérieur d'un domaine par référence, c'est à dire sous la forme d'un paramètre de méthode ou sous la forme d'une valeur de retour, la référence est passée du domaine source vers le domaine cible. On dit qu'elle est "marschallée". Cela se produit lorsqu'un appel est réalisé entre différents domaines d'application dans le même processus, dans plusieurs processus différents, ou sur plusieurs machines différentes.

Le code du serveur ressemble à n'importe quel autre objet, il contient des méthodes publiques ou privées et peut dériver d'une interface quelconque, mais ce n'est pas obligatoire. En RMI, toute interface distribuée doit être typée java.rmi.server.Remote et l'implémentation dériver de UnicastRemoteObject, alors qu'en Remoting aucun typage particulier n'est nécessaire.

 

L'enregistrement du serveur distribué dans l'annuaire

 

Il existe plusieurs paramètres d'enregistrement d'un serveur dans Remoting :

  • Single Call ou Singleton à état partagé (SingleCall)

  • Géré par client dont l'état n'est pas partagé (Client activated Objects)

L'enregistrement se fait via l'appel à la méthode : RemotingConfiguration.RegisterWellKnownServiceType()  prenant en paramètre :

  • Le type de la classe distribuée (HelloServer)

  • L'URL du serveur

  • Le mode d'activation

Vous remarquerez que le nom de la DLL (helloserver) est spécifié pour indiquer au Framework que l'implémentation du serveur (et à fortiori son interface) se trouve dans ce fichier généré lors du processus de compilation.

 

MainServer.cs


using System;

using System.Runtime.Remoting;

using System.Runtime.Remoting.Channels;

using System.Runtime.Remoting.Channels.Tcp;

 

 

namespace NETRemotingSamples

{

      /// <summary>

      /// Main permettant de réaliser l'enregistrement .

      /// </summary>

  public class MainServer

  {

 

   public static int Main(string [] args)

    {

 

      // Déclaration des channels

      TcpChannel chan = new TcpChannel(8085);

      ChannelServices.RegisterChannel(chan);

 

    // Enregistrement du serveur

     RemotingConfiguration.RegisterWellKnownServiceType(

              Type.GetType("HelloServer,helloserver"),

            "SayHello", WellKnownObjectMode.Singleton);

     System.Console.WriteLine("Hit <enter> to exit");

     System.Console.ReadLine();

     return 0;

    }          

  }

}

MainServer.java


package
hello;

 

import java.rmi.server.*;

 

public class MainServer

{

  public static void main(String args[])

      {

      // Creation de l'instance

      HelloImpl obj = new HelloImpl("SayHello");

      // Enregistrement de l'objet

      java.rmi.Naming.rebind("rmi://localhost:" +

                              port + "/SayHello", obj);

      System.out.println("Bound RMI object in registry");

      }

}

 

 

 

 

 

 

 

 

 

 

 

 





 

 

 

RMI distingue deux types d'objets distribués : les singletons comme Remoting et les SingleCall  en utilisant l'API d'activation. En Java, les objets sont chargés relativement à une machine virtuelle et le marshalling s'opère entre les JVM, la notion de domaine d'application n'existe pas.

 


Les Channels

Le concept de channel a été intégré dans Remoting par Microsoft dans le but de rendre le modèle de distribution complètement paramétrable. L'intérêt est de pouvoir spécifier l'utilisation de différents protocoles en fonction de ces besoins. Il est vrai que la multiplicité des formats de messages et de protocoles de nos jours est un facteur de complexité pour les fournisseurs d'ORB qui doivent sans cesse adapter leurs middleware aux nouveaux formats. A l'heure actuelle, Remoting propose par défaut Http et TCP. Chaque protocole étant souvent associé à un format de représentation des messages, le Framework propose par défaut d'utiliser des Sérialisateurs de messages Soap avec Http et binaire pour TCP. Rien ne vous empêche par exemple de créer vous-même votre propre channel ou utiliser vos formats de messages IIOP (Corba) pour TCP.

 

Concernant RMI, la situation est quelque peu différente. Jusqu'à la version 1.2 du Jdk, les classes de personnalisation existaient mais n'étaient pas publiques car intégrées dans la couche de Transport. Sun, à l'époque,  préférait privilégier son protocole maison JRMP basé sur des sockets TCP. Depuis, il est dorénavant possible d'ajouter de nouveaux protocoles ou d'enrichir les protocoles existants (IIOP et JRMP) via la classe RMISocketFactory tel que décrit ici.

 

L'annuaire

Rmi passe par un annuaire appelé la RmiRegistry pour stocker les références (Proxies) vers des serveurs distants. Ce service, lui-même objet distribué, doit être lancé manuellement ou dynamiquement avant toute communication entre un client et un serveur. .NET adopte une approche différente dans la mesure où le client communique directement avec le serveur sur un port spécifié entre les deux parties au niveau de l'URL.Dans la pratique, implémenter un Naming Service dans Remoting ne relève pas de l'impossible. Il suffit d'écrire un custom service au dessus d'un channel quelconque et de masquer l'opération d'enregistrement à l'utilisateur pour le rediriger vers notre code. Gageons qu'à l'avenir, Microsoft proposera ce genre de service primordial dans les architectures distribuées. D'ailleurs, dans RMI, la RmiRegistry est elle-même un simple serveur distant.

 

 

Ecrire le client 

Le client dans .NET ressemble beaucoup au client RMI. En effet, le but de l'opération consiste à récupérer un Proxy représentant l'objet distant et à invoquer par délégation une méthode sur l'objet distribué. Sur le fond, si RMI utilise le proxy généré par RMIC (générateur de Stub), Remoting utilise un mécanisme beaucoup plus complexe basé sur le concept de TransparentProxy très proche du Dynamic Proxy de Java.

C'est pourquoi, dans les étapes de déploiement, vous ne verrez pas de génération de code concernant les Proxy.

 

 

HelloClient.cs


using System;

using System.Runtime.Remoting;

using System.Runtime.Remoting.Channels.Http;

 

using NETRemotingSamples;

 

public class HelloClient

{

  public static void Main(String[] args)

   {

    // passe l'url serveur et son type
    // récupère un proxy à l'aide de l'activator

    HelloServer helloProxy = (HelloServer)

                           Activator.GetObject(

                           typeof(HelloServer),
                           "tcp://localhost:8085/SayHello");

           

    // On effectue l'appel de la méthode            

    helloProxy.HelloMethod("salut bilou !"));

   }

}

HelloClient.java


package
hello;

import java.rmi.* ;

 

 

 

 

public class HelloClient

{

 

      public static void main(String args[])

      {

         Hello helloProxy =

 

 

         (Hello) Naming.lookup(

                  "//" + host + ":" + port + "/SayHello");

 

        // On effectue l'appel de la méthode  

         String message = helloProxy.helloMethod("bilou");

      }

}

 

La méthode Activator.GetObject() se charge donc de créer à la volée un proxy avec la DLL du serveur. Nous verrons par la suite qu'il est possible de redéfinir le proxy utilisé par défaut afin d'intercepter les appels vers l'objet distribué.

  

Compilation, déploiement et exécution

 

Etudions les différentes étapes de la compilation :

 

1)  Compilation de l'objet distribué
 

 


Un fichier helloserver.dll contenant l'implémentation du serveur est généré. Ce fichier doit se trouver dans le même répertoire que
le client et le serveur ou placé dans le GAC, dans tous les cas, visible des deux parties. 

 

2) Compilation du MainServer

Le MainServer est compilé en utilisant le fichier DLL précédent car il se charge de son activation lors des appels.

 

3) Compilation du client

Le client utilise la même DLL lors de la compilation pour permettre à l'Activator de générer le proxy. D'ailleurs, placer le fichier contenant l'implémentation du serveur coté client peut paraître troublant car en RMI le client dispose uniquement d'un Proxy ne contenant que des méthodes techniques (invocation, transport, marshalling). Cela peut poser quelques soucis de sécurité et nuit au concept de couplage client->interface. 

 

Voici la fenêtre d'exécution après avoir fait "d:\start MainServer.exe" et lancé le client "Client.exe" :

 

 

 

 

 Les Custom Formatters

Ce principe consiste à implémenter un sérialisateur/désérialisateur spécifique de messages par l'intermédiaire d'une classe respectant une interface donnée du Framework. Par défault, Remoting propose deux Custom Formatters: BinaryFormatter associé au channel TCP et SoapFormatter associé au channel Http. Il est aussi possible de les combiner pour optimiser les performances (par exemple SoapBinaire).

 

Les fichiers de configuration

 

Les fichiers de configuration permettent de paramétrer entièrement le système Remoting par l'intermédiaire d'un fichier XML. L'exemple suivant nous illustre la manière de configurer les serveurs, channels, formatters, etc. ...

 

 

<configuration>

<system.runtime.remoting>

<application name="MyFoo">

  <service>

    <wellknown type="Foo, common" objectUri="Foo.soap" mode="Singleton"/>

  </service>

  <channels>

    <channel ref="http server" name="MyHttpChannel" port="9000">

      <serverProviders>       

        <provider ref="ip filter" mode="accept">

          <filter mask="255.255.255.255" ip="127.0.0.1" />         

        </provider>

        <formatter ref="soap" />

      </serverProviders>

    </channel>

  </channels>

</application>

<channelSinkProviders>

  <serverProviders>

    <provider id="ip filter" type="IPFilter.IPFilterChannelSinkProvider, IPFilterSink" />

  </serverProviders>

</channelSinkProviders></system.runtime.remoting>

</configuration>

 

Remoting.xml

 

 

Pour charger le fichier suivant dans un client .NET Remoting, il vous suffit d'utiliser la méthode Configure() décrite ci après :

 

 

using System;

using System.IO;

using System.Runtime.Remoting;

 

public class MyHost

{

      public static void Main(String[] args)

      {

            RemotingConfiguration.Configure ("Remoting.xml);

      }

}

 

  

Résumé

 

Microsoft n'a pas réinventé la poudre, les mécanismes classiques de distribution via le principe de Proxy ont encore de beaux jours devant eux. La seule différence provient du fait que RMI propose, par défaut,  une génération statique de Proxy et que .NET Remoting se base sur un Transparent Proxy (équivalent du Dynamic Proxy de Java). D'ailleurs, dans le monde JAVA, bon nombre de serveurs d'applications EJB fonctionnent déjà avec le mécanisme de Dynamic Proxy (JBoss.org) qui a tendance à remplacer la génération de code. Concernant les autres caractéristiques de .NET Remoting, nous pouvons remarquer le modèle de développement original basé sur les channels marquant une première ouverture des APIs de Microsoft à des implémentations tierces. A l'heure où nous écrivons ces quelques lignes, peut-être quelqu'un a t-il déjà pensé à implémenter un Message Formatter pour IIOP au dessus du channel TCP ? ;-)

 

Dans de prochains articles, nous aborderons l'aspect intégration avec COM+, la Sécurité, totalement occultée dans cet article, ainsi que le mode de fonctionnement du Ramasse miette distribué basé sur le principe de Leases.

 

Auteur : Sami Jaber