Read Aloud the Text Content
This audio was created by Woord's Text to Speech service by content creators from all around the world.
Text Content or SSML code:
7.1 Introduzione L’architettura RMI introduce la possibilità di richiedere l’esecuzione di metodi remoti in Java, integrando il tutto con il paradigma a oggetti. RMI è un insieme di strumenti, politiche e meccanismi che permettono ad un’applicazione Java in esecuzione su una macchina di invocare i metodi di un oggetto di un’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 e un programma client invoca i metodi attraverso questo riferimento locale mantenuto in una variabile interfaccia. Le interazioni in RMI sono sempre sincrone e bloccanti; l’ambiente di lavoro è unico dato che si utilizza il linguaggio Java, ma si possono usare sistemi anche eterogenei grazie alla portabilità del codice. 7.2 Architettura RMI In Java non sono direttamente possibili i riferimenti remoti, ma RMI permette di costruirli tramite due proxy: lo stub dal lato client e lo skeleton dal lato server; tramite questi due proxy, usando quindi il pattern proxy, si nasconde al livello applicativo la natura distribuita dell’applicazione. Tuttavia, il client riuscirà a percepire differenze nell’affidabilità, semantica, durata, etc. Per poter usare i riferimenti remoti, RMI utilizza una variabile interfaccia che dinamicamente può contenere un riferimento a un’istanza di una classe qualunque che implementa l’interfaccia stessa. I livelli presenti nell’architettura RMI sono: stub: è il proxy locale su cui vengono fatte le invocazioni destinate all’oggetto remoto; skeleton: è l’entità remota che riceve le invocazioni fatte sullo stub e le realizza effettuando le corrispondenti chiamate sul server; RRL, Remote Reference Layer: è responsabile della gestione dei riferimenti agli oggetti remoti, dei parametri e delle astrazioni di stream-oriented connection; TL, Transport Layer: è il livello di trasporto connesso e: è responsabile della gestione delle connessioni fra i diversi spazi di indirizzamento (JVM diverse); gestisce il ciclo di vita delle connessioni e le attivazioni integrate in JVM; può utilizzare protocolli applicativi diversi, purché siano connection-oriented (di default, in RMI, si usa TCP a livello di trasporto); utilizza un protocollo proprietario. registry: è il servizio di nomi che consente al server di pubblicare un servizio e al client di recuperarne il proxy. I due livelli RRL e TL sono parte della macchina virtuale JVM. Nel modello a oggetti distribuito di Java, un oggetto remoto consiste in: un oggetto i cui metodi sono invocabili da un’altra JVM, potenzialmente in esecuzione su un differente host; un oggetto descritto tramite una o più interfacce remote che dichiarano i metodi accessibili da remoto. Per quanto riguarda le differenze tra una chiamata remota e una locale, il client invoca un metodo di un oggetto remoto attraverso un riferimento remoto (variabile interfaccia): la sintassi è uguale, quindi c’è trasparenza per il client, ma la semantica, in questo caso at-most-once con uso di TCP, è diversa: le chiamate locali hanno un’affidabilità massima, quelle remote possono fallire. Il server remoto nell’architettura RMI esegue ogni chiamata in modo indipendente e parallelo. 7.3 Interfacce e implementazione In RMI si fa distinzione tra: definizione del comportamento; per questo si usano le interfacce: ogni interfaccia deve estendere java.rmi.Remote; ogni metodo deve propagare java.rmi.RemoteException. implementazione del comportamento; per questo si usano le classi: la classe che implementa il comportamento deve implementare l’interfaccia definita; la classe deve estendere java.rmi.UnicastRemoteObject. I componenti remoti sono riferiti tramite variabili interfaccia, che possono contenere solo istanze di classi che implementano l’interfaccia. Per utilizzare Java RMI è necessario: 1. definire l’interfaccia e l’implementazione del componente utilizzabile in remoto (ovvero l’interfaccia e il server); 2. compilare l’interfaccia e la classe del punto 1 con il comando java, poi generare stub e skeleton con il comando rmic -vcompat della classe utilizzabile in remoto; 3. pubblicare il servizio nel sistema di nomi, attivare il registry e registrare il servizio (il server deve fare una bind sul registry); 4. ottenere, dal lato client, il riferimento all’oggetto remoto tramite il name service, facendo una lookup sul registry, e compilare il client. A questo punto l’interazione tra client e server può procedere; si noti che è necessario attivare prima il server e poi il client. Ciascun metodo remoto ha un solo risultato di uscita e nessuno, uno o più parametri di ingresso; i parametri devono essere passati per valore (dati primitivi oppure oggetti Serializable) o per riferimento remoto (oggetti Remote). Il registry è un processo in esecuzione sull’host del server e registra tutti i servizi: invoca tante operazioni di bind/rebind quanti sono gli oggetti server da registrare ciascuno con un nome logico. 7.4 RMI Registry Un RMI registry risponde alla necessità di localizzazione di un servizio: un client in esecuzione su una macchina ha bisogno di localizzare un server, in esecuzione su un’altra macchina, a cui connettersi. Il registry mantiene al suo interno un insieme di coppie {name, reference} in cui name è una stringa arbitraria, del tipo //nomehost:porta/servizio, che indica un servizio e reference indica su quale server si trova tale servizio. Quando si avvia il programma rmiregistry sull’host server si può specificare o meno la porta sulla quale lo si sta utilizzando; di default è la porta 1099. Dato che l’rmiregistry viene attivato in una shell a parte, esso è attivato in una nuova istanza JVM separata dalla JVM del server e anch’esso, per ragioni di economicità di implementazione e di principio, è un server RMI. È anche possibile creare all’interno del codice del server un proprio registry con il metodo public static Registry createRegistry(int port), un metodo della classe LocateRegistry; in questo caso il registry verrà creato nella stessa istanza JVM del server. 7.5 Stub e Skeleton Stub e skeleton rendono possibile la chiamata di un servizio remoto come se fosse locale, agendo da proxy. Sono entrambi generati dal compilatore RMI e l’ambiente Java supporta già direttamente la serializzazione e de-serializzazione. La procedura di comunicazione è: il client ottiene un riferimento remoto, attua la chiamata dei metodi tramite lo stub e aspetta il risultato; lo stub effettua la serializzazione delle informazioni per la chiamata (ID del metodo, identificazione e argomenti) e invia le informazioni allo skeleton utilizzando le astrazioni messe a disposizione dal RRL; lo skeleton effettua la de-serializzazione dei dati ricevuti, invoca la chiamata sull’oggetto che implementa il server (dispatching), effettua la serializzazione del valore di ritorno e lo invia allo stub; lo stub effettua la de-serializzazione del valore di ritorno e lo restituisce al client. 7.6 Serializzaz e passagg dei param In generale, in sistemi RPC, i parametri di ingresso e uscita subiscono una duplice trasformazione per risolvere problemi di rappresentazioni eterogenee (livello 6 di OSI, presentazione): marshalling: processo di codifica degli argomenti e dei risultati per la trasmissione; unmarshalling: processo inverso di decodifica di argomenti e risultati ricevuti. In Java, grazie all’uso del BYTECODE uniforme e standard, non c’è bisogno di queste trasformazioni: i dati vengono semplicemente serializzati e de-serializzati, ossia inseriti in un messaggio lineare, utilizzando le funzionalità offerte direttamente a livello di linguaggio: la serializzazione trasforma oggetti complessi in semplici sequenze di byte (anche grafi di oggetti) tramite il metodo writeObject() su uno stream di output; la de-serializzazione decodifica una sequenza di byte e costruisce una copia dell’oggetto originale tramite il metodo readObject() da uno stream di input. Stub e skeleton utilizzano queste funzionalità per lo scambio dei parametri di ingresso e uscita con l’host remoto. I tipi primitivi sono passati per valore sia nel caso locale che nel caso remoto; gli oggetti nel caso locale sono passati per riferimento, mentre nel caso remoto sono passati per valore (interfaccia Serializable deep copy); gli oggetti remoti sono passati per riferimento remoto tramite l’interfaccia Remote.