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