Java RMI Alcune premesse Interfaccia e Implementazione Modello OO • Interfaccia esprime una vista astratta di un ente computazionale, nascondendo – organizzazione interna – dettagli di funzionamento • Implementazione esprime – la rappresentazione dello stato interno – il codice di un ente computazionale In Java • Interfaccia – definita attraverso la parola chiave interface – contiene solo dichiarazioni di metodi NON implementazioni – uso di interfacce come “marcatori” Esempio: Serializable e Remote • Classe – definita attraverso la parola chiave class – realizza l’astrazione definita dall’interfaccia – può implementare una o più interfacce Reti di calcolatori, Java RMI - 1 Esempi interfaccia e implementazione public interface ICounter { public void incrementa(); public void reset(); public int getValue(); } public class Counter implements ICounter{ private int valore=0; //stato interno public public public public Counter(){ valore=0; } void incrementa(){ valore++; } void reset(){ valore=0; } int getValue(){ return valore; } } Riferimenti locali in Java Gli oggetti locali vengono riferiti attraverso l’uso di un riferimento locale Esempio ICounter c; è un riferimento ad un oggetto con interfaccia ICounter l’oggetto Counter (l’implementazione) viene poi creato dinamicamente c = new Counter(); Reti di calcolatori, Java RMI - 2 RMI RPC in JAVA Le RMI introducono la possibilità di richiedere le esecuzione di metodi remoti in JAVA Integrando il tutto con il paradigma OO Definizioni e generalità Insieme di politiche e meccanismi che permettono ad un’applicazione Java in esecuzione su una macchina di invocare i metodi di un'altra applicazione Java in esecuzione su una macchina remota Viene creato localmente solo il riferimento ad un oggetto remoto, che è invece effettivamente attivo su un nodo remoto Un programma cliente invoca i metodi attraverso questo riferimento locale Eterogeneità di ambienti come conseguenza del linguaggio Java ma un unico ambiente di lavoro Reti di calcolatori, Java RMI - 3 Architettura Registry RMI System Client Program Server Program Stubs Skeletons Remote Reference Layer Remote Reference Layer Transport Layer Stub: proxy locale su cui vengono fatte le invocazioni destinate all’oggetto remoto Skeleton: entità remota che riceve le invocazioni fatte sullo stub e le realizza effettuando le corrispondenti chiamate sul server Registry: servizio di nomi che consente al server di pubblicare un servizio e al client di recuperarne il proxy - Esempio di altro servizio di nomi: DNS Remote Reference Layer: - fornisce il supporto alle chiamate inoltrate dallo stub - definisce e supporta la semantica dell’invocazione e della comunicazione Transport Layer: localizza il server RMI relativo all’oggetto remoto richiesto, gestisce le connessioni (TCP/IP, timeout) e le trasmissioni (sequenziali, serializzate), usando un protocollo proprietario Reti di calcolatori, Java RMI - 4 Accesso ad oggetti remoti In Java non sono possibili riferimenti remoti C1 instance CLASS server S1 S1 instance S1 instance state operations Supporto di INTEGRAZIONE per la DISTRIBUZIONE ma si possono costruire con RMI Remote Method Invocation due proxy, stub dalla parte del cliente, skeleton dalla parte del servitore C1 instance S1 instance C1 Stub CLIENT node S1 Skeleton SERVER node Reti di calcolatori, Java RMI - 5 Modello a oggetti distribuito Nel modello ad oggetti distribuito di Java un oggetto remoto consiste in: - un oggetto descritto tramite interfacce remote che dichiarano i metodi accessibili da remoto - un oggetto i cui metodi sono invocabili da un'altra JVM, potenzialmente in esecuzione su un host differente Separazione tra definizione del comportamento => interfacce implementazione del comportamento => classi Per realizzare componenti utilizzabili in remoto: 1. definizione del comportamento Ö interfaccia che - estende java.rmi.Remote e - propaga java.rmi.RemoteException 2. implementazione comportamento Ö classe che - implementa l’interfaccia definita - estende java.rmi.UnicastRemoteObject Reti di calcolatori, Java RMI - 6 Uso di RMI Per sviluppare un’applicazione distribuita usando RMI si deve: 1. Definire interfacce e implementazioni componenti utilizzabili in remoto dei 2. Compilare le classi (con javac) e generare stub e skeleton (con rmic) delle classi utilizzabili in remoto 3. Pubblicare il servizio - attivare il registry - registrare il servizio (il server deve fare una bind sul registry) 4. Il cliente deve ottenere il riferimento all’oggetto remoto tramite il name service, facendo una lookup sul registry A questo punto l’interazione tra il cliente e il server può procedere Reti di calcolatori, Java RMI - 7 Esempio: servizio di echo remoto Definizione dell’interfaccia del servizio public interface EchoInterface extends java.rmi.Remote { String getEcho(String echo) throws java.rmi.RemoteException; } Reti di calcolatori, Java RMI - 8 Implementazione del Server public class EchoRMIServer extends java.rmi.server.UnicastRemoteObject implements EchoInterface{ // Costruttore public EchoRMIServer() throws java.rmi.RemoteException { super(); } // Implementazione del metodo remoto dichiarato nell'interfaccia public String getEcho(String echo) throws java.rmi.RemoteException { return echo; } public static void main(String[] args){ // Registrazione del servizio try { EchoRMIServer serverRMI = new EchoRMIServer(); Naming.rebind("EchoService", serverRMI); } catch (Exception e) {e.printStackTrace(); System.exit(1); } } } Reti di calcolatori, Java RMI - 9 Implementazione del Client public class EchoRMIClient { // Avvio del Client RMI public static void main(String[] args) { BufferedReader stdIn= new BufferedReader( new InputStreamReader(System.in)); try { // Connessione al servizio RMI remoto EchoInterface serverRMI = (EchoInterface) java.rmi.Naming.lookup("EchoService"); // Interazione con l'utente String message, echo; System.out.print("Messaggio? "); message = stdIn.readLine(); // Richiesta del servizio remoto echo = serverRMI.getEcho(message); System.out.println("Echo: "+echo+"\n"); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } } Reti di calcolatori, Java RMI - 10 Compilazione javac EchoInterface.java EchoRMIClient.java EchoRMIServer.java Creazione dello Stub e dello Skeleton Con il compilatore RMI: rmic -vcompat EchoRMIServer Che genera i file: EchoRMIServer_Stub.class EchoRMIServer_Skel.class Esecuzione 1. Avviamento del registry: rmiregistry 2. Avviamento del server: java EchoRMIServer 3. Avviamento del client: java EchoRMIClient Reti di calcolatori, Java RMI - 11 La serializzazione Marshalling: processo di codifica degli argomenti e dei risultati per la trasmissione Unmarshalling: processo inverso di decodifica di argomenti e risultati ricevuti In Java questo problema è risolto usando la serializzazione, che viene fatta in maniera trasparente dal supporto Serializzazione: trasformazione di complessi in semplici sequenze di byte oggetti => metodo writeObject() su uno stream di output Deserializzazione: decodifica di una sequenza di byte e costruzione di una copia dell’oggetto originale => metodo readObject() da uno stream di input Utilizzo: - storage - trasmissione (parametri e valori di ritorno in RMI) Stiamo lavorando al livello OSI di PRESENTAZIONE Reti di calcolatori, Java RMI - 12 Si possono serializzare soltanto istanze di oggetti serializzabili, ovvero che: - implementano l’interfaccia Serializable contengono esclusivamente oggetti (o riferimenti a oggetti) serializzabili NOTA BENE: NON viene trasferito l’oggetto vero e proprio ma solo le informazioni che caratterizzano l’istanza Al momento della deserializzazione sarà ricreata una copia dell’istanza “trasmessa” usando il .class (che deve quindi essere accessibile!!!) dell’oggetto e le informazioni ricevute. Esempio oggetto serializzabile Riprendendo il server di echo Ö messaggio come oggetto anziché come stringa public class Message implements Serializable { String content; // … altri eventuali campi public Message(String msg) { content=msg; } public String toString() { return content; } } Reti di calcolatori, Java RMI - 13 Passaggio di parametri Tipo Tipi primitivi Oggetti Metodo Locale Per valore Per riferimento Oggetti Remoti Esportati Per riferimento Metodo Remoto Per valore Per valore (deep copy) Per riferimento remoto Shallow Copy vs Deep Copy Passaggio per valore => Serializable Objects Passaggio per riferimento => Remote Objects Serializable Objects Oggetti la cui locazione non è rilevante per lo stato sono passati per valore: ne viene serializzata l’istanza che sarà deserializzata a destinazione per crearne una copia locale. Remote Objects Oggetti la cui funzione è strettamente legata alla località in cui eseguono (server) sono passati per riferimento: ne viene serializzato lo stub, creato automaticamente dal proxy (stub o skeleton) su cui viene fatta la chiamata in cui compaiono come parametri Reti di calcolatori, Java RMI - 14 Stub e Skeleton Stub e Skeleton garantiscono una parziale trasparenza e sono oggetti generati dal compilatore RMI che gestiscono il supporto RMI via serializzazione/deserializzazione e comunicazione (socket) tra client e server Procedura di comunicazione: 1. il client ottiene un’istanza dello stub 2. il client chiama metodi sullo stub 3. lo stub: - crea una connessione con lo skeleton (o ne usa una già esistente) - fa il marshalling delle informazioni per la chiamata (id del metodo e argomenti) - invia le informazioni allo skeleton 4. lo skeleton: - fa unmarshalling dei dati ricevuti - effettua la chiamata sull’oggetto che implementa il server - fa marshalling del valore di ritorno e invio allo allo stub 5. lo stub: - fa unmarshalling del valore di ritorno e restituzione del risultato al client Reti di calcolatori, Java RMI - 15 Interazione Client/Server: Socket e Thread Client L’invocazione di un metodo remoto implica la connessione TCP (Socket stream) con l’oggetto remoto Condivisione delle connessioni tra la JVM client e la JVM server: richiesta di connessione per una stessa JVM se c’è una connessione aperta Ö riuso se non c’è una connessione aperta Ö nuova Al completamento di una operazione la connessione viene liberata e rimane attiva fino allo scadere di un intervallo di timeout RMI permette a più thread clienti di accedere ad uno stesso oggetto server gestendo in modo automatico la concorrenza delle operazioni Ö l’implementazione del cliente non deve preoccuparsi Reti di calcolatori, Java RMI - 16 Server: Dipende tutto dall’implementazione. Nel seguito consideriamo una possibile e semplice implementazione concorrente. L’esportazione di un oggetto remoto provoca la creazione di • diversi thread per la gestione delle richieste (Server Socket) • un thread che riceve le richieste (Listener Thread) associati all’oggetto remoto Condivisione delle server socket e dei listener: server in esecuzione nella stessa JVM possono condividere la porta d’ascolto (soluzione di default se non la specificano) e il listener che ne gestisce le richieste Ogni richiesta viene gestita da • un thread che esegue la richiesta (Server Thread) RMI permette a più thread di accedere ad uno stesso oggetto server (gestione concorrente delle richieste) Ö l’implementazione dei metodi remoti deve essere thread-safe Reti di calcolatori, Java RMI - 17 RMI Registry Localizzazione del servizio: un client in esecuzione su una macchina ha bisogno di localizzare un server a cui vuole connettersi, che è in esecuzione su un’altra macchina. Tre possibili soluzioni: • Il client conosce in anticipo dov’è il server • L’utente dice all’applicazone client dov’è il server (es. e-mail client) • Un servizio standard (naming service) in una locazione ben nota, che il client conosce, funziona come punto di indirezione Java RMI utilizza un naming service: RMI Registry Mantiene un insieme di coppie {name, reference} Name: stringa arbitraria non interpretata Name Echo Server Reference Echo Daytime Server Daytime Login Login Server Trasparenza alla locazione?!? Reti di calcolatori, Java RMI - 18 Accesso al Registry Metodi della classe java.rmi.Naming: public public public public public static static static static static void bind(String name, Remote obj) void rebind(String name, Remote obj) void unbind(String name) String[] list(String name) Remote lookup(String name) name -> combina la locazione del registry e il nome logico del servizio, nel formato: //registryHost:port/logical_name A default: registryHost = macchina su cui esegue il programma che invoca il metodo port = 1099 Ognuno di questi metodi crea una connessione (socket) con il registry identificato da host e porta Attivazione del Registry Usare il programma rmiregistry di Sun, lanciato specificando o meno la porta: rmiregistry rmiregistry 10345 Reti di calcolatori, Java RMI - 19 Sicurezza del registry Problema: accedendo al registry (individuabile interrogando tutte le porte di un host) è possibile ridirigere per scopi maliziosi le chiamate ai server RMI registrati (es. list()+rebind()) Soluzione: i metodi bind(), rebind() e unbind() sono invocabili solo dall’host su cui è in esecuzione il registry Ö non si accettano modifiche della struttura client/server da nodi esterni Ö sull’host in cui vengono effettuate le chiamate deve esserci almeno un registry in esecuzione Reti di calcolatori, Java RMI - 20 Distribuzione delle classi In una applicazione RMI è necessario che siano disponibili gli opportuni file .class nelle località che lo richiedono (per l’esecuzione o per la deserializzazione) Il Server deve poter accedere a: • • • • interfacce che definiscono il servizio implementazione del servizio stub e skeleton delle classi di implementazione altre classi utilizzate dal server Il Client deve poter accedere a: • • • • interfacce che definiscono il servizio stub delle classi di implementazione del servizio classi del server usati dal client (es. valori di ritorno) altre classi utilizzate dal client Reti di calcolatori, Java RMI - 21 Esempio Completo Gestione Registrazioni Congresso Si progetti un’applicazione Client/Server per la gestione delle registrazioni ad un congresso. L’organizzazione del congresso fornisce agli speaker delle varie sessioni un’interfaccia tramite la quale iscriversi ad una sessione, e la possibilità di visionare i programmi delle varie giornate del congresso, con gli interventi delle varie sessioni. Il Server mantiene sul suo nodo di residenza i programmi delle 3 giornate del congresso, ciascuno dei quali è memorizzato in una struttura dati in cui ad ogni riga corrisponde una sessione (in tutto 12 per ogni giornata). Per ciascuna sessione vengono memorizzati i nomi degli speaker che si sono registrati (al massimo 5). Sessione Intervento 1 Intervento 2 S1 Nome Speaker1 … … Intervento 5 Nome Speaker2 S2 S3 … S12 Reti di calcolatori, Java RMI - 22 Il Client può richiedere operazioni per: - registrare uno speaker ad una sessione; - ottenere il programma del congresso; Il Client inoltra le richieste al Server in modo appropriato, e per ogni possibile operazione prevedono anche una gestione di eventuali condizioni anomale (come per esempio la richiesta di registrazione ad una giornata e/o sessione inesistente oppure per la quale sono già stati coperti tutti gli spazi d’intervento). Si effettuino i controlli dove è più opportuno farli. Il Client é implementato come un processo ciclico che continua a fare richieste sincrone fino ad esaurire tutte le esigenze utente, cioè fino alla fine del file di input dell'utente. Reti di calcolatori, Java RMI - 23 Alcuni esempi di interazione per la richiesta di registrazione: ¾ Giornata? 25 ¾ Giornata non valida ¾ Giornata? 2 ¾ Sessione? S46 ¾ Sessione non valida ¾ Giornata? 2 ¾ Sessione? S1 ¾ Nome speaker? Pippo ¾ Registrazione effettuata correttamente ¾ Giornata? 2 ¾ Sessione? S1 ¾ Nome speaker? Pluto ¾ Registrazione non effettuata: sessione completa ecc. Alcuni esempi di interazione per la visione del programma: ¾ Giornata? 1 ¾ Programma della prima giornata congresso Sessione S1: primo intervento: NomeSpeaker1 secondo intervento: non registrato … quinto intervento: NomeSpeaker5 Sessione S2: … ecc. del Reti di calcolatori, Java RMI - 24 Il progetto RMI si compone di: • una classe (Programma contenuta nel file Programma.java), che implementa la struttura dati contenente gli interventi delle varie sessioni tenute nella medesima giornata; si noti che i programmi delle varie giornate andranno poi gestiti con una opportuna struttura dati che li raccolga tutti; • un’interfaccia (ServerCongresso, contenuta nel file ServerCongresso.java) in cui vengono definiti i metodi invocabili in remoto dal client (registrazione, programma): • una classe (ServerCongressoImpl contenuta nel file ServerCongressoImpl.java), che implementa i metodi del server invocabili in remoto; • una classe (ClientCongresso contenuta nel file ClientCongresso.java), che realizza l’interazione con l’utente mettendo a disposizione le funzionalità descritte, fornite effettuando le opportune chiamate remote. Il Server presenta l’interfaccia di invocazione: ServerCongressoImpl mentre il Client viene attivato con: ClientCongresso NomeHost Il Client (istanza della classe relativa) deve recuperare dal registry, in esecuzione sull’host specificato, il riferimento all’oggetto remoto, ServerCongresso, di cui deve invocare i metodi. Reti di calcolatori, Java RMI - 25 Compilazione esempio esteso javac Programma.java ServerCongresso.java ServerCongressoImpl.java ClientCongresso.java Creazione dello Stub e dello Skeleton Con il compilatore RMI: rmic -vcompat ServerCongressoImpl Che genera i file: ServerCongressoImpl _Stub.class ServerCongressoImpl _Skel.class Esecuzione esempio esteso 2. Avviamento del registry: rmiregistry 4. Avviamento del server: java ServerCongressoImpl 5. Avviamento del client: java ClientCongresso Reti di calcolatori, Java RMI - 26 Classe Programma public class Programma implements Serializable{ public String speaker[][] = new String[12][5]; public Programma(){ for (int i=0; i<5; i++) for (int e=0; e<12; e++) speaker[e][i] = ""; } public synchronized int registra (int sessione, String nome){ for (int k=0;k<5; k++){ if (speaker[sessione][k]=="") { speaker[sessione][k] = nome; return 0; } } return 1; } public void stampa(){ System.out.println("Sessione\tIntervento1\t Intervento2\tIntervento3\tIntervento4\t Intervento5\n"); for (int k=0; k<12; k++){ String line = new String("S"+(k+1)); for (int j=0;j<5;j++){ line = line + "\t\t"+speaker[k][j]; } System.out.println(line); } } } Reti di calcolatori, Java RMI - 27 Interfaccia Remota del Servizio import java.rmi.Remote; import java.rmi.RemoteException; public interface ServerCongresso extends Remote { int registrazione(int giorno, String sessione, String speaker) throws RemoteException; Programma programma(int giorno) throws RemoteException; } Reti di calcolatori, Java RMI - 28 Client class ClientCongresso{ public static void main(String[] args) { final int REGISTRYPORT = 1099; String registryHost = "localhost"; String serviceName = "ServerCongresso"; BufferedReader stdIn = new BufferedReader (new InputStreamReader(System.in)); // Connessione al servizio RMI remoto String completeName = "//" + registryHost + ":" + REGISTRYPORT + "/" + serviceName; ServerCongresso serverRMI = (ServerCongresso)Naming.lookup(completeName); Reti di calcolatori, Java RMI - 29 // Ciclo di interazione con l’utente System.out.println("\nRichieste di servizio fino a fine file"); System.out.print("Servizio (R=Registrazione, P=Programma del congresso): "); String service; boolean ok; while((service=stdIn.readLine())!=null){ if (service.equals("R")){ // giornata ok=false; int g; System.out.print("Giornata (1-3)? "); while (ok!=true){ g = Integer.parseInt(stdIn.readLine()); if (g < 1 || g > 3){ System.out.println("Giornata non valida"); System.out.print("Giornata (1-3)? "); continue; } else ok=true; } // sessione ok=false; String sess; System.out.print("Sessione (S1 - S12)? "); while (ok!=true){ sess = stdIn.readLine(); if (!sess.equals("S1") && … !sess.equals("S12")){ ... continue; } else ok=true; } // speaker System.out.print("Speaker? "); String speak = stdIn.readLine(); Reti di calcolatori, Java RMI - 30 // Tutto corretto if (serverRMI.registrazione(gg, sess, speak)==0) System.out.println("Registrazione di "+speak+ "effettuata giorno "+gg+" sessione "+sess); else System.out.println("Registrazione non " "effettuata, sessione completa”); } // R else if (service.equals("P"){ int g; boolean ok=false; System.out.print("Giornata (1-3)? "); while (ok!=true){ g = Integer.parseInt(stdIn.readLine()); if (g < 1 || g > 3){ System.out.println("Giornata non valida"); System.out.print("Giornata (1-3)? "); continue; }else ok=true; } Programma prog = serverRMI.programma(g); System.out.println("Programma giornata "+g+"\n"); prog.stampa(); } // P else System.out.println("Servizio non disponibile"); System.out.print("Servizio (R=Registrazione, P=Programma del congresso): "); } //!EOF } //try catch (Exception e){ ... } } // main } // ClientCongresso Reti di calcolatori, Java RMI - 31 Server public class ServerCongressoImpl extends UnicastRemoteObject implements ServerCongresso { static Programma prog[]; // Costruttore public ServerCongressoImpl() throws RemoteException { super(); } // Richiesta di prenotazione public int registrazione(int giorno, String sessione, String speaker) throws RemoteException { int numSess = -1; System.out.println("Server RMI: richiesta registrazione con parametri"); System.out.println("giorno = "+giorno); System.out.println("sessione = "+sessione); System.out.println("speaker = "+speaker); if (sessione.equals("S1")) numSess = 0; else if (sessione.equals("S2")) numSess = 1; … else if (sessione.equals("S12")) numSess = 11; /* Se i dati sono sbagliati significa che sono stati trasmessi male e quindi solleva una eccezione */ if (numSess == -1) throw new RemoteException(); if (giorno < 1 || giorno > 3) throw new RemoteException(); return prog[giorno-1].registra(numSess,speaker); } Reti di calcolatori, Java RMI - 32 // Richiesta di programma public Programma programma(int giorno) throws RemoteException{ System.out.println("Server RMI: richiesto programma del giorno "+giorno); if (giorno < 1 || giorno > 3) throw new RemoteException(); return prog[giorno-1]; } // Avvio del Server RMI public static void main(String[] args){ //creazione programma prog=new Programma[3]; for (int i=0; i<3; i++) prog[i]=new Programma(); final int REGISTRYPORT = 1099; String registryHost = "localhost"; String serviceName = "ServerCongresso"; try{ // Registrazione del servizio RMI String completeName = "//" + registryHost + ":" + REGISTRYPORT + "/" + serviceName; ServerCongressoImpl serverRMI = new ServerCongressoImpl(); Naming.rebind(completeName, serverRMI); } catch (Exception e){ ... } } //main } //ServerCongressoImpl Reti di calcolatori, Java RMI - 33