Esercizio1: Variante dell'esempio client server "Hello World"del tutorial
della Sun.
Costruiremo un client ed un server che in prima istanza verranno fatti
girare sulla stessa macchina. Comunque, nonostante Server e Client
risiedano sulla stessa macchina il codice necessario al client (in particolar
modo la classe stub) verra' caricato dinamicamente (e non copiato
brutalemente da una directory ad un'altra) durante l'esecuzione del client
in maniera del tutto trasparente.
Nel seguito supponiamo che l'utente abbia username "massimo" e che quindi
il path della sua home directory sia "/home/massimo".
Ciascuno di voi sostituisca "massimo" con il proprio username. Seguiamo
i seguenti punti passo per passo.
1) Creiamo una directory javarmi dove faremo tutti i nostri esperimenti
con Java Rmi. All'interno di tale directory creiamo due directory server
e client
al fine di tenere separati i codici del client e del
server.
2) La directory server avra' al suo interno una directory common
dove andremo a mettere tutte le classi prodotte dalla compilazione del
sorgente del server e che
desideriamo rendere disponibili anche al client. In
particolar modo ci riferiamo alle classi stub (prodotte con rmic) che
verrano caricate a tempo di esecuzione
dal client.
3) All'interno delle due directory server e client avremo il medesimo file
chiamato policy il cui codice e' il seguente:
grant {
// Allow
everything for now
permission
java.security.AllPermission;
};
4) All'interno di entrambe le directories server e client creiamo
una directory hello che fungera' da package (l'uso dei packages non
e' comunque essenziale).
5) In /home/massimo/javarmi/server/hello mettiamo i seguenti due
codici sorgenti java:
5.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;
}
5.2) HelloImpl.java (vale a dire la classe remota)
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: I am your remote object!";
}
public static void main(String
args[]) {
// Create
and install a security manager
if (System.getSecurityManager()
== null) {
System.setSecurityManager(new RMISecurityManager());
}
try
{
Hello obj = new HelloImpl();
// Bind this object instance to the name "HelloServer"
Naming.rebind("//:2345/HelloServer", obj);
// notate che non
abbiamo scritto il nome dell'host ma solo la porta. Cio' vuol dire che
// per default si
prende il localhost.
System.out.println("HelloServer bound in registry");
} catch
(Exception e) {
System.out.println("HelloImpl err: " + e.getMessage());
e.printStackTrace();
}
}
}
6) Compiliamo i sorgenti del server. Apriamo una finestra che utilizzeremo
solo per il server (giusto per simulare due macchine
diverse; ovviamente potremmo usare sempre la stessa
finestra) e digitiamo i seguente comandi:
cd /home/massimo/javarmi/server/hello (andiamo
nella directory dove risiedono i sorgenti del server)
javac -d /home/massimo/javarmi/server/common
Hello.java HelloImpl.java
l'opzione -d ha come risultato quello di creare nell'area
comune /home/massimo/javarmi/server/common una directory 'hello' al
cui interno vengono
messe le classi Hello.class e HelloImpl.class
prodotte dalla compilazione. Verificatelo. Notate che in /home/massimo/javarmi/server/hello
non e' stata
aggiunta alcuna classe.
7) Generazione di stub e skeleton.
cd /home/massimo/javarmi/server/
rmic -d /home/massimo/javarmi/server/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 anch'esse messe nella directory hello dell'area
comune /home/massimo/javarmi/server/common. Verificatelo.
8) Lanciamo il registro RMI sulla porta utilizzata dall'oggetto
remoto (in questo esempio stiamo usando la 2345, ma potrebbe essere benissimo
un'altra).
Importantissimo: Prima di lanciare il registry RMI dobbiamo
resettare la variabile CLASSPATH da dove vengono caricate le classi in fase
di
compilazione ed esecuzione. Infatti se tale variabile
CLASSPATH contenesse un path fino alla classe HelloImpl_Stub.class
allora il meccanismo
java.rmi.server.codebase (che vedremo nel punto seguente)
non funzionerebbe correttamente impedendo al Client di caricare dianamicamente
la classe HelloImpl_Stub.class. Digitiamo allora
i seguenti comandi:
unset CLASSPATH (oppure
unsetenv CLASSPATH - per resettare la variabile CLASSPATH).
rmiregistry 2345 &
(per lanciare il registro alla porta 2345) .
Se avessi fatto semplicemente rmiregistry
& il registro RMI sarebbe stato lanciato sulla porta di default 1099.
9) Finalmente lanciamo il server dalla directory server. Digitiamo
i seguenti comandi.
cd /home/massimo/javarmi/server
java -classpath /home/massimo/javarmi/server/common/
-Djava.rmi.server.codebase=file:///home/massimo/javarmi/server/common/
-Djava.rmi.server.hostname=localhost
-Djava.security.policy=/home/massimo/javarmi/server/policy
hello.HelloImpl
Discutiamo le vari clausole.
(i) La -classpath dice all'interprete dove trovare
i file *.class necessari per l'esecuzione. Ricordiamo che abbiamo messo
tali files in una aria comune. Tale clausola
ha anche l'effetto si settare
la variabile CLASSPATH con tale valore. Nota che l'interprete ha bisogno di
tutte le classi per andare ine secuzione, anche gli stub.
(ii) La -Djava.rmi.server.codebase=file:///home/massimo/javarmi/server/common/
definisce il protocollo attraverso cui il client puo' accedere
alle informazioni
che necessita a run-time
(cioe' la classe HelloImpl_Stub.class). Il protocollo e' "file://
"che e' utilizzabile solo se client e server hanno accesso allo stesso file
system (come nel caso
del nostro esempio in cui client e server girano sulla stessa macchina).
Se client e server girassero su macchine differenti avremmo potuto usare
un protoccollo del
"http://" utilizzando un webserver.
(iii) -Djava.rmi.server.hostname=localhost
dice semplicemente che il server lo stiamo lanciando sul localhost
(iv) -Djava.security.policy=/home/massimo/javarmi/server/policy
e' clausola di policy che per adesso non spieghiamo.
Se tutto va correttamente sul vostro schermo apparira' la scritta:
HelloServer bound in registry
La quale vi comunica che l'oggetto remoto,che abbiamo chiamato HelloServer,
e' stato introdotto nel registro RMI. Piu' precisamente che
la stringa Helloserver e' stata associata ad una referenza all'oggetto
remoto.
10) Passiamo al codice del Cllient. Apriamo allora una nuova
finestra (giusto per simulare che adesso siamo sulla macchina client). Nella
directory
/home/massimo/javarmi/client/hello mettiamo
i due seguenti codici Hello.java (l'interfaccia 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) l 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.
11) Compiliamo il codice del Client.
cd /home/massimo/javarmi/client/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.
12) Lanciamo il programma del Client.
Nota che poiche' stiamo simulando una programmazione
distribuita ma siamo sulla stessa macchina la variabile CLASSPATH e sporca
e precisamente
contiene l'informazione su dove sta l'area comune.
Ma nella vera programmazione distribuita il client non ha la piu' pallida
idea di dove
si trovi tale area condivisa. Percio' se vogliamo
veramente simulare una programmazione distribuita dobbiamo fare il comando
unset CLASSPATH (oppure unsetenv CLASSPATH).
Nota che cio' e' necessario solo perche' stiamo
usando la stessa macchina e lo stesso utente per client e server e quindi
la variabile CLASSPATH e ' unica.
In una vera programmazione distributa il client
non avrebbe nessun bisogno di fare cio.
cd /home/massimo/javarmi/client/
java -Djava.security.policy=/home/massimo/javarmi/client/policy
hello.HelloClient
localhost:2345
Percio' andiamo nella directory client e lanciamo
il codice del client.
- Nota che scriviamo hello.HelloClient perche' siamo nella
directory client e non dentro hello.
- Nota anche che localhost rappresenta la macchina su cui il
server gira (in questo caso il server gira sulla stessa macchina del client)
- 2345 e' la porta su cui e' in attesa di richieste il
registro RMI nella macchina del server.
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 localhost
;
- La porta su cui gira il registro RMI presso la macchina del Server,
nel nostro caso 2345.
Niente altro!!!! Per esempio 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 a HelloClient. E' invece il protocollo RMI
che si prende cura di fornire tale
informazione al client.
13) ESPERIMENTI:
i) Provate a togliere la classe stub dalla directory
'common' (mettetela da qualche altra parte) e rilanciate il client.
Noterete che il client non funziona
perche' cerca la classe stub e non la trova.
ibis) Provate adesso a rimuovere la classe stub da common
e metterla in /home/massimo/javarmi/client/hello. Lanciate nuovamente il
Client e noterete che tutto
funziona magnificamente. Solamente che facendo cosi' avete 'barato' perchel'
il codice del client non carica
la classe stub dal web
server della macchina server attraverso il protocollo rmi ma utilizza lo
stub che trova localemente. E questa non
e' programmazione distribuita!
ii) Provate a lanciare il cliente senza indicare
la porta.
iiii) Provate a lanciare il Client senza la
policy
iv) Dopo aver uciso il processo java che
vi ricordo si fa col comando
kill -9 numero-processo.
Dove numero processo lo ottenete
facendo ps -u username.
provate a rilanciare il server
senza la clausola -classpath. Oppure togliendo i file stub dall'area comune.
Noterete che anche il server ha bisogno
degli stub.