Esercizi da svolgere |
RMI è una libreria di classi che fa parte integrante delle API Java e che permette a
dei programmi Java di chiamare certi metodi su un server remoto. RMI permette di
eseguire parti di un singolo programma in una JVM sulla macchina locale e altre parti
dello stesso programma in una JVM su una macchina remota. Un tale programma è anche chiamato distributed object application.
L'architettura RMI definisce come si comportano gli oggetti, come e quando le
eccezioni possono essere lanciate, come viene gestita la memoria, come i parametri
sono passati a, e restituiti da, i metodi remoti.
Per esempio, nel caso in cui si vogliano passare oggetti che non implementino un'interfaccia remota, è necessario che questi vengano passati per valore; cioé vengono passate delle copie complete, usando la serializzazione.
Esattamente come per le applet, tutte le attività che un oggetto remoto può effettuare
sono controllate da un oggetto SecurityManager.
Nell'architettura RMI, la definizione di un comportamento e l'implementazione di quel comportamento sono concetti separati.
Questo è l'ideale in un sistema distribuito dove ai clienti interessa la definizione di un servizio mentre i server
pensano a fornire il servizio.
In RMI la definizione di un servizio remoto è espressa usando un'interfaccia Java, mentre l'implementazione del servizio remoto è codificato in una classe.
Quindi è fondamentale ricordare che:
Vediamo i passi da seguire per creare una (stupida) applicazione distribuita nella quale un server RMI prende un oggetto Swappable
. Gli oggetti Swappable
contiengono due valori e il metodo swap
. Il server provvede a fare lo swap dei due valori invocando il metodo swap
.
Swapper
definisce la parte accessibile da remoto estendendo l'interfaccia java.rmi.Remote
:
|
|
Il metodo doSwap
, essendo membro di un'interfaccia remota è anche'esso un metodo remoto, e come tale deve essere in grado di lanciare java.rmi.RemoteException
, (errori di comunicazione, protocolli, ...). Questa eccezione è controllata a compile-time.
Swappable
dal client al server, e poi di ritorno dal server a client. Questo è possibile solo se l'oggetto è serializzabile:
|
|
L'interfaccia non è remota, ma estende solo l'interfaccia Serializable
. Il metodo swap
restituisce un Object
per restare generale.
classe
implementazione dell'interfaccia remota, la quale deve almeno:
Il server deve anche creare e pubblicare gli oggetti remoti. Tale procedura di inizializzazione può essere incapsulata in un metodo main
nella stessa classe implementazione dell'oggetto remoto. Nell'inizializzazione occorre:
SecurityManager
(rosso)
|
|
UnicastRemoteObject
fornisce le implementazioni dei metodi di java.lang.Object
(equals
, hashCode
, toString
). Un'implementazione che non estenda UnicastRemoteObject
deve fornire le adeguate implementazioni per i metodi di java.lang.Object
nonchè esportare esplicitamente tale nuovo oggetto remoto; ciò avviene invocando il metodo UnicastRemoteObject.exportObject
il quale comunica al runtime RMI dell'esistenza di tale nuovo oggetto remoto. Fatto questo l'oggetto potrà accettare chiamate in ingresso.
Tutti i programmi RMI che hanno bisogno di scaricare delle classi per oggetti ricevuti come parametri, valori di ritorno o eccezioni nell'invocazione di metodi remoti, devono installare un security manager; altrimenti RMI non scaricherà nessuna classe. RMISecurityManager
applica una politica di sicurezza simile a quella per le applet. Per ottenere permessi più ampi è necessario definire una propria classe SecurityManager
o usare il policy file .java.policy
.
Dopo aver reso disponibile ai client l'oggetto remoto il main
esce. Fintanto che esiste un riferimento all'oggetto Server
in un'altra virtual machine, locale o remota, l'oggetto Server
non verrà interrotto ne garbage-collected. Siccome l'oggetto fa il binding di una referenza a se stesso nel registry, è raggiungibile da un cliente remoto, il registry stesso! È il registry RMI che provvede a mantenere attivo il processo Server
.
Swappable
e lo invia al server per l'operazione do swap.java Client host element1 element2
|
|
Anche in questo caso è necessario installare un security manager altrimenti RMI non scaricherebbe nemmeno lo stub del server. Lo stub è un client-side proxy che implementa il set completo di interfacce remote che l'oggetto remoto implementa.
La cosa più interessante di questo esempio è che l'oggetto Swapper
non necessita della definizione della classe Swappable
fino quando un oggetto Complex
non è passato come argomento al metodo doSwap
.
> javac interfacce/*.java > jar cvf interfacce.jar interfacce/*.class
Avremo cura di porre le classi delle interfacce in ~/public_html/classes
. In questo modo saranno accessibili da web all'indirizzo http://www.cli.di.unipi.it/~TUA_LOGIN/classes/, oppure regolarmente come /home/p/pippo/public_html/classes/
> setenv CLASSPATH /home/p/pippo/es8:/home/p/pippo/public_html/classes/interfacce.jar > cd /home/p/pippo/es8 > javac server/Server.java > rmic -d . server.Server > mkdir /home/p/pippo/public_html/classes/server > cp server/Server.class /home/p/pippo/public_html/classes/server > cp Server_*.class /home/p/pippo/public_html/classes > cd /home/p/pippo/public_html/classes > jar xvf interfacce.jar
Adesso il server è pronto a essere eseguito. Andiamo a comipalare il client.
> cd /home/p/pippo/es8 > javac client/Client.java > javac -d /home/p/pippo/public_html/classes client.Complex.java
rmic
crea lo stub e lo skeleton per il Server
.
La "magia" RMI sarebbe ancora piu evidente se il client e il server venissero compilati da utenti differenti; infatti durante l'esecuzione sia il client (Complex.class) che il server (gli stub) sarebbero costretti a spedire delle classi via rete verso il partner.
Se vogliamo che le classi siano accessibili attraverso un Web server, la politica di sicurezza nel file java.policy
sarà del tipo:
grant { permission java.net.SocketPermission "*:1024-65535", "connect,accept"; permission java.net.SocketPermission "*:80", "connect"; };
Questa politica permette al codice scaricato da qualunque codebase solo due cose:
Prima di lanciare il registry RMI è necessario che la variabile CLASSPATH
non contenga anche il path a una delle classi (compresi gli stub per le classi implementazione dell'oggetto remoto) che si vuole siano scaricate ai client dell'oggetto remoto.
> unsetenv CLASSPATH && rmiregistry &
interfaccia.jar
e le classi implementazione dell'oggetto remoto siano nel CLASSPATH
:
> setenv CLASSPATH /home/p/pippo/es8:/home/p/pippo/public_html/classes/interfacce.jar
Al momento di lanciare il Server dobbiamo specificare, usando la proprietà java.rmi.server.codebase
, dove saranno rese disponibili le classi del server che possono essere scaricate dai client.
La proprietà java.rmi.server.hostname
viene usata nel caso in cui le API di Java non dovessero essere in grado di risolvere il fully qualified host name.
Lanciamo il server:
> cd /home/p/pippo/es8 > java -Djava.rmi.server.codebase=http://www/~pippo/classes/ -Djava.rmi.server.hostname=www.cli.di.unipi.it -Djava.security.policy=java.policy server.Server
> setenv CLASSPATH /home/p/pippo/es8:/home/p/pippo/public_html/classes/interfaccia.jar
Da cui si deduce che Complex.class
e le interfacce vanno messe in ~/public_html/classes/
Una volta lanciati il registry e il server possiamo avviare il client:
> cd /home/p/pippo/es8 > java -Djava.rmi.server.codebase=http://www/~pippo/client/ -Djava.security.policy=java.policy client.Client olivia 3 5
La proprietà java.rmi.server.codebase
è la locazione dove il client espone le proprie classi (la classe Complex
).
I rimanenti argomenti sono il nome dell'host sul quale è stato lanciato il server e i due parametri per costruire l'oggetto Complex
.