• Non ci sono risultati.

Architettura di Java Remote Method Invocation

N/A
N/A
Protected

Academic year: 2021

Condividi "Architettura di Java Remote Method Invocation"

Copied!
13
0
0

Testo completo

(1)

Architettura di Java

Remote Method

Invocation

Lo scopo iniziale degli sviluppatori del linguaggio Java [1] era quello di creare un linguaggio orientato agli oggetti che fosse “pulito”, semplice e portabile. Originariamente, era stato pensato soprattutto per facilitare la comunicazione fra devices hardware di vario genere, ma in seguito, con lo sviluppo esponenziale di Internet, sono state aggiunte molte altre caratteristiche che praticamente lo hanno reso “il linguaggio della Rete”. Inoltre, Java è risultato particolarmente adatto per la creazione di applicazioni distribuite su larga scala.

La caratteristica principale di Java è quella di avere un compilatore che traduce il codice sorgente non già in linguaggio macchina, ma in un linguaggio intermedio detto Java bytecode che è indipendente dalla piattaforma, ed è quindi libero da ogni limitazione di portabilità, almeno teoricamente. Il bytecode viene poi interpretato dalla Java Virtual Machine (JVM) che provvede così

(2)

all’esecuzione vera e propria; se si dispone di una JVM opportuna per ciascuna delle piattaforme che si incontrano in un’applicazione, è possibile sviluppare interamente in Java applicazioni distribuite che girano anche su reti eterogenee.

Java possiede anche alcune caratteristiche che supportano la mobilità del codice. Infatti viene fornito un meccanismo programmabile, il class loader, che permette di caricare e collegare le classi di cui si ha bisogno dinamicamente a runtime (le classi sono le unità fondamentali in cui il bytecode è suddiviso). Il class loader può prelevare le classi che gli sono necessarie anche da un nodo remoto; inoltre il caricamento e il collegamento delle classi può essere comandato esplicitamente dall’applicazione. Si può affermare, allora, che Java supporta una weak mobility (del bytecode) con meccanismi di fetching di parti di codice. Questi meccanismi possono essere sincroni o asincroni e permettere un’esecuzione immediata o posticipata del codice prelevato. Il codice caricato, comunque, non è associato a uno stato di esecuzione, né possiede legami con risorse poste su nodi remoti. Questo implica che non sono necessari meccanismi di gestione dello spazio dei dati.

1.1 Applicazioni a oggetti distribuiti

con JavaRMI

Il meccanismo RMI (Remote Method Invocation), presente nel linguaggio Java a partire dalla versione 1.1 [2], rende possibile l’implementazione di applicazioni a oggetti distribuiti, composte cioè da oggetti posti su nodi di rete diversi e in grado di interagire fra loro mediante semplici invocazioni di metodo. Infatti con RMI un oggetto può chiamare un metodo di un altro oggetto posto su un nodo remoto nello stesso modo in cui viene effettuata una chiamata a un metodo locale. Ovviamente ciò che all’occhio del programmatore può sembrare

(3)

una semplice invocazione di metodo, nasconde, in realtà, un meccanismo ben più complesso, tramite il quale è possibile far comunicare tra loro, oggetti posti su differenti JVM.

Nella terminologia adottata da RMI, l’oggetto il cui metodo crea la chiamata remota viene definito oggetto client, mentre l’oggetto remoto è chiamato oggetto server; di conseguenza, il computer che esegue il codice Java che chiama il metodo remoto, rappresenta il client di quella chiamata, mentre il computer che ospita l’oggetto remoto che elabora la chiamata, rappresenta il server. E’ importante ricordare che la terminologia client/server si applica solo ad una singola invocazione di metodo, ed è possibile che i ruoli cambino durante la comunicazione: il server di una precedente chiamata può diventare client quando a sua volta chiama un metodo di un oggetto posto su un altro computer.

Dunque, un’applicazione RMI è basata su un lato server e un lato client. Il server crea un certo numero di oggetti remoti, rende pubblico il riferimento a essi e attende che un client invochi metodi su qualcuno di tali oggetti. Il client, da parte sua, deve procurarsi il riferimento remoto a un oggetto, e attraverso esso, invocare il metodo desiderato.

Le operazioni fondamentali compiute da un’applicazione RMI sono dunque:

• Localizzare l’oggetto remoto. Il server inserisce un riferimento remoto in un registro (rmi-registry) nel quale l’oggetto è individuabile mediante un nome che lo identifica univocamente, e al quale il client può accedere per ottenere il riferimento remoto cui è interessato; oppure il riferimento remoto può essere passato come valore di ritorno, o come parametro di ingresso, di un altro metodo remoto.

• Comunicare con gli oggetti remoti. La comunicazione è completamente nascosta al programmatore, che vede la chiamata di metodo remoto come se fosse locale.

(4)

• Caricare il bytecode per gli oggetti passati come parametri o come

ritorno di metodi. RMI offre la possibilità, grazie ai meccanismi di class

loading, di caricare bytecode da server ftp o http posti su nodi remoti, anche se questi nodi non sono coinvolti nell’interazione client-server relativa all’invocazione di metodo remoto considerata.

1.2 Architettura di RMI

Un oggetto remoto in RMI è progettato per fornire un certo servizio. La

definizione del servizio è rappresentata da un’interfaccia Java che rispetti

determinate regole, mentre la sua implementazione è costituita da un’opportuna classe che deve implementare (nel senso della keyword Java implements) quell’interfaccia e contenere quindi il codice effettivo che i metodi da essa specificati devono eseguire. L’interfaccia remota deve estendere java.rmi.Remote, ed ogni suo metodo deve lanciare l’eccezione java.rmi.RemoteException, oltre alle eccezioni specifiche delle applicazioni. L’eccezione RemoteException serve ad indicare eventuali errori di comunicazione di rete al client, il quale, intercettandola, potrà eventualmente gestire il recovery dell’applicazione.

Sia il lato client che il lato server devono possedere la definizione del servizio, ovvero devono avere a disposizione l’interfaccia; ovviamente la classe che implementa il servizio (e le cui istanze sono effettivamente gli oggetti remoti) deve essere presente solo sul server. Anche sul client, però, è necessaria una classe che formalmente implementi l’interfaccia. Questa classe, detta stub, svolge funzioni di proxy (vedi figura 1.1); quando il client invoca un metodo dell’oggetto remoto, in realtà effettua una chiamata al corrispondente metodo

(5)

dello stub locale, il quale si occupa di trasmettere la chiamata all’oggetto remoto. Più precisamente, lo stub sul client crea un blocco di informazioni contenente:

• Un identificatore dell’oggetto remoto da utilizzare. • Una descrizione del metodo da chiamare.

• I parametri sui quali è stato eseguito il marshal, cioè un’operazione di codifica in un formato adatto al loro trasporto da una Virtual Machine all’altra.

Successivamente, queste informazioni vengono inviate al server, dove un oggetto ricevitore esegue, per ciascuna chiamata al metodo remoto, le seguenti azioni:

• Esegue l’unmarshal sui parametri. • Individua l’oggetto da invocare. • Chiama il metodo desiderato.

• Cattura ed esegue il marshal sul valore di ritorno o sull’eventuale eccezione sollevata dalla chiamata.

• Invia allo stub del client tale risultato.

A sua volta, lo stub del client esegue l’unmarshal sul risultato, restituendo il valore di ritorno al client oppure rilanciando l’eventuale eccezione nello spazio di elaborazione del chiamante.

Questo processo (rappresentato in figura 1.2) può apparire complesso, ma in realtà, il tutto avviene in modo completamente automatico e trasparente per il programmatore Java; infatti, la sintassi per una invocazione di metodo remoto è identica a quella utilizzata per una chiamata locale.

(6)

Figura 1.1 – Interfaccia di un servizio remoto e relativo stub

(7)

1.2.1 Livelli dell’architettura

L’architettura interna di RMI è divisa in tre livelli fondamentali, ovvero, dal più “alto” al più “basso”:

• Livello Stub1. Uno stub è un oggetto locale che agisce come proxy per l’oggetto remoto, offrendo gli stessi metodi di quest’ultimo. Gli oggetti locali richiamano i metodi dello stub come se fossero quelli dell’oggetto remoto; lo stub, poi, si occupa di comunicare questa chiamata all’oggetto remoto e di riceverne il risultato.

• Livello Riferimento Remoto. Gestisce i riferimenti agli oggetti remoti e si occupa di aprire le connessioni (di tipo unicast) fra il server e il client. • Livello Trasporto. Fornisce tutte le funzionalità necessarie alla gestione

delle connessioni fra server e client. Le connessioni sono effettuate utilizzando i protocolli TCP/IP, ma la flessibilità di RMI permette di utilizzare altri protocolli.

Al di sopra dei tre livelli citati si collocano gli oggetti direttamente implementati dal programmatore (livello Oggetto).

La figura 1.3 fornisce una rappresentazione di questi livelli sia dal lato client che dal lato server.

1 Questo livello è presente solamente sul lato client. Nelle versioni precedenti alla JDK 1.2, esisteva anche

sul lato server un livello intermedio fra il livello Riferimento Remoto e il livello oggetto, detto livello

(8)

Figura 1.3 – Livelli dell’architettura di RMI

Oltre alle interfacce Remote e RemoteException, il programmatore dispone di ulteriori classi e interfacce che sono a sua disposizione per specificare e implementare i servizi remoti. Queste sono rappresentate nella figura 1.4, e di seguito ne viene data una breve descrizione, rimandando ai capitoli seguenti per approfondimenti.

(9)

Figura 1.4 – Gerarchia delle principali classi di RMI

L’interfaccia Remote non definisce alcun metodo; le interfacce che definiscono servizi remoti devono sempre estenderla, cosicché tale interfaccia è implementata, indirettamente, da ogni oggetto remoto.

La classe RemoteObject è una classe astratta che costituisce la radice della gerarchia. Si limita a ridefinire alcuni metodi della classe Object (come equals(), hashCode(), toString()) per renderli applicabili a oggetti remoti, a fornire il metodo toStub(), che restituisce un’istanza della classe Stub per l’oggetto remoto, e a implementare l’interfaccia Serializable (ridefinendo anche i metodi writeObject() e readObject()) rendendo

serializzabili, in tal modo, tutti gli oggetti remoti .

La classe astratta RemoteServer fornisce un metodo

(getClientHost()) che restituisce il nome dello host che agisce da client durante una chiamata di metodo remoto e provvede a funzionalità di logging. RemoteServer è la classe base per le due classi che possono essere effettivamente usate per definire oggetti remoti: Activatable e UnicastRemoteObject. La prima permette di definire oggetti remoti persistenti e attivabili dal sistema, mentre la seconda definisce oggetti remoti la cui vita è legata a quella del processo che li ha creati.

(10)

1.2.2 Servizio di registrazione

Il servizio di registrazione degli oggetti remoti si basa sugli oggetti Registry. Questi oggetti sono, a loro volta, oggetti remoti con i quali sia i client che i server possono interagire, invocando metodi remoti su di essi. È importante notare che, lo stub per questi oggetti è una classe appartenente ai package standard di RMI forniti con il Java SDK, quindi il servizio di registrazione è a disposizione di qualunque JVM. Questo significa che, un oggetto che intenda creare un’istanza del Registry può farlo specificando solamente l’URL del nodo sul quale deve essere creato; tipicamente la JVM che crea un oggetto remoto utilizza per la sua registrazione un Registry creato sullo stesso host dove essa risiede.

A livello implementativo, Registry è in realtà l’interfaccia del servizio, mentre le implementazioni dei suoi metodi sono fornite con la classe RegistryImpl, i cui metodi sono accessibili ai client attraverso gli omonimi metodi statici della classe Naming.

1.2.3 Passaggio di parametri di metodi remoti

Il meccanismo RMI prevede che i parametri di ingresso, così come il valore di ritorno di un metodo remoto, siano oggetti serializzabili [3]. Un oggetto è serializzabile se la classe di cui è istanza, oppure una sua superclasse, implementa l’interfaccia Serializable (la quale non dichiara alcun metodo).

Tutti i tipi primitivi, insieme a molti dei tipi disponibili nelle librerie standard di Java, sono serializzabili; lo sono anche gli oggetti remoti, in quanto la classe RemoteObject implementa l’interfaccia Serializable. Gli oggetti serializzati, vengono trasformati in sequenze di byte e poi, trasmessi attraverso la connessione di rete, in qualità di parametri o valori di ritorno di metodi.

(11)

Ogni volta che un oggetto non remoto deve essere trasportato da una JVM ad un’altra, questa esegue una copia e la invia tramite la connessione di rete (ovviamente dopo averla serializzata). Questa tecnica è diversa da quella usata per il passaggio dei parametri in un metodo locale. Infatti, quando si passano gli oggetti (o li si restituiscono come risultato) in un metodo locale, vengono passati solo i riferimenti agli oggetti, che sono degli indirizzi di memoria che hanno senso solo sulla JVM locale.

Quando, invece, viene passato un oggetto remoto dal server al client, quest’ultimo riceve uno stub che verrà salvato in una variabile oggetto il cui tipo è identico a quello dell’interfaccia remota. Solo attraverso lo stub, il client potrà accedere ai metodi dell’oggetto remoto situato sul server. Inoltre, il meccanismo RMI provvede a inviare un’annotazione con la quale si indica l’URL della locazione dalla quale può essere scaricato il bytecode della classe dell’oggetto remoto.

1.3 Garbage Collection Distribuita

Fra le prerogative di Java c’è il fatto che, benché il linguaggio usi praticamente sempre l’allocazione dinamica di memoria attraverso lo heap, il programmatore non deve porsi il problema della garbage collection, in quanto essa viene fatta automaticamente, grazie al supporto runtime del linguaggio.

Anche in ambito distribuito è desiderabile avere questa caratteristica, per questo motivo RMI implementa un meccanismo abbastanza complesso per la

garbage collection distribuita. A tale proposito, la libreria di runtime di RMI

tiene traccia di ogni riferimento all’oggetto remoto da parte di ciascuna JVM, tramite un meccanismo detto reference counting [4]: in particolare, ogni volta che un client riferisce un oggetto remoto, viene registrato tale riferimento in

(12)

opportune strutture dati. Sul lato server, per ogni oggetto remoto viene mantenuta una lista dei client che vi fanno riferimento; l’oggetto remoto potrà essere eliminato solo quando la lista diventa vuota. Più precisamente, in tale situazione viene autorizzato il sistema locale (non quello distribuito) di garbage collection a eliminare l’oggetto nel momento in cui questo non venga più riferito neppure localmente.

1.4 Class loading

RMI offre la possibilità di caricare dinamicamente da una locazione remota il bytecode delle classi degli oggetti che vengono passati come parametri o restituiti come valore di ritorno nelle invocazioni di metodo remoto (class

loading remoto). A questo scopo, sia i parametri che il valore di ritorno di un

metodo remoto vengono trasmessi attraverso delle particolari classi di stream, denominate MarshalOutputStream e MarshalInputStream. Ogni oggetto che viene “filtrato” attraverso un MarshalOutputStream è associato ad una

codebase, ovvero a un indirizzo URL, presso cui è possibile ottenere il bytecode

della classe dell’oggetto in questione. Quando l’oggetto viene letto, utilizzando un MarshalInputStream, viene acquisito anche l’URL, e si tenta di caricare il bytecode della classe corrispondente, mediante la classe RMIClassLoader.

La classe da caricare viene cercata con la seguente strategia:

• Si controlla, dapprima, se la classe sia stata precedentemente caricata e sia quindi già disponibile;

• Nel caso la classe non sia già disponibile, il suo caricamento viene delegato al class loader specificato, ovvero al class loader del thread in esecuzione;

(13)

• Se il campo che specifica il class loader da usare assume valore nullo, il caricamento è delegato al class loader di bootstrap.

• Se anche l’ultimo tentativo fallisce, si cerca, infine, di caricare la classe dalla codebase URL.

Si noti che il caricamento di una classe dalla codebase remota, essendo molto più costoso in termini temporali, viene riservato come extrema ratio per l’ultimo tentativo possibile.

Figura

Figura 1.1 – Interfaccia di un servizio remoto e relativo stub
Figura 1.3 – Livelli dell’architettura di RMI
Figura 1.4 – Gerarchia delle principali classi di RMI

Riferimenti

Documenti correlati

Si chiama anche COMPLEMENTO DIRETTO, perchè l’azione del verbo ricade direttamente sul complemento, senza preposizioni; gli altri complementi, che richiedono preposizioni,

NOMINALE, COSTITUITO DA UN NOME (SOSTANTIVO O AGGETTIVO) DETTO PARTE NOMINALE O NOME DEL PREDICATO, UNITO AL SOGGETTO PER MEZZO DELLA COPULA (CHE SVOLGE CIOÈ LA FUNZIONE DI

L’attività del caregiver è stata riconosciuta dalla legge di Bilancio 2018, che ha istituito per loro un fondo di 20 milioni di euro l’anno per il triennio 2018-2020,

E' un complemento diretto in quanto, a differenza dei complementi indiretti, è legato direttamente al termine da cui dipende (il verbo):?. • Stamattina ho

Nel caso in cui la ditta fosse impossibilitata ad effettuare il recapito, o nel caso di compiuta giacenza, è tenuta alla restituzione della corrispondenza al

In continuità con l’approccio assunto dalla Regione Lazio nel perseguire una strategia di intervento, già all’inizio della programmazione dei Fondi europei 2014-2020,

Durante tale periodo chiunque abbia interesse può prenderne visione dalle ore 8.00 alle ore 14.00 nei giorni feriali e dalle ore 9.00 alle ore 12.00 nei giorni festivi. Entro

Architettura di Java Remote Method Invocation (RMI).. Il