Esercizio3: Server Remoti.
Come al solito lavoreremo dentro un package che chiameremo servers che potrete mettere dentro
la vostra directory javarmi.
Esercizio 3.1:
- Il server carica l'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;
}
e 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!!!");
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!!!");
}
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.
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 ed 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.
PASSIAMO AL CLIENT:
- Il client carica possiedera' 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. In Lo studente e'
invitato a capire il funzionamento dei
sorgenti. In pratica il client chiede l'ora la 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 il
host del server faccia una unbind
deregistrando il server remoto dal registro RMI il server resta
comunque in esecuzione sulla sua
porta ed 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 esportato. Ma se il server viene
esportato....
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 il server remoto che percio' non e' piu'
accedibile da parte del client nonostante questi mantenga
una referenza remota.
Esercizio 3.4
Infine vediamo l'uso dell'interfaccia Unreferenced. Forniamo un nuovo
server che implementa Unreferenced
e quindi il metodo unreference che viene lanciato da 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.out.println("Invoco il garbage
collector locale in modo da de-esportare automaticamente il server.");
System.gc();
}
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'.