Programmation d'Applications Réparties avec RPC et Java RMI

Ce document présente les concepts fondamentaux et les techniques de programmation d’applications réparties à l’aide de RPC (Remote Procedure Call) et Java RMI (Remote Method Invocation). Il s’adresse aux étudiants et développeurs souhaitant comprendre et mettre en œuvre des communications distribuées en réseau, notamment sous Unix et en environnement Java orienté objet.

D'après le document Programmation d'Applications Réparties avec RPC et Java RMI

Cet article a été rédigé automatiquement à partir du document source, puis vérifié avant publication.

Document source

Programmation d'Applications Réparties avec RPC et Java RMI

Programming, RPC, RMI, Networking · PDF · 121 pages · 2049

Afficher l'aperçu du document

Consulter le document original →

Ce document présente les concepts fondamentaux et les techniques de programmation d’applications réparties à l’aide de RPC (Remote Procedure Call) et Java RMI (Remote Method Invocation). Il s’adresse aux étudiants et développeurs souhaitant comprendre et mettre en œuvre des communications distribuées en réseau, notamment sous Unix et en environnement Java orienté objet.

Programmation réseau sous Unix et RPC

En environnement Unix, l’utilisateur ne peut pas accéder directement aux protocoles situés dans le noyau. Il utilise les protocoles TCP et UDP via des sockets, qui sont des interfaces similaires à celles du système de fichiers, servant de portes vers le réseau. Le programmeur peut accéder directement aux sockets (couche 4), sans passer par les couches supérieures 5 et 6, ce qui déroge au modèle OSI de l’ISO.

Qu’est-ce que RPC ?

RPC (Remote Procedure Call) est un mécanisme d’appel de procédures ou fonctions à distance. Il offre une abstraction masquant le protocole de communication et la distribution, assurant ainsi une transparence pour le programmeur.

Composants de RPC

  • IDL (Interface Definition Language) : langage de description des données et fonctions, proche du C.
  • Méthode de représentation portable des données.
  • Protocole d’appel : transmission des paramètres du client au serveur et du résultat du serveur au client.
  • Protocole de publication et de recherche d’un service.

Types de RPC

  • RPC par host-serveur : le client spécifie le serveur hôte. Exemples : SUN ONC/RPC, OSF DCE.
  • RPC par objet-serveur : le client ne désigne pas explicitement le serveur. Exemples : OMG CORBA, SUN Java RMI, Microsoft DCOM.
  • RPC intégrées dans les systèmes de composants : SUN J2EE EJB, OMG CCM, WS-SOAP.

Exemple : ONC RPC (SUN)

ONC RPC est une solution classique disponible sur Unix et Windows, utilisant le protocole XDR (eXchange Data Representation) pour la description et l’échange des données. Il est notamment utilisé par NFS (Network File System). L’outil rpcgen génère automatiquement les convertisseurs (marshalling/unmarshalling), les souches client et serveur, ainsi qu’un Makefile. Un service lookup permet la publication et la découverte des services RPC.

Représentation des données et XDR

Les machines clientes et serveurs peuvent avoir des représentations mémoire différentes (Little-endian vs Big-endian, alignement, tailles de données). XDR assure une représentation portable des données sur le réseau, utilisant un codage big endian sur des mots de 4 octets, avec une syntaxe proche du C. Il supporte les types fondamentaux (int, double), les énumérations, structures, unions, tableaux, chaînes de caractères et données opaques.

Exemple de types XDR

TypeIdentificateurTaille
Entier relatifint4 octets (IEEE)
Entier positifunsigned int4 octets (IEEE)
Enumérationenum {id = constante, …}4 octets (IEEE)
Booléenbool (False=0, True=1)4 octets (IEEE)
Entier longhyper8 octets
Entier positif longunsigned hyper8 octets
Réels simplesfloat4 octets (IEEE)
Réels doublesdouble8 octets (IEEE)
Réels longsquadruple16 octets (IEEE)
Rienvoid0 octet

Langage de description RPCL

Le langage RPCL, utilisé pour ONC RPC, définit les interfaces dans des fichiers .x avec la structure suivante :

[Définition des constantes]
[Définition des types]
program NOM_PROGRAMME {
  [version NOM_VERSION {
    [type_resultat nom_procedure(type_du_parametre) = numero_procedure;]
  } = numero_version]
} = numero_programme;

Exemple de définition de constantes et structures :

const identificateur = valeur;

struct nom_du_type {
  type attribut;
}

typedef type nom_du_type;

typedef int vecteur <1000>;
typedef string chaine <255>;

Exemple pratique : helloworld.x

typedef string chaine<255>;

program HELLO_WORLD_PROG {
  version HELLO_WORLD_VERSION_1 {
    void hello_world_null(void) = 0; /* test réseau, sorte de ping */
    chaine hello_world(chaine) = 1;
  } = 1;
} = 0x22222220;

Commande rpcgen génère les fichiers nécessaires au client et serveur.

Exemple de serveur helloworld_server.c

#include "helloworld.h"

void * hello_world_null_1_svc(void *argp, struct svc_req *rqstp) {
  static char * result;
  printf("Ping\n"); fflush(stdout);
  return (void *) &result;
}

chaine * hello_world_1_svc(chaine *argp, struct svc_req *rqstp) {
  static chaine result;
  static char tab[255];
  strcpy(result, "Hello ");
  strcat(result, *argp);
  printf("Result: %s\n", *argp);
  printf("Result: %s\n", result);
  return &result;
}

Exemple de client helloworld_client.c

#include "helloworld.h"

void hello_world_prog_1(char *host) {
  CLIENT *clnt;
  void *result_1;
  char *hello_world_null_1_arg;
  chaine *result_2;
  chaine hello_world_1_arg;

  clnt = clnt_create(host, HELLO_WORLD_PROG, HELLO_WORLD_VERSION_1, "udp");
  if (clnt == NULL) {
    clnt_pcreateerror(host);
    exit(1);
  }

  result_1 = hello_world_null_1((void*)&hello_world_null_1_arg, clnt);
  if (result_1 == (void *) NULL) {
    clnt_perror(clnt, "call failed");
    exit(1);
  }

  result_2 = hello_world_1(&host, clnt);
  if (result_2 == (chaine *) NULL) {
    clnt_perror(clnt, "call failed");
  } else {
    printf("%s\n", *result_2);
  }
  clnt_destroy(clnt);
}

int main(int argc, char *argv[]) {
  if (argc < 2) {
    printf("usage: %s server_host\n", argv[0]);
    exit(1);
  }
  hello_world_prog_1(argv[1]);
}

Compilation et exécution

  • Génération des sources : rpcgen helloworld.x
  • Compilation serveur : gcc -o server helloworld_server.c helloworld_xdr.c helloworld_svc.c -lrpcsvc -lnsl
  • Lancement serveur : ./server &
  • Vérification des services : rpcinfo -p
  • Compilation client : gcc -o client helloworld_client.c helloworld_xdr.c helloworld_clnt.c -lnsl
  • Lancement client : ./client

Publication et résolution des services RPC

Pour que le client trouve le serveur, il faut publier le service et permettre sa découverte. Le portmapper (RFC 1823) est un serveur RPC qui tourne sur chaque hôte et écoute sur les ports 111 TCP et UDP. Il gère :

  • Publication d’un programme (numéro de programme et version)
  • Suppression d’un programme
  • Découverte d’un programme (obtention du port associé)
  • Liste des programmes enregistrés
  • Appel direct d’une fonction distante

RPCbind remplace le portmapper, est indépendant du transport et permet l’enregistrement multi-transport.

Introduction à Java RMI

Java RMI est une évolution des RPC orientée objet, permettant l’invocation de méthodes à distance sur des objets situés dans des espaces d’adressage différents. L’interface Java sert d’IDL, et le passage d’arguments se fait par référence d’objet (contenant localisation et protocole d’accès).

Principes de la programmation orientée objet (OO)

  • Objet : collection de données et opérations.
  • Classe : description d’un ensemble d’objets similaires.
  • Sous-classe : spécialisation d’une classe.
  • Instance : objet concret d’une classe.
  • Méthode : corps d’une procédure.
  • Message : appel d’une méthode.

Avantages des objets en programmation répartie

  • Encapsulation : interface définie, état interne masqué.
  • Réutilisabilité via classes et instances.
  • Héritage : spécialisation et réutilisation de code.
  • Polymorphisme : objets interchangeables avec interface compatible.

Challenges du client/serveur orienté objet

  • Nommage et identification unique des objets distribués (références).
  • Sécurité d’accès et gestion du partage.
  • Durée de vie des objets (temporaire ou persistante).
  • Gestion des objets concurrents (passifs ou actifs).
  • Synchronisation intra-objet et inter-objets.
  • Migration et réplication d’objets.
  • Ramasse-miettes distribué.

Architecture et fonctionnement de Java RMI

Java RMI repose sur un schéma similaire au RPC classique. Le programmeur fournit :

  • Les interfaces Java des objets distants.
  • Les objets réalisant ces interfaces.
  • Le programme serveur instanciant ces objets.
  • Le programme client invoquant ces objets.

Le middleware Java fournit :

  • Un générateur de talons (stub) nommé rmic (automatique depuis JDK 5.0).
  • Un service de noms (rmiregistry).
  • Un ensemble de classes pour l’invocation à distance.
  • La possibilité d’exécuter du code généré ailleurs (chargement dynamique).

Composants clés

  • Stub (amorce client) : représentant local de l’objet distant, il sérialise les arguments, transmet l’appel, désérialise la réponse.
  • Skeleton (souche serveur) : reçoit l’appel, désassemble les arguments, appelle l’objet distant, assemble la réponse.
  • Couche transport : gère les connexions TCP/IP via JRMP (Java Remote Method Protocol).
  • Service de noms : permet la publication et la recherche d’objets distants via des URL de la forme rmi://host[:port]/service.

Mode opératoire côté serveur

  1. Création de l’objet serveur.
  2. Enregistrement auprès du service de noms local (méthode rebind).
  3. Création du skeleton, ouverture du port de communication.
  4. Publication de l’objet serveur et du port auprès du rmiregistry.

Mode opératoire côté client

  1. Le client interroge le service de noms (Naming.lookup) pour localiser l’objet serveur.
  2. Le service de noms récupère la référence (stub) auprès du rmiregistry.
  3. Le stub est installé localement et retourné au client.
  4. Le client invoque la méthode distante via le stub.

Exemple complet Java RMI

Interface distante Hello.java

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Hello extends Remote {
  String sayHello() throws RemoteException;
}

Implémentation de l’objet distant HelloObj.java

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class HelloObj extends UnicastRemoteObject implements Hello {
  private String msg;

  public HelloObj(String msg) throws RemoteException {
    super();
    this.msg = msg;
  }

  public String sayHello() throws RemoteException {
    return "Hello world: " + msg;
  }
}

Serveur HelloServeur.java

import java.rmi.Naming;

public class HelloServeur {
  public static void main(String[] args) {
    try {
      HelloObj obj = new HelloObj("HelloServeur");
      Naming.rebind("rmi://localhost:1099/HelloServeur", obj);
      System.out.println("HelloServer bound in registry");
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

Note : Le serveur de noms rmiregistry doit être lancé avant le serveur.

Client HelloClient.java

import java.rmi.Naming;

public class HelloClient {
  public static void main(String[] args) {
    try {
      Hello obj = (Hello) Naming.lookup("rmi://localhost/HelloServeur");
      String msg = obj.sayHello();
      System.out.println(msg);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

Compilation et déploiement

  • Compiler les sources : javac Hello.java HelloObj.java HelloServeur.java HelloClient.java
  • Lancer le serveur de noms : rmiregistry & (Unix) ou start rmiregistry (Windows)
  • Lancer le serveur : java HelloServeur &
  • Lancer le client : java HelloClient

Gestion des exceptions

Toutes les méthodes distantes doivent déclarer qu’elles peuvent lever une RemoteException, qui se produit en cas de problème réseau, d’objet inaccessible ou d’erreur de sérialisation.

Classes principales Java RMI

  • java.rmi.Remote : interface de base des objets distants.
  • java.rmi.server.UnicastRemoteObject : classe de base pour les objets distants mono-serveur.
  • java.rmi.Naming : classe utilitaire pour l’enregistrement et la recherche d’objets distants.
  • java.rmi.registry.LocateRegistry : gestion du registre RMI.

Exportation d’un objet

Un objet distant est exporté via la classe UnicastRemoteObject, qui crée un stub contenant une référence (adresse IP, port) permettant la communication via sockets. L’objet reste actif tant que la JVM est en fonctionnement.

Activation d’objets persistants

La classe Activatable permet de créer des objets activables à la demande, démarrés par un démon RMI rmid. Ces objets sont persistants, indépendants de la durée de vie d’un processus serveur.

Glossaire des termes clés

  • RPC (Remote Procedure Call) : mécanisme d’appel de procédures à distance.
  • RMI (Remote Method Invocation) : extension orientée objet de RPC en Java.
  • IDL (Interface Definition Language) : langage de description des interfaces (Java sert d’IDL en RMI).
  • Stub : représentation locale d’un objet distant côté client, gérant la communication.
  • Skeleton (souche) : côté serveur, reçoit les appels et les transmet à l’objet distant.
  • XDR (eXchange Data Representation) : format portable pour l’échange de données en RPC.
  • Portmapper / RPCbind : services de publication et résolution des programmes RPC.
  • rmiregistry : registre de noms pour les objets distants en Java RMI.
  • UnicastRemoteObject : classe Java pour objets distants mono-serveur.
  • Activatable : classe Java pour objets distants activables à la demande.
  • RemoteException : exception levée lors d’erreurs de communication distante.

Points clés à retenir

  • RPC permet d’appeler des procédures sur des machines distantes en masquant la complexité réseau.
  • Java RMI étend RPC en orientant la programmation objet, avec passage d’arguments par référence.
  • Les stubs et souches assurent la sérialisation et la communication entre client et serveur.
  • Le service de noms (rmiregistry) est essentiel pour la publication et la découverte des objets distants.
  • La gestion des exceptions et la sécurité sont intégrées dans Java RMI.
  • La classe UnicastRemoteObject facilite l’exportation d’objets distants mono-serveur.
  • Les objets activables permettent la persistance et l’activation à la demande.
  • Le développement Java RMI suit un cycle : définition d’interface, implémentation serveur, génération des stubs, déploiement et exécution.

Partager

Commentaires

Aucun commentaire pour le moment. Posez la première question.

Les commentaires sont relus avant publication. Votre e-mail n'est jamais affiché.

← Toutes les révisions