Esercizio3: Server Remoti.
Come al solito lavoreremo dentro un package che questa volta chiameremo servers
e che potrete mettere dentro la vostra directory javarmi.
Esercizio 3.1:
- Il server implementa la seguente interfaccia remota RemoteDate.java
package servers;
import java.rmi.*;
import java.rmi.server.*;
import java.util.*;
public interface RemoteDate extends Remote{
public Date getDate() throws RemoteException;
}
ecco l'implementazione del server RemoteDateServer.java:
package servers;
import java.rmi.*;
import java.rmi.server.*;
import java.util.*;
import java.rmi.registry.*;
class RemoteDateServer extends UnicastRemoteObject implements RemoteDate {
int count = 1; // Questo intero conta le richieste remote del client.
RemoteDateServer() throws RemoteException{};
public Date getDate() {
System.out.println("Il Client ha chiesto l'ora " + count + " volte");
if (count++ == 4){
try{
System.out.println("Ora Basta!!!");
Registry stub_reg = LocateRegistry.getRegistry(2345);
System.out.println("Ho ottenuto una referenza (cioe' uno stub) del Registro RMI (non del server!!)");
stub_reg.unbind("DateServer");
System.out.println("Ho usato la referenza per fare l'unbind del server DateServer dal Registro RMI");
System.out.println("Caro Client comprati un orologio!!!");
}
catch (Exception e){e.printStackTrace(getLog());
}
};
return new Date();
}
public static void main(String[] args){
try{
RemoteDateServer server = new RemoteDateServer();
System.out.println("Ho esportato il seguente server remoto su una porta scelta da RMI: \n" + server);
Registry reg = LocateRegistry.createRegistry(2345); // creo il registro RMI
System.out.println("Ho lanciato il seguente registro RMI alla porta 2345: \n" + reg);
reg.rebind("DateServer",server); // Faccio la bind
System.out.println("Il server remoto e' adesso registrato nel registro RMI. ");
Registry stub_reg = LocateRegistry.getRegistry(2345); //ottengo uno stub al registro.
System.out.println("Quello che segue e' uno stub al registro RMI sulla porta 2345: \n" + stub_reg);
if (reg.equals(stub_reg)){
System.out.println("Il registro RMI ed il suo stub sono considerati uguali in una semantica remota!!!");
}
}
catch (Exception e){e.printStackTrace();
}
}
}
Tale server consente ai suoi client (possono essere piu' di uno) di
chiedergli l'ora per 4 volte.
Ovvero di chiedere l'ora presso la macchina server che non e'
necessariamente la stessa
della macchina client.
Alla quarta volta, il server si indispettisce e fa l'unbind del server
dal registro RMI per non
consentire ai client di fare ulteriori lookup per ottenere la referenza
remota al server e chiedere
l'ora per la quinta volta. Notate che il registro RMI viene
creato da programma attraverso
l'interfaccia Registry. Inoltre notate come la semantica remota
consideri uguale la referenza
locale al registro RMI ed un suo stub, sebbene, come potete vedere
dalle stampe,
non siano esattamente uguali.
Compilate i sorgenti, avendo sempre cura di mettere le classi stub
nell'area condivisa, separatamente dal codice
del server (poiche' non c'e' nessun motivo per cui il client debba
poter vedere il bytecode del server, anzi....).
IMPORTANTE: Notate che per
lanciare il server non e' necessaria la clausola di policy. Questo
perche'
nel server non abbiamo installato il SecurityManager. L'installazione
del SecurityManager e' necessaria
solamente nel caso in cui vi sia caricamento di codice a
run-time. Cosa che, in questo caso, accade solo
nel client (per la classe stub) ma non nel server.
NOTA: In un secondo momento
potete pensare di usare il metodo getClientHost() per ottenere
l'IP del client che in quel momento sta chiedendo l'ora. Guardate la
documentazione API relativa.
PASSIAMO AL CLIENT:
- Il client avra' anche lui la medesima interfaccia remota, il file di
policy (quello abituale),
ed il codice RemoteDateClient.java:
package servers;
import java.rmi.*;
import java.rmi.server.*;
import java.util.*;
import java.rmi.registry.*;
class RemoteDateClient{
static RemoteDate server = null;
public static void main(String[] args){
// Queste due linee di codice servono per
caricare il SecurityManager
if (System.getSecurityManager() == null)
System.setSecurityManager(new RMISecurityManager());
try{
while (true){
Registry reg =
LocateRegistry.getRegistry(args[0],Integer.parseInt(args[1]));
server =
(RemoteDate)reg.lookup("DateServer");
System.out.println("Data e ora della macchina locale: "+new Date());
System.out.println("Data e ora del server remoto in "+args[0]+" e': "+
server.getDate());
int c =
System.in.read();
}
}
catch (Exception e){ e.printStackTrace();
}
}
}
Il client ottiene una referenza remota al server attraverso
l'interfaccia Registry. Si presuppone che
lanciate il client passandogli due argomenti: l'host remoto su cui gira
il registro RMI e la sua porta
(in questo caso 2345). Il client si occupa di stampare l'ora
locale e l'ora remota (cioe' presso l'host
remoto). Quest'ultima l'ottiene invocando il metodo remoto
getDate().
Compilate il codice e lanciatelo con
la clausola per la policy. Osservate attentamente quello che
accade sullo schermo del client e del server. Lo studente e' invitato a
capire il funzionamento dei
sorgenti. In pratica il client chiede l'ora al server remoto per
4 volte, ma alla quinta, poiche' il server
ha fatto l'unbind, la sua lookup lancera' un'eccezione.
Esercizio 3.2.
Fare il medesimo esercizio in cui RemoteDateClient.java e' rimpiazzato
da
RemoteDateClient2.java (ovviamente ricompilate, ect...)
package servers;
import java.rmi.*;
import java.rmi.server.*;
import java.util.*;
import java.rmi.registry.*;
class RemoteDateClient2{
static RemoteDate server = null;
public static void main(String[] args){
if (System.getSecurityManager() == null)
System.setSecurityManager(new RMISecurityManager());
try {
while (true){
Registry reg =
LocateRegistry.getRegistry(args[0],Integer.parseInt(args[1]));
String[] list =
reg.list(); // Nota che la list non prende argomenti poiche' il
registro e' gia' noto.
if (list.length == 0){
System.out.println("Ed io ti frego ugualmente perche' mi sono tenuto la
referenza all'oggetto remoto!!!!");
System.out.println("Data ed ora della macchina locale: "+new Date());
System.out.println("Data ed ora del server remoto in "+args[0]+":
"+server.getDate());
int c =
System.in.read();
}
else{
server =
(RemoteDate)reg.lookup("DateServer");
System.out.println("Data e ora della macchina locale: "+new Date());
System.out.println("Data e ora del server remoto in " +args[0]+":
"+server.getDate());
int c =
System.in.read();
};
}
}
catch (Exception e){
e.printStackTrace();
}
}
}
In questo caso il client riesce ad aggirare la unbind utilizzando
direttamente la referenza remota
al server conservata in una variabile locale. Percio', sebbene
l'host del server faccia una unbind
deregistrando il server remoto dal registro RMI, il server resta
comunque in esecuzione ed esportato
sulla sua porta. Quindi e' accedibile da parte di tutti coloro che
hanno mantenuto una sua referenza remota.
Per lo meno fintantoche' il processo che ha lanciato il server e' in
esecuzione
o il server non viene deesportato. Ma se il server viene
deesportato....
Esercizio 3.3.
Fare il medesimo esercizio in cui RemoteDateClient.java viene
rimpiazzzato da RemoteDateClient2.java
(ovviamente ricompilate, ect...) e RemoteDateServer.java e' rimpiazzato
da RemoteDateServer2.java
(ovviamente ricompilate, ect...).
package servers;
import java.rmi.*;
import java.rmi.server.*;
import java.util.*;
import java.rmi.registry.*;
class RemoteDateServer2 extends RemoteServer implements RemoteDate{
//Nota che stiamo estendendo RemoteServer
int count = 1;
RemoteDateServer2() throws RemoteException {UnicastRemoteObject.exportObject(this);}
//Poiche' non estendiamo UnicastRemoteObject l'esportazione deve essere fatta manualmente.
public Date getDate(){
try {
System.out.println("Il Client " + getClientHost() + " ha chiesto l'ora " + count + " volte");
if (count++ == 4){
System.out.println("Ora Basta!!!");
System.out.println("Sto facendo l'unbind dell'oggetto remoto dal Registro RMI");
Registry stub_reg = LocateRegistry.getRegistry(2345);
stub_reg.unbind("DateServer");
System.out.println("Ho fatto l'unbind dell'oggetto remoto dal Registro RMI");
System.out.println("Caro Client comprati un orologio!");
System.out.println("Questa e' la lista dei log sul server: " + getLog());
System.out.println("Ed adesso de-esporto anche il server remoto!!!");
UnicastRemoteObject.unexportObject(this,true);
}
}
catch (Exception e){
e.printStackTrace(getLog());
}
return new Date();
}
public static void main(String[] args){
try {
RemoteDateServer2 server = new RemoteDateServer2();
System.out.println("Ho esportato il seguente server remoto su una porta scelta da RMI: \n" + server);
Registry reg = LocateRegistry.createRegistry(2345); // creo il registro RMI
System.out.println("Ho lanciato il seguente registro RMI alla porta 2345: \n" + reg);
reg.rebind("DateServer",server); // Faccio la bind
System.out.println("Il server remoto e' adesso registrato nel registro RMI. ");
}
catch (Exception e){
e.printStackTrace();
}
}
}
In questo caso, il server e' piu' "dispettoso" dopo aver esaudito le
richieste del client per 4 volte, non solo fa l'unbind
ma anche de-esporta brutalmente
il server remoto che percio' non e' piu' accedibile da
parte del client nonostante questi
si in possesso di una referenza remota.
Esercizio 3.4
Infine vediamo l'uso dell'interfaccia Unreferenced.
Forniamo un nuovo server che implementa Unreferenced
e quindi il metodo unreferenced che viene lanciato dal sistema RMI
quando si rende conto che non vi sono
piu' referenze remote. Il nuovo server chiamatelo RemoteDateServer3.java:
package servers;
import java.rmi.*;
import java.rmi.server.*;
import java.util.*;
import java.rmi.registry.*;
class RemoteDateServer3 extends RemoteServer implements RemoteDate,
Unreferenced {
int count = 1;
RemoteDateServer3() throws RemoteException
{UnicastRemoteObject.exportObject(this);}
//Poiche' non estendiamo UnicastRemoteObject
l'esportazione deve essere fatta manualmente.
public Date getDate(){
try {
System.out.println("Il Client " +
getClientHost() + " ha chiesto l'ora " + count + " volte");
}
catch (Exception e){
e.printStackTrace(getLog());
}
count++;
return new Date();
}
public void unreferenced() {
System.out.println("Il Sistema RMI ha
lanciato il metodo unreferenced() di RemoteDateServer,");
System.out.println("Cio' vuol dire che
non vi sono piu' referenze remote attive al server remoto");
System.out.println("Eliminiamo anche
la referenza locali al server remoto nel registro RMI.");
try {
Registry stub_reg =
LocateRegistry.getRegistry(2345);
stub_reg.unbind("DateServer");
}
catch (Exception e){
e.printStackTrace();
}
System.gc();
System.out.println("Ho invocato il garbage
collector locale in modo da de-esportare automaticamente il server.");
}
public static void main(String[] args){
try {
RemoteDateServer3 server = new RemoteDateServer3();
System.out.println("Ho esportato il seguente server remoto su una porta
scelta da RMI: \n" + server);
Registry reg = LocateRegistry.createRegistry(2345); // creo il registro
RMI
System.out.println("Ho lanciato il seguente registro RMI alla porta
2345: \n" + reg);
reg.rebind("DateServer",server); // Faccio la bind
System.out.println("Il server remoto e' adesso registrato nel registro
RMI. ");
}
catch (Exception e){
e.printStackTrace();
}
}
}
Lanciate il server come fatto precedentemente. Dopodiche' lanciate una
delle due applicazioni client viste
prima. Fate 4-5 richieste e poi uccidete il processo del client
(Control C). A questo punto lasciate stare il server
senza toccare niente e vedrete che dopo 5-10 minuti il sistema DGB
segnalera' al sistema RMI che non
vi sono piu' referenze remote e quindi verra' automaticamente invocato
il metodo unreferenced. Notate
che nel corpo del metodo unreferenced rimuoviamo l'unica referenza
locale, mantenuta nel registro RMI.
Dopodiche' possiamo invocare il garbage collector locale per rimuovere
il server. Dopo qualche minuto
il server verra' rimosso ed automaticamente de-esportato, ed il
programma del server terminera'.