martedì 19 ottobre 2010

RMI

La documentazione principale per conoscere le RMI è: Tutorial Oracle Java

I principali problemi nell'uso dei Socket per la comunicazione tra Client e Server sono legati a:
  1. Si deve stabilire una porta su cui il Server ascolta (e il Client in qualche modo deve conoscerla).
  2. Si deve stabilire un protocollo tra i due sistemi (e l'implementazione è lasciata totalmente al programmatore).
  3. Non esistono logiche di programmazione ad oggetti: si ragiona su invio di dati (primitivi o Stringhe); se si vuole implementare una programmazione ad oggetti (vedi uso di Design Pattern Command si deve codificare manualmente.
  4. Non esistono controlli a compile time; Client e Server sono lasciati a loro stessi nella gestione dei tipi.
RMI (Remote Method Invocation) nasce come strato superiore, basato su Socket, per fornire ai programmatori la possibilità di lavorare in modalità "ad oggetti" su sistemi remoti.
L'aspetto negativo principale di RMI è legato all'overhead implicito nel suo uso:
  • Per rendere trasparente la comunicazione bisogna passare attraverso classi generate da Java (i cosiddetti stubs, che rallentano le prestazioni.
Le classi e le interfacce utilizzate per le RMI sono:

Vediamo come si utilizzano realmente:

Sistema RMI dal punto di vista Server
Primo passo da effettuare è definire una Interfaccia che definisca cosa possiamo eseguire sul nostro oggetto Server:
public interface OurServerObject extends Remote {
    public boolean isInsideServer() throws RemoteException;
    public Date getDate() throws RemoteException;
}
Punto importante da ricordare è che le nostre interfacce devono essere Remote e che ogni metodo definito deve poter lanciare RemoteException; questa eccezione (di tipo checked) può avvenire se:

  • Server non attivo, non raggiungibile, o la comunicazione con il Client è terminata
  • Sono avvenuti errori durante il marshaling di parametri o valori di ritorno
Si crea quindi una classe (reale, non astratta) che verrà utilizzata nel Server, ed implementerà la nostra interfaccia:
public class OurServerObjectImpl extends UnicastRemoteObject implements OurServerObject {
    public OurServerObjectImpl() throws RemoteException {
    }

    public boolean isInsideServer() throws RemoteException {
        return true;
    }
    public Date getDate() throws RemoteException {
        return Calendar.getInstance().getTime();
    }
}
Importante il costruttore vuoto perché verrà utilizzato nella creazione dell'oggetto (e deve poter lanciare l'eccezione RemoteException); la nostra classe verrà utilizzata per creare l'oggetto remoto che i Client utilizzeranno;
Creiamo infine una istanza di Server che si occuperà di avviare il registro RMI e di registrare il nostro oggetto:
public class Server {
    public static void main(String [] args) throws RemoteException, MalformedURLException {
        java.rmi.registry.LocateRegistry.createRegistry(1099); //porta di default

        OurServerObjectImpl obj = new OurServerObjectImpl();
        
        //registra l'oggetto nel registro
        Naming.rebind("OurFirstObject", obj);
    }
}
La classe Server verrà lanciata da linea di comando, creerà il nostro oggetto, lo inserirà nel registro RMI e infine...rimarrà in attesa che il sistema liberi le risorse (quindi non terminerà correttamente!!!)
Sistema RMI dal punto di vista Client
Un client può essere un qualsiasi computer (remoto e non) che viene considerato in una JVM separata; i requisiti per poter utilizzare il nostro sistema remoto sono:

  1. Conoscere l'indirizzo della macchina (come IP o come nome host)
  2. STOP!
Come vedete, usare un oggetto remoto non richiede praticamente niente; vediamo con una classe reale come accedere agli oggetti del Server:
public class Client {
public static void main(String[] args) throws MalformedURLException, RemoteException, NotBoundException {
System.out.println("Ottengo un riferimento al server");

//ottengo il riferimento
OurServerObject obj = (OurServerObject) Naming.lookup("OurFirstObject");

System.out.println("Siamo in un server ? -> " + obj.isInsideServer());
System.out.println("Ecco la data del server: " + obj.getDate());
}
}
Altre informazioni importanti su RMI:
Il sistema RMI è basato su un Server Multi-Thread; ogni chiamata al Server restituirà l'istanza dell'oggetto posizionato in memoria; questo significa che tocca al programmatore occuparsi di gestire ogni aspetto per rendere il sistema thread-safe.
Il nostro oggetto estendeva UnicastRemoteObject; se abbiamo un oggetto remoto che non può essere modificato in tal senso, possiamo inserire nel costruttore del nostro oggetto una chiamata al metodo UnicastRemoteObject.exportObject(this) e ricordarci di implementare correttamente i metodi Object.hashcode(), Object.equals(Object o) e Object.toString().
Dalla Java 1.5 in poi non c'è bisogno di creare, usando rmic gli stub e gli skeleton; il sistema li gestisce in maniera automatica; purtroppo, per la certificazione, è necessario crearli con rmic.
Abbiamo utilizzato i metodi remoti senza renderci conto di un particolare importantissimo: i parametri per i metodi remoti e i valori di ritorno devono essere di tipo Serializable; questo porta a varie conseguenze che verranno trattate nel paragrafo relativo a quella interfaccia.
Gli oggetti che inviamo (e riceviamo) possono essere:
  1. tipi primitivi - in questo caso viene fatta una copia (pass-by-value)
  2. oggetti - se non sono Serializable verrà lanciato NotSerializableException; viene passato per valore (come i tipi primitivi)
  3. oggetti remoti - viene mandato lo stub (quindi per riferimento)
In generale gli oggetti che creiamo sul Server (e dentro gli oggetti remoti) possono essere:
  • Remote
  • UnicastRemoteObject
  • Serializable
Gli oggetti Remote sono oggetti che vengono considerati remoti; gli unici metodi che possono essere chiamati da un client sono i metodi definiti in un interfaccia Remote.
Gli oggetti UnicastRemoteObject sono tutti oggetti che vivono sul server; sono gli unici oggetti che rimangono sul server, e che restituiscono un riferimento quando ci viene restituita una istanza dell'oggetto.

Ecco anche il codice relativo all'esempio di RMI.
Ecco anche il codice relativo all'esempio di RMI usando un Factory Method.

Nessun commento:

Posta un commento