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'.