CERCA SITEMAP FEED RSS 1280
Ultimo aggiornamento: 30 Agosto 2009

Remote Method Invocation

I sistemi distribuiti richiedono che processi eseguiti su differenti host siano in grado di comunicare fra loro.
Per le comunicazioni più semplici Java offre il meccanismo delle socket che richiedono l’implementazione di un protocollo mediante il quale client e server si scambiano messaggi.
Un’alternativa è rappresentata dalle Remote Procedural Call (RPC): lo sviluppatore ha l’illusione di chiamare una procedura locale delegando al sistema il compito di trasformare la richiesta in un’invocazione di una procedura residente su un altro host.
Le Remote Procedural Call tuttavia non si prestano molto all’implementazione di applicazioni distribuite object-oriented nelle quali non vi è la sola invocazione di procedure remote ma la "condivisione" di veri e propri oggetti.
Sistemi di questo tipo richiedono quindi un meccanismo per l’invocazione di metodi remoti (Remote Method Invocation) in cui un riferimento locale (stub) provvede a gestire l’invocazione dei metodi di un oggetto remoto.

Le applicazioni RMI sono generalmente costituite da due parti:
  • la parte server crea gli oggetti remoti ed i riferimenti a questi e quindi attende l’invocazione dei metodi da parte di un client
  • la parte client recupera i riferimenti a questi oggetti e ne invoca i metodi
Le applicazioni RMI necessitano di:
  • poter recuperare oggetti remoti
  • comunicare con oggetti remoti
  • caricare classi per oggetti che sono passati come parametri o restituiti come valore
Per recuperare oggetti remoti esistono due approcci: registrarli (rmiregistry) oppure passare e ricevere i riferimenti a questi come parte di una normale operazione.
Remote Method Invocation
L’invocazione di metodi remoti è totalmente trasparente al programmatore: i livelli Remote Reference Layer e Transport Layer si occupano di fatto della gestione a basso livello della comunicazione:
  • Il Remote Reference Layer (RRL) ha il compito di instaurare la connessione fra il client e il server eseguendo operazioni di codifica e decodifica dei dati
  • Il Transport Layer esegue la connessione vera e propria tra le macchine utilizzando quindi i socket con il protocollo TCP/IP
Dal punto di vista del singolo oggetto remoto, il server è chi lo alloca e lo esporta mentre il client è chi richiede ne un riferimento.
In definitiva quindi con RMI tutte le parti di una applicazione distribuita possono agire sia come client che come server.

Oggetti remoti

In Java un oggetto remoto è un oggetto i cui metodi possono essere invocati da remoto ed è descritto da una o più interfacce: RMI di fatto consiste nell’invocazione dei metodi di un’interfaccia remota su un oggetto remoto.

Un’interfaccia remota deve soddisfare i seguenti requisiti:
  • estendere l’interfaccia java.rmi.Remote
  • dichiarare metodi che includano le eccezioni nella clausola throws e che definiscano gli oggetti remoti passati come parametri o restituiti come risultato mediante interfacce remote.
Un’interfaccia rappresenta quindi una descrizione del comportamento di un oggetto remoto e definisce i metodi che possono essere invocati da remoto.
public interface MiaInterfaccia extends java.rmi.Remote
{
   String getRisposta(String risposta) throws java.rmi.RemoteException;
}
E’ opportuno precisare che a differenza delle chiamate locali le chiamate remote possono anche fallire a causa di:
  • problemi di comunicazione
  • errori nel passaggio di parametri o nel recupero di valori
  • errori di protocollo
La classe java.rmi.RemoteException rappresenta appunto un’eccezione che si verifica quando l’invocazione di un metodo remoto fallisce.
L’implementazione del comportamento di un oggetto avviene estendendo la classe UnicastRemoteObject la quale può implementare più interfacce remote e può anche contenere metodi non definiti nelle interfacce interfacce stesse con la limitazione che tali metodi non potranno essere invocati da remoto.
public class OggettoRemoto extends java.rmi.server.UnicastRemoteObject implements MiaInterfaccia
{
   public String getRisposta(String risposta) throws java.rmi.RemoteException
   {
      return risposta;
   }
}
Gli argomenti passati ed i valori ritornati devono necessariamente essere degli oggetti serializzabili ovvero dei tipi primitivi o che comunque implementino l’interfaccia java.io.Serializable.
Quando parametri o valore di ritorno devono essere convertiti in oggetti nella JVM che li riceve occorre disporre di tutte le definizioni delle corrispondenti classi.
Per supportare il caricamento dinamico delle classi, RMI fornisce speciali sottoclassi di java.io.ObjectInputStream e java.io.ObjectOutputStream che sovrascrivono i metodi resolveClass e annotateClass così da fornire informazioni sulle classi rappresentative degli oggetti inviati.
In particolare per ogni classe il metodo annotateClass aggiunge allo stream il risultato dell’invocazione del metodo java.rmi.server.RMIClassLoader.getClassAnnotation il quale può essere null o una stringa rappresentativa di un URL al quale può essere recuperata la definizione della classe stessa.
Il metodo resolveClass invece restituisce il risultato dell’invocazione del metodo RMIClassLoader.loadClass con il nome della classe desiderata come unico parametro.

Stub e Skeleton

Uno Stub è una rappresentazione locale di un oggetto: il client invoca cioè i metodi dello Stub il cui compito sarà quello di invocare i metodi dell’oggetto remoto che rappresenta.

Quando viene invocato un metodo lo Stub questo provvede a compiere le seguenti operazioni:
  • inizia una connessione con la Java Virtual Machine che contiene l’oggetto remoto
  • trasmette i parametri alla JVM
  • aspetta il risultato dell’invocazione del metodo
  • legge il risultato o l’eccezione generata
  • restituisce il risultato a chi ha invocato il metodo
Ogni oggetto remoto ha sulla Java Virtual Machine un suo corrispondente Skeleton il cui scopo è quello di:
  • leggere i parametri di un’invocazione remota
  • invocare il metodo remoto dell’oggetto che rappresenta
  • restituire il risultato al chiamante
Tipicamente una procedura di comunicazione fra Stub e Skeleton può essere schematizzata nel seguente modo:
Comunicazione
Il diagramma mette in evidenza le varie fasi della comunicazione fra Stub e Skeleton quando viene invocato un metodo di un oggetto remoto.

Per semplicità non vengono illustrati il Remote Reference Layer ed il Transport Layer responsabili dell’invio e della codifica delle informazioni scambiate fra Stub e Skeleton.
Stub e Skeleton vengono generati dal compilatore RMI mediante il comando rmic:
rmic [-vcompat] oggettoremoto
che genera:
oggettoremoto_Stub.class e oggettoremoto_Skel.class

Registrazione di un oggetto

Per usufruire di un servizio il client deve essere in grado di localizzare il server che fornisce il servizio stesso.

Esistono tre approcci al problema:
  • Il client conosce l’indirizzo del server
  • L’utente dice all’applicazione client dov’è il server
  • Un servizio standard (naming service) noto al client è in grado di fornire informazioni relative alla locazione di determinati servizi.
Java RMI utilizza un naming service (RMI Registry) che consente al client di poter ottenere i riferimenti agli oggetti remoti: ogni volta che un oggetto viene registrato (viene cioè creata nellRMI Registry un’associazione, bind, fra un nome URL-formatted e un oggetto) i client possono farne richiesta attraverso il nome ed invocarne i metodi.
Mentre è possibile creare associazioni solo sull’ RMIRegistry locale la ricerca degli oggetti può avvenire su qualunque host.
L’operazione di registrazione di un servizio può generare degli errori:
  • AlreadyBoundException se il nome logico è gia utilizzato
  • MalformedURLException per errori nella sintassi dell’URL dell’oggetto remoto
  • RemoteException negli altri casi
Esempio di binding:
...

OggettoRemoto oggettoremoto = new OggettoRemoto();
Naming.rebind("//localhost/OggettoRemoto ",oggettoremoto);

...
Una volta che il Server ha registrato l’oggetto, il Client può recuperare un riferimento a questo (lo stub) mediante il metodo Naming.lookup(String name) dove name è nella forma //host:port/nomeoggettoremoto
MiaInterfaccia oggetto= (MiaInterfaccia)Naming.lookup("//localhost/OggettoRemoto");
e quindi richiamare i metodi dell’oggetto:
String risposta = oggetto.getRisposta("risposta");
Naturalmente la classe Naming offre anche metodi per:
  • eliminare la registrazione di un oggetto unbind(String name)
  • ottenere una lista degli oggetti registrati list(String name)
  • sovrascrivere una registrazione rebind(String name, Remote obj)

Sicurezza e Codebase

Per rendere maggiormente sicure le applicazioni RMI è possibile impedire che le classi scaricate dal Server ed eseguite sul Client effettuino operazioni per le quali non sono state preventivamente abilitate.
Per questa ragione una delle prime operazioni che Client e Server dovrebbero effettuare prima di invocare metodi remoti o registrare oggetti remoti è quella di installare sul sistema un Security Manager il quale preleva i permessi da un file policy specificato al momento del lancio delle applicazioni.
if(System.getSecurityManager() == null)
{
  System.setSecurityManager(new RMISecurityManager());
}
java -Djava.security.policy = echo.policy Server java -Djava.security.policy = echo.policy Client
Affinchè Client e Server possano disporre delle definizioni delle classi necessarie all’interazione è possibile specificare il codebase al quale reperirle.

Il codebase può essere:
  • una directory del filesystem locale (file://)
  • l’indirizzo di un server FTP (ftp://)
  • l’indirizzo di un server http (http://)
Naturalmente le classi vengono dapprima ricercate nel CLASSPATH locale e solo in caso di insuccesso vengono ricercate nel codebase.
java -Djava.rmi.server.codebase= "http://server_host_name/... MioClient"
java -Djava.rmi.server.codebase= "http://client_host_name/... MioServer"