Esercizio1: Variante dell'esempio client server "Hello World"del
tutorial della Sun.
Costruiremo un semplicissimo server remoto HelloServer. Tale server ha un solo
metodo remoto
sayHello() la cui invocazione da parte
di un client ritorna una frase. In questo esercizio
viene data particolare enfasi al fatto che il client carica
dinamicamente la classe stub relativa
all'oggetto remoto (e non la copia brutalemente dal server) in maniera
del tutto trasparente.
La programmazione distribuita client/server prevede che il codice del
server e quella del client
vadano eseguite su due macchine diverse. In questo esercizio lavorerete
in coppia su due macchine
differenti: uno di voi si occupera del server e l'altro del client.
Seguiamo allora i seguenti passi:
1) Sia il client che il server devono creare nella rispettiva
home-directory una directory javarmi
dove terremo tutti i nostri esercizi in Java RMI.
All'interno di tale directory entrambi creino una directory
hello che
fungera'da package ed in cui mettere tutto il codice relativo
all'esempio dell'esercizio
corrente.
2) Poiche' la classe stub (prodotte con rmic) deve essere
caricata dinamicamente dal client, la
metteremo in un'area di memoria del server che possa
essere acceduta anche dal client.
Ad esempio la mettiamo nella directory public_html del server che puo'
essere acceduta dal
client attraverso un protocollo http:// oppure ftp://. Per
comodita' creiamo all'interno di
~/public_html una directory common, accertandoci di dare a tutti
i diritti di lettura ed
esecuzione della directory.
3) All'interno della directory del ~/javarmi/ del client e del server
inseriamo il file di policy:
grant {
//
Allow everything for now
permission java.security.AllPermission;
};
per fornire al client i permessi necessari per
avere l'accesso dinamico alle classi stub.
Il file di policy, in generale, serve per
deteminare i permessi delle applicazioni distribuite.
Per adesso, per semplicita', il file di
policy dato sopra fornisce tutti i diritti possibili.
In seguito, ovviamente, saremo molto accurati nel
definire il file di policy, includendo
solo la quantita' minima di permessi necessatri
all'applicazione per il suo funzionamento.
4) Nella directory ~/javarmi/hello/ del server mettiamo i due codici
sorgenti java:
4.1) Hello.java
(vale a dire l'interfaccia remota)
package hello;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Hello extends Remote {
String sayHello() throws RemoteException;
}
4.2) HelloImpl.java
(vale a dire l'implementazione del server
remoto)
package hello;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.RMISecurityManager;
import java.rmi.server.UnicastRemoteObject;
public class HelloImpl extends UnicastRemoteObject
implements Hello {
public HelloImpl() throws
RemoteException {
super();
}
public String sayHello() {
return "Hello World: questa frase proviene dall'invocazione
del metodo sayHello
del server remoto";
}
public static void
main(String args[]) {
//
Crea ed installa un security manager.
if
(System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
try {
Hello obj = new HelloImpl();
// Creo
un'istanza del server remoto
Naming.rebind("//:2378/HelloServer", obj);
// Il
registro RMI viene lanciata dal localhost (per motivi di sicurezza non
puo'
//essere
lanciato da nessun altro) alla porta 2378.
//Se il numero
di porta non fosse stato indicato, di default sarebbe
// stato
lanciato, per default, alla porta 1099.
// Nel
registro RMI il nome "HelloServer" viene associato allo stub
dell'istanza dell'oggetto remoto creato.
System.out.println("L'oggetto remoto HelloServer e' stato registrato
nel registro RMI");
} catch (Exception e) {
System.out.println("HelloImpl err: " + e.getMessage());
e.printStackTrace();
}
}
}
5) Compiliamo i sorgenti del server.
cd ~/javarmi/hello
(andiamo nella directory dove risiedono i sorgenti del server)
javac Hello.java HelloImpl.java (compiliamo
l'interfaccia remota e l'implementazione)
6) Generazione di stub e skeleton.
Generiamo adesso le classi stub e skeleton. I
files di stub e skeleton vengono creati attraverso il tool rmic:
cd ~/javarmi/ (NOTA che ci mettiamo in javarmi e non in
hello, per via dei packages)
rmic -d ~/public_html/common/
hello.HelloImpl
tale fase di compilazione da' luogo alla
produzione delle classi HelloImpl_Skel.class e
HelloImpl_Stub.class che, per via della
clausola -d, vengono messe nella directory
~/public_html/common/ all'interno di un
package hello creato da rmic stesso.
Verificate che cio sia veramente accaduto.
7) Lanciamo il registro RMI sulla macchina del server sulla
porta utilizzata dall'oggetto remoto
(in questo esempio stiamo usando la 2378, ma potrebbe
essere benissimo un'altra). Il comando
per lanciare il registro RMI (che e' esso stesso un
oggetto remoto) e':
rmiregistry 2378 &
per lanciare il registro alla porta 2378. Se la porta
viene omessa, il registro viene lanciato per
default alla porta 1099. Comunque, lanciare il registro
RMI e' un'operazione abbastanza
delicata che e se non effettuata correttamente e' fonte
di errori di non facile interpretazione.
Teniamo presente alcune cose.
7.1) IMPORTANTE:
Il registro RMI deve poter accedere all'interfaccia remota per
poter
inserire correttamente lo
stub al suo interno. Cio' vuol dire che quando viene lanciato
deve poter accedere alla
classe Hello.class, o direttamente oppure settando appropriatamente
la variabile CLASSPATH.
7.2) IMPORTANTE: In
caso di caricamento dinamico degli stub da parte dei clients (come
avviene
nella maggior parte dei casi)
il registro RMI non deve poter accedere alle classi stub generate con
rmic, altrimenti il
meccanismo di caricamento dinamico degli stub da parte del client non
funziona correttamente.
Di conseguenza un modo per far funzionare correttamente
il vostro server e' quello di posizionarsi
nella directory javarmi e digitare:
unnset CLASSPATH
(oppure unsetenv CLASSPATH - per resettare la variabile
CLASSPATH).
rmiregistry 2378 &
(per lanciare il registro RMI alla porta 2378) .
Notate che poiche' programmate con i packages, per
vedere la classe Hello.class dovete lanciare il registro in javarmi e
non
nel package hello. Ovviamente potete lanciare il
registro in altri luoghi settando opportunamente la variabile CLASSPATH
in
modo che si abbia accesso all'interfaccia remota e non
agli stub.
8) Finalmente lanciamo il server dalla directory javarmi.
Digitiamo i seguenti comandi.
cd ~javarmi/
java -classpath :~/public_html/common/
-Djava.rmi.server.codebase=file:///........./public_html/common/
-Djava.security.policy=/....../javarmi/policy
hello.HelloImpl
Discutiamo le vari clausole.
(i) La -classpath dice all'interprete dove
trovare i file *.class necessari per l'esecuzione. Con i due punti
indichiamo che oltre a
considerare la directory corrente (cioe' ~/javarmi/) dobbiamo aggiungere
la
directory common in cui si
trovano gli stub che devono essere acceduti dall'implementazione per
effettuare la bind (o rebind)
nel registro RMI.
(ii) La -Djava.rmi.server.codebase=file:///......./public_html//common/ definisce
il protocollo attraverso
cui il client puo'
caricare dinamicamente la classe HelloImpl_Stub.class. In questo
caso usiamo il
protocollo "file://
"che e' utilizzabile solo se client e server hanno accesso allo stesso
file
system (come nel vostro
caso, poiche' tutte le macchine del laboratorio condividono il medesimo
file
system). Al posto
di ..... dovete mettere il path del vostro file system per arrivare a /public_html/common/
.
Un'altra possibilita'
era quella di usare il protocollo "http://" utilizzando un webserver.
(iii) La
-Djava.security.policy=/......./javarmi/policy e' la clausola di
policy
attraverso cui si
specificano i permessi di cui necessita il codice per l'esecuzione.
Se tutto va correttamente, sul vostro schermo apparira' la
scritta:
"L'oggetto remoto HelloServer e' stato registrato nel registro RMI"
La quale vi comunica che l'oggetto remoto,che abbiamo chiamato
HelloServer, e' stato registrato nel registro RMI.
Piu' precisamente che la stringa HelloServer e' stata associata ad una
referenza remota all'oggetto.
9) Passiamo al codice del Client. Cioe' vediamo quello che
deve essere fatto presso la macchina client.
Nella directory ~/javarmi/hello mettiamo i due seguenti
codici Hello.java (l'interfaccia remota esattamente uguale
a quella del server) e il codice del Client HelloClient.java che
riportiamo sotto.
package hello;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.RMISecurityManager;
public class HelloClient {
public static void
main(String args[]) {
// "obj" is the identifier that we'll use to refer
// to the remote object that implements the "Hello"
// interface
Hello obj = null;
String str = "";
// Create and install a security manager
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
try {
String name = "//" + args[0] + "/HelloServer";
System.out.println("1) Il client sta facendo una lookup per ottenere
una referenza all'oggetto remoto all'indirizzo: " + name);
obj = (Hello)Naming.lookup(name);
System.out.println("2) La lookup ha avuto successo");
str = obj.sayHello();
System.out.println("3) Il Client ha invocato il metodo sayHello
dell'oggetto remoto con successo ");
System.out.println(str);
System.out.println("4) Il Client ha stampato il risultato
dell'invocazione del metodo remoto. Fine.");
} catch (Exception e) {
System.out.println("HelloClient exception: " +
e.getMessage());
e.printStackTrace();
}
}
}
}
Notate come siano stati aggiunti nel codice delle linee di stampa per
agevolare eventuale debug e farvi capire,
in caso di errore, dove il programma si e' arrestato.
10) Compiliamo il codice del Client.
cd ~/javarmi/hello/
javac Hello.java HelloClient.java
Nota che le classi Hello.class e
HelloClient.class vengono create localmente nella directory 'hello' del
client.
Infatti sono delle classi che il client
non deve condividere con nessuno. Similmente, nel server, abbiamo messo
le classi Hello.class ed HelloImpl.class
nel package hello e non in common perche' non devono essere conosciute
dal client.
11) Lanciamo il programma del Client.
Supponendo che il server sia stato lanciato
sulla macchina "delta05" di seguito riportiamo i comandi per
lanciare l'applicazione del client client:
cd ~/javarmi/
java
-Djava.security.policy=/home/massimo/javarmi/policy
hello.HelloClient
delta05:2378
Percio' andiamo nella directory client e
lanciamo il codice del client.
- Nota che scriviamo hello.HelloClient perche' siamo
nella directory javarmi e non dentro hello
- 2378 e' la porta su cui e' in attesa di richieste il
registro RMI nella macchina del server.
Se tutto va come previsto al client
1) Il client sta facendo una lookup per ottenere una referenza
all'oggetto remoto all'indirizzo: //...../HelloServer
2) La lookup ha avuto successo
3) Il Client ha invocato il metodo sayHello dell'oggetto remoto con
successo
Hello World: questa frase proviene dall'invocazione del metodo
sayHello del server remoto
4) Il Client ha stampato il risultato dell'invocazione del metodo
remoto. Fine.
IMPORTANTE: Le uniche
informazioni in possesso del client per invocare un metodo dell'oggetto
remoto sono:
- Il nome con cui l'oggetto e' registrato presso il registro
RMI del Server, vale a dire HelloServer;
- La macchina su cui gira il server, nel nostro caso delta05;
- La porta su cui gira il registro RMI presso la macchina del
Server, nel nostro caso 2378.
Niente altro!!!!
IMPORTANTE: Il client non ha alcuna idea di dove sia l'area
condivisa usata dal server per tenere
il codice da caricare dinamicamente (cioe' gli
stub) durante la sua esecuzione. Questa mobilita' di codice server-client
e' completamente trasparente al client. E' il
protocollo RMI che si prende cura di fornire tale informazione al
client.
IMPORTANTISSIMO:
Quando il Server non vi servira' piu' non dimenticate di uccidere il
processo
del registro RMI che gira in background.
12) ESERCIZI/ESPERIMENTI:
a) fermate il server ed uccidete il registro RMI. Provate allora a:
a1) Lanciare il registro RMI da una locazione dove non
vede l'interfaccia remota Hello.class e gli
stub. In tal caso quando provate a
lanciare il server, l'interporete vi dira' che il registro RMI
non riesce a trovare
hello.Hello.class.
a2) Uccidete server e registro RMI. Copiate la classe
stub che e' in ~/public_html/common/hello/
dentro ~/javarmi/hello. E lanciate
adesso il registro RMI dalla directory javarmi. Quando lancerete
il server non noterete alcun
problema. Ma quando lancerete il client, l'interprete vi segnala problemi
nell'accedere allo stub. Tali
problemi derivano dal fatto che quando avete lanciato il registro RMI
questi aveva accesso llo stub e lo
ha caricati prima di quando doveva. Rivedremo tale problema
piu' in dettaglio.
a3) Provate adesso a copiare la classe stub, dall'area
common del server, al package hello del client.
Lanciate nuovamente il client e
noterete che tutto funziona magnificamente. Solamente che facendo
cosi' avete 'barato'perche' il
client non carica dinamicamente dall'area condivisa common ma utilizza
lo stub che trova localemente. E
questa non e' programmazione distribuita!
b) Provate a lanciare il client senza indicare la porta del registro
RMI.
c) Provate a lanciare il Client senza la clausola di policy.
d) Provate a lanciare il Server senza la clausola di policy.
e) Provate a togliere dal Server il SecurityManager ed a
lanciarlo senza clausola di policy.
f) provate a lanciare il server con un codebase sbagliato, ovvero
che non sia in grado di rintracciare
l'area common col package hello in cui vi sono gli stub.
Un effetto simile lo si puo' ottenere
lasciando il codebase invariato e togliendo i diritti di
lettura nella directory common.