Programmazione distribuita in Java da materiale di Carlo Ghezzi e Alfredo Mo:a
Obie<vo Poter allocare computazioni su nodi fisici diversi e consen@re il coordinamento tra le computazioni sui diversi nodi
Nodi fisici e nodi logici Occorre dis@nguere tra nodi fisici e logici Può essere opportuno proge:are ignorando all inizio il nodo fisico in cui un nodo logico sarà allocato Java consente addiri:ura di vedere tu:o a:raverso la nozione di ogge< e di invocazione di metodi, dove l invocazione può essere remota
Archite:ura client- server È il modo classico di proge:are applicazioni distribuite su rete Server offre un servizio centralizzato a:ende che altri (client) lo conta<no per fornire il proprio servizio Client si rivolge ad apposito/i server per o:enere cer@ servizi
Middleware Per programmare un sistema distribuito vengono forni@ servizi (di sistema) specifici, come estensione del sistema opera@vo Il middleware viene posto tra il sistema opera@vo e le applicazioni In Java il middleware fa parte del linguaggio, diversamente da altri middleware (es. CORBA)
Middleware
Socket in Java Client e server comunicano (pacche< TCP) a:raverso socket Package java.net, classi: Socket, ServerSocket DatagramSocket (UDP): non considera@ in questo corso socket client socket server Endpoint individua@ da: indirizzo IP numero di porta
Socket (dal tutorial Java) A socket is one endpoint of a two- way communica@on link between two programs running on the network A socket is bound to a port number so that the TCP layer can iden@fy the applica@on that data is des@ned to be sent to
Comunicazione client- server 2 1 3
A:esa connessione (lato server) Creare un istanza della classe java.net.serversocket specificando il numero di porta su cui rimanere in ascolto ServerSocket sc = new ServerSocket(4567); Chiamare il metodo accept() che fa in modo che il server rimanga in ascolto di una richiesta di connessione (la porta non deve essere già in uso) Socket s = sc.accept(); Quando il metodo completa la sua esecuzione la connessione col client è stabilita e viene res@tuita un istanza di java.net.socket connessa al client remoto
Aprire connessione (lato client) Aprire un socket specificando l indirizzo IP e numero di porta del server Socket sc = new Socket( 127.0.0.1, 4567); Il numero di porta è compreso fra 1 e 65535 Le porte inferiori a 1024 sono riservate a servizi standard All indirizzo e numero di porta specifica@ ci deve essere in ascolto un processo server Socket ss = serversocket.accept(); Se la connessione ha successo si usano (sia dal lato client che dal lato server) gli stream associa@ al socket per perme:ere la comunicazione tra client e server (e viceversa) Scanner in = new Scanner(sc.getInputStream()); PrintWriter out = new PrintWriter(sc.getOutputStream());
Chiusura connessioni Per chiudere un ServerSocket o un Socket si u@lizza il metodo close() Per ServerSocket, close() fa terminare la accept() con IOExcep@on Per Socket, close() fa terminare le operazioni di le:ura o scri:ura del socket con eccezioni che dipendono dal @po di reader/writer u@lizzato Sia ServerSocket sia Socket hanno un metodo isclosed() che res@tuisce vero se il socket è stato chiuso
EchoServer Si crei un server che acce:a connessioni TCP sulla porta 1337 Una volta acce:ata la connessione il server leggerà ciò che viene scri:o una riga alla volta e ripeterà nella stessa connessione ciò che è stato scri:o Se il server riceve una riga quit chiuderà la connessione e terminerà l esecuzione
EchoServer public class EchoServer { private int port; private ServerSocket serversocket; public EchoServer(int port) { this.port = port; public static void main(string[] args) { EchoServer server = new EchoServer(1337); try { server.startserver(); catch (IOException e) { System.err.println(e.getMessage());
public void startserver() throws IOException { // apro una porta TCP serversocket = new ServerSocket(port); System.out.println("Server socket ready on port: " + port); // resto in attesa di una connessione Socket socket = serversocket.accept(); System.out.println("Received client connection"); // apro gli stream di input e output per leggere e scrivere // nella connessione appena ricevuta Scanner in = new Scanner(socket.getInputStream()); PrintWriter out = new PrintWriter(socket.getOutputStream()); // leggo e scrivo nella connessione finche' non ricevo "quit" while (true) { String line = in.nextline(); if (line.equals("quit")) { break; else { out.println("received: " + line); out.flush(); // chiudo gli stream e il socket System.out.println("Closing sockets"); in.close(); out.close(); socket.close(); serversocket.close(); E fondatamentale richiamare flush() per assicurarsi di scaricare il buffer (il suo contenuto verrà effe<vamente spedito al des@natario)
LineClient Si crei un client che si colleghi, con protocollo TCP, alla porta 1337 dell indirizzo IP 127.0.0.1 Una volta stabilita la connessione il client legge una riga alla volta dallo standard input e invia il testo digitato al server Il client inoltre stampa sullo standard output le risposte o:enute dal server Il client deve terminare quando il server chiude la connessione
LineClient public class LineClient { private String ip; private int port; public LineClient(String ip, int port) { this.ip = ip; this.port = port; public static void main(string[] args) { LineClient client = new LineClient("127.0.0.1", 1337); try { client.startclient(); catch (IOException e) { System.err.println(e.getMessage());
public void startclient() throws IOException { Socket socket = new Socket(ip, port); System.out.println("Connection established"); Scanner socketin = new Scanner(socket.getInputStream()); PrintWriter socketout = new PrintWriter(socket.getOutputStream()); Scanner stdin = new Scanner(System.in); try { while (true) { String inputline = stdin.nextline(); socketout.println(inputline); socketout.flush(); String socketline = socketin.nextline(); System.out.println(socketLine); catch(nosuchelementexception e) { System.out.println("Connection closed"); finally { stdin.close(); socketin.close(); socketout.close(); socket.close();
Archite:ura del server Il server che abbiamo visto acce:a una sola connessione (da un solo client) Un server dovrebbe essere in grado di acce:are connessioni da diversi client e di dialogare con ques@ contemporaneamente Idea: server mul@- thread All interno del processo Server far eseguire l istruzione accept() in un nuovo thread In questo modo è possibile acce:are più client contemporaneamente
EchoServer mul@- thread Spos@amo la logica che ges@sce la comunicazione con il client in una nuova classe ClientHandler che implementa Runnable La classe principale del server si occupa solo di istanziare il ServerSocket, eseguire la accept() e di creare i thread necessari per ges@re le connessioni acce:ate La classe ClientHandler si occupa di ges@re la comunicazione con il client associato al socket assegnato
public class MultiEchoServer { private int port; public MultiEchoServer(int port) { this.port = port; public void startserver() { ExecutorService executor = Executors.newCachedThreadPool(); ServerSocket serversocket; try { serversocket = new ServerSocket(port); catch (IOException e) { System.err.println(e.getMessage()); // porta non disponibile return; System.out.println("Server ready"); while (true) { try { Socket socket = serversocket.accept(); executor.submit(new EchoServerClientHandler(socket)); catch(ioexception e) { break; // entrerei qui se serversocket venisse chiuso executor.shutdown(); public static void main(string[] args) { MultiEchoServer echoserver = new MultiEchoServer(1337); echoserver.startserver(); EchoServer mul@- thread
public class EchoServerClientHandler implements Runnable { private Socket socket; public EchoServerClientHandler(Socket socket) { this.socket = socket; public void run() { try { Scanner in = new Scanner(socket.getInputStream()); PrintWriter out = new PrintWriter(socket.getOutputStream()); // leggo e scrivo nella connessione finche' non ricevo "quit" while (true) { String line = in.nextline(); if (line.equals("quit")) { break; else { out.println("received: " + line); out.flush(); // chiudo gli stream e il socket in.close(); out.close(); socket.close(); catch (IOException e) { System.err.println(e.getMessage()); EchoServer mul@- thread
Serializzazione
Le basi della serializzazione La serializzazione è un processo che trasforma un ogge:o in memoria in uno stream di byte La de- serializzazione è il processo inverso Ricostruisce un ogge:o Java da uno stream di byte e lo riporta nello stesso stato nel quale si trovava quando è stato serializzato 01001001011 00110
Le basi della serializzazione Solo le istanze delle classi possono essere serializzate I @pi primi@vi non possono essere serializza@ Affinché sia possibile serializzare un ogge:o, la sua classe o una delle sue superclassi deve implementare l interfaccia Serializable L interfaccia Serializable è un interfaccia vuota u@lizzata solo come metodo per marcare un ogge:o che può essere serializzato Per serializzare/de- serializzare un ogge:o basta scriverlo dentro un ObjectOutputStream/ObjectInputStream Per scrivere/leggere i @pi primi@vi u@lizzare i metodi della DataOutput/DataInput interface implementa@ da ObjectOutputStream/ObjectInputStream
Serializzazione Le basi della serializzazione FileOutputStream out = new FileOutputStream( save.ser ); ObjectOutputStream oos = new ObjectOutputStream( out ); oos.writeobject( new Date() ); oos.close(); De-Serializzazione FileInputStream in = new FileInputStream( save.ser ); ObjectInputStream ois = new ObjectInputStream( in ); Date d = (Date) ois.readobject(); ois.close();
Metodo di encoding Il metodo di encoding standard di Java Traduce i campi dell ogge:o in uno stream di byte Tipi primi@vi Ogge< non- transient e non- sta@c Per ques@ ul@mi si applica lo stesso processo di serializzazione non-transient obj2 transient obj3 serializable obj1 not serializable non-transient obj3
Metodo di encoding La serializzazione fallisce se almeno uno degli ogge< appartenen@ al grafo dei riferimen@ non- transient non è serializzabile E possibile intervenire su questo comportamento ridefinendo cer@ campi come transient oppure ridefinendo la procedura di serializzazione per un determinato @po di ogge:o non-transient obj2 non-transient obj3 serializable obj1 not serializable non-transient obj3
Metodo di encoding Dato un OutputStream e un insieme di ogge< che devono essere serializza@ Java assegna un iden@fica@vo univoco ad ogni ogge:o Se l ogge:o è già stato serializzato (perché presente come riferimento in uno degli ogge< preceden@) non viene serializzato nuovamente Nell esempio che segue salvando obj1 e obj2 su uno stesso stream, obj3 verrà serializzato una sola volta Ma se serializziamo obj1 e obj2 su due stream diversi, allora quando leggeremo ci ritroveremo con due istanze diverse di obj3 obj1 obj2 obj1 obj2 obj3 obj3 obj3 bis
Serializzazione ed ereditarietà Se una classe serializzabile C ha una super- classe non- serializzabile S, le istanze di C possono comunque essere serializzate se S ha un costru:ore vuoto accessibile dalla so:oclasse Il costru:ore senza argomen@ di S viene invocato automa@camente durante la deserializzazione di C in modo da costruire la parte rela@va ad S nell ogge:o che s@amo deserializzando
Personalizzare la serializzazione Il processo di serializzazione e di deserializzazione può essere personalizzato in due modi: Implementando i metodi writeobject(objectoutputstream oos) e readobject(objectinputstream ois) devono essere dichiara@ come private verranno invoca@ da ObjectOutputStream e ObjectInputStream durante il processo di serializzazione/ deserializzazione mediante reflec@on Implementando l interfaccia Externalizable E quindi i metodi readexternal(objectinput in) e writeexternal(objectoutput out) per leggere e scrivere il proprio stato da/su uno stream
Esempio class SessionDTO implements Serializable { private static final long serialversionuid = 1L; private transient int data; // Stores session data //Session activation time (creation, deserialization) private transient long activationtime; public SessionDTO(int data) { this.data = data; this.activationtime = System.currentTimeMillis(); private void writeobject(objectoutputstream oos) throws IOException { oos.defaultwriteobject(); oos.writeint(data); System.out.println("session serialized"); private void readobject(objectinputstream ois) throws IOException, ClassNotFoundException { ois.defaultreadobject(); data = ois.readint(); activationtime = System.currentTimeMillis(); System.out.println("session deserialized"); public int getdata() { return data; public long getactivationtime() { return activationtime;
RMI Remote Method Invoca@on da materiale di Carlo Ghezzi e Alfredo Mo:a
L idea alla base di tu:a la programmazione distribuita è semplice Un client esegue una determinata richiesta Tale richiesta viaggia lungo la rete verso un determinato server des@natario Il server processa la richiesta e manda indietro la risposta al client per essere analizzata Con i socket però dobbiamo ges@re personalmente il formato dei messaggi e la ges@one della connessione Verso RMI
Verso RMI Quello che cerchiamo è un meccanismo con il quale il programmatore del client esegue una normale chiamata a metodo senza preoccuparsi che c è una rete di mezzo Per farlo la soluzione tecnologica è quella di installare un proxy sul client Il proxy appare al client come un normale ogge:o ma maschera tu:o il processo di u@lizzo della rete per eseguire il metodo sul server Allo stesso modo il programmatore che implementa il servizio non vuole preoccuparsi della ges@one della comunicazione con il client e per questo installa anche lui un proxy sul server Il proxy del server comunica con il proxy del client creando un livello di astrazione al programmatore che non vede la rete
Verso RMI
Le basi di RMI Ogge:o remoto Ogge:o i cui metodi possono essere invoca@ da una Java Virtual Machine diversa da quella in cui l ogge:o risiede Interfaccia Remota Interfaccia che dichiara quali sono i metodi che possono essere invoca@ da una diversa Java Virtual Machine Server Insieme di uno o più ogge< remo@ che, implementando una o più interfacce remote, offrono delle risorse (da@ e/o procedure) a macchine esterne distribuite sulla rete Remote Method Invoca@on (RMI) Invocazione di un metodo presente in una interfaccia remota implementata da un ogge:o remoto La sintassi di una invocazione remota è iden@ca a quella locale
Archite:ura interna Il client colloquia con un proxy locale del server, de:o stub lo stub rappresenta il server sul lato client implementa l'interfaccia del server è capace di fare forward di chiamate di metodi il client ha un riferimento locale all ogge:o stub Esiste anche un proxy del client sul lato server, de:o skeleton è una rappresentazione del client chiama i servizi del server sa come fare forward dei risulta@ lo skeleton ha un riferimento all'ogge:o
Archite:ura interna
RMI Registry Il registro RMI si occupa di fornire al client lo Stub richiesto In fase di registrazione il server potrà fornire un nome canonico per il proprio ogge:o remoto Il client potrà quindi o:enere lo stub u@lizzando il nome che gli è stato assegnato
Scaricare lo Stub Il registro RMI per spedire lo stub al client ha diverse opzioni Se il client e il server risiedono sulla stessa macchina è possibile indicare al client il path locale per lo stub Se il client e il server risiedono su macchine differen@ è necessario u@lizzare un server HTTP per perme:ere al registro di spedire lo stub
Riassumendo Lato client Viene richiesto a un registro RMI lo stub per l invocazione di un determinato ogge:o remoto I parametri in ingresso all invocazione remota vengono serializza@ (tale processo è chiamato marshalling) L invocazione remota viene inviata al server Lato server Il server localizza l ogge:o remoto che deve essere invocato Chiama il metodo desiderato passandogli i parametri ricevu@ dal client Ca:ura il valore di ritorno o le eventuali eccezioni Spedisce allo stub del client un pacche:o contenente i da@ ritorna@ dal metodo
Running Example Vogliamo realizzare un applicazione che ges@sce un magazzino (Warehouse) Il magazzino con@ene un insieme di prodo< e ogni prodo:o è iden@ficato da: Una stringa che iden@fica il prodo:o Un prezzo
Interfaccia condivisa client- server L ogge:o remoto Warehouse definisce l interfaccia tra client e server Estende la Remote interface di Java Richiede la ges@one di eventuali RemoteExcep@on nel caso in cui ci fossero errori di rete
Interfaccia condivisa client- server import java.rmi.*; Interfaccia condivisa dal client e dal server. Entrambi sanno che si tra:a di un interfaccia remota public interface Warehouse extends Remote { double getprice(string description) throws RemoteException; I client saranno costre< a ges@re gli errori che possono sorgere durante l invocazione di un ogge:o remoto
Warehouse Server Il server implementa l interfaccia remota Estende la classe UnicastRemoteObject che rende l ogge:o accessibile da remoto
import java.rmi.*; import java.rmi.server.*; import java.util.*; Warehouse Server public class WarehouseImpl extends UnicastRemoteObject implements Warehouse { private Map<String, Double> prices; public WarehouseImpl() throws RemoteException { prices = new HashMap<String, Double>(); prices.put("blackwell Toaster", 24.95); prices.put("zapxpress Microwave Oven", 49.95); Rende l ogge:o accessibile da remoto public double getprice(string description) throws RemoteException { Double price = prices.get(description); return price == null? 0 : price; Definisco l implementazione lato server dell interfaccia definita precedentemente
Warehouse Server (Alterna@va) import java.rmi.*; import java.rmi.server.*; import java.util.*; Definisco l implementazione lato server dell interfaccia definita precedentemente public class WarehouseImpl implements Warehouse { public WarehouseImpl() throws RemoteException { prices = new HashMap<String, Double>(); prices.put("blackwell Toaster", 24.95); prices.put("zapxpress Microwave Oven", 49.95); UnicastRemoteObject.exportObject(this, 0); Rende l ogge:o accessibile da remoto (soluzione alterna@va) public double getprice(string description) throws RemoteException { Double price = prices.get(description); return price == null? 0 : price; private Map<String, Double> prices;
Pubblicare l ogge:o remoto (1) All avvio il server pubblica sul registro RMI l ogge:o remoto In questo modo il client potrà cercare gli ogge< remo@ disponibili e o:enere un riferimento Il registro RMI deve essere online prima di avviare il server (vedremo come lanciarlo) Di default il registro si trova in localhost sulla porta 1099 WarehouseImpl centralwarehouse = new WarehouseImpl(); Registry registry = LocateRegistry.getRegistry(); registry.bind("central_warehouse", centralwarehouse); S@amo facendo un binding tra il nome central_warehouse e l ogge:o remoto centralwarehouse
Pubblicare l ogge:o remoto (2) import java.rmi.*; import javax.naming.*; public class WarehouseServer { public static void main(string[] args) throws RemoteException, NamingException { System.out.println("Constructing server implementation..."); WarehouseImpl centralwarehouse = new WarehouseImpl(); System.out.println("Binding server implementation to registry..."); Registry registry= LocateRegistry.getRegistry(); registry.bind( central_warehouse", centralwarehouse); System.out.println("Waiting for invocations from clients...");
Note tecniche su bind() Per ragioni di sicurezza un applicazione può associare, deassociare o riassociare un ogge:o a un nome solo se l applicazione gira sullo stesso host del registro Questo evita che client malevoli cambino le informazioni del registro I client possono comunque fare un lookup degli ogge< String[] remoteobjects = registry.list();
Note tecniche sul registro Il registro è anch esso un ogge:o remoto Il metodo bind() appar@ene all interfaccia remota implementata dal registro, infa< void bind(string name, Remote obj) throws RemoteException, AlreadyBoundException, AccessException; I parametri della chiamata dovranno essere serializza@/deserializza@ Il registro scaricherà a run@me la definizione dell interfaccia remota (nel nostro caso Warehouse) per serializzare l ogge:o che gli s@amo passando
Warehouse Client Lato client possiamo o:enere un riferimento al nostro stub con questo codice, purché: Il registro sia online L ogge:o remoto sia stato già pubblicato dal server String remoteobjectname = "central_warehouse"; Warehouse centralwarehouse = (Warehouse) registry.lookup(remoteobjectname); Notare il cas@ng a Warehouse Perché non usiamo WarehouseImpl? Client e Server hanno in comune solo Warehouse, l interfaccia remota Il client non sa nemmeno cosa sia WarehouseImpl
Warehouse Client import java.rmi.*; import java.util.*; import javax.naming.*; public class WarehouseClient { public static void main(string[] args) throws NamingException, RemoteException { Registry registry= LocateRegistry.getRegistry(); System.out.print("RMI registry bindings: "); String[] e = registry.list(); for (int i=0; i<e.legth; i++) System.out.println(e[i]); String remoteobjectname = "central_warehouse"; Warehouse centralwarehouse = (Warehouse) registry.lookup(remoteobjectname); String descr = "Blackwell Toaster"; double price = centralwarehouse.getprice(descr); System.out.println(descr + ": " + price);
Deployare l applicazione RMI Cosa ci serve per far par@re il tu:o Avviamo il server HTTP per perme:ere al registro RMI di recuperare la definizione delle nostre interfacce remote Avviamo il registro RMI Per perme:ere al client di trovare gli ogge< remo@ pubblica@ dal nostro server Avviamo il server All avvio il server registrerà l ogge:o remoto Warehouse Il registro RMI scarica la definizione dell interfaccia remota dal server HTTP Avviamo il client Per vedere finalmente l output della nostra applicazione
Se<ngs (1) Sulla macchina server avremo server/ WarehouseServer.class WarehouseImpl.class download/ Warehouse.class Sulla macchina client avremo client/ WarehouseClient.class
Se<ngs (2) In fase di bind il registro RMI ha quindi bisogno di accedere alla definizione delle interfacce remote I file.class solitamente vengono distribui@ con un normale web server Nel nostro esempio il server deve rendere disponibile il file WareHouse.class Scarichiamo quindi NanoHTTPD web server Un mini- server web la cui implementazione è contenuta tu:a in NanoHTTPD.java Dopo aver compilato il server dovremmo trovarci nella seguente situazione download/ Warehouse.class NanoHTTPD.class
Avviamo l HTTP Server Avviamo il server HTTP in localhost sulla porta 8080 Per verificare che tu:o funzioni proviamo ad accedere all indirizzo h:p://localhost:8080 via browser Tale indirizzo verrà poi u@lizzato dal server per dichiarare la loca@on della codebase rmi property (vedi slide Avviamo il server) $ java download/nanohttpd 8080
Avviamo il registro RMI Su Linux e Osx $ rmiregistry Su Windows $ start rmiregistry In generale è necessario che l eseguibile rmiregistry sia presente nel nostro path rmiregistry viene distribuito con Java, lo trovate quindi nella sua cartella di installazione
Avviamo il Server Andiamo nella directory del server e digi@amo $ java WarehouseServer Cosa vediamo? Constructing server implementation... Binding server implementation to registry... Exception in thread "main" javax.naming.communicationexception [Root exception is java.rmi.serverexception: RemoteException occurred in server thread; nested exception is: java.rmi.unmarshalexception: error unmarshalling arguments; nested exception is: java.lang.classnotfoundexception: Warehouse] at com.sun.jndi.rmi.registry.registrycontext.bind(registrycontext.java:143) at com.sun.jndi.toolkit.url.genericurlcontext.bind(genericurlcontext.java:226) at javax.naming.initialcontext.bind(initialcontext.java:419) at WarehouseServer.main(WarehouseServer.java:13)
Avviamo il Server Andiamo nella directory del server e digi@amo $ java -Djava.rmi.server.codebase=http://localhost:8080/ WarehouseServer L opzione - Djava.rmi.server.codebase server a specificare l URL dove si trovano I file.class che servono al registro RMI Quando eseguite questo comando date un occhiata al terminale dove sta girando NanoHTTPD Vedrete un messaggio che fa vedere la richiesta del registro RMI interessato al file WareHouse.class
Nota tecnica Dopo aver avviato il server la console resta in esecuzione Se guardiamo il codice del server questo potrebbe sembrarci strano Il programma ha solo creato un ogge:o WarehouseImpl e l ha registrato sul RMI registry La spiegazione è che quando creiamo un ogge:o di @po UnicastRemoteObject viene creato un thread separato che @ene vivo il programma Questa funzione è necessaria per assolvere alla funzione di server
Avviamo il client Infine apriamo una quarta console, andiamo nella cartella contenente i file del client e digi@amo $ java WarehouseClient Questo completa la nostra prima applicazione RMI
Loggare la nostra applicazione Viste le immense difficoltà che un applicazione distribuita comporta può essere molto u@le loggare la nostra applicazione Nel caso più semplice basta avviare il server con l opzione Djava.rmi.server.logCalls=true Tu:e le chiamate RMI ed eventuali eccezioni verranno salvate in System.err
Server HTTP un alterna@va Se vogliamo deployare la nostra applicazione senza usare un server HTTP basta avviare il server con la seguente opzione Djava.rmi.server.codebase=file:/path/to/classDir/ Lo slash finale è importante Questa soluzione è o<male nei nostri ambien@ di test dove il registro RMI e il client risiedono effe<vamente sulla stessa macchina del server
Passaggio di ogge< Un ogge:o non- remoto (passato come parametro, o res@tuito come risultato da un metodo remoto) è passato per copia Ovvero serializzato, scri:o nello stream, e ricaricato all altro estremo dello stream, ma come ogge:o differente Modificare quindi un ogge:o ricevuto mediante invocazione remota non ha alcun effe:o sull istanza originale di chi l ha inviato Un ogge:o remoto (già esportato, passato come parametro, o res@tuito come risultato da un metodo remoto) è passato mediante il suo stub Ogge< remo@ non esporta@ non verranno sos@tui@ dal loro stub corrispondente Un ogge:o remoto passato come parametro può solo implementare interfacce remote
Referen@al Integrity Se due riferimen@ ad un ogge:o sono passa@ da una JVM ad un altra u@lizzando una singola chiamata remota, ques@ riferimen@ punteranno allo stesso ogge:o anche nella JVM ricevente All interno di una stessa chiamata remota il sistema RMI man@ene la referen@al integrity tra gli ogge< passa@ come parametro o come valori di ritorno obj1 obj2 Caso (1) remoteobject.remotemethodtwoparameters(obj1, obj2); obj3 Caso (2) remoteobject.remotemethodoneparameter(obj1); remoteobject.remotemethodoneparameter(obj2);
Concorrenza La specifica RMI prevede che il server possa eseguire le invocazioni dei metodi remo@ in modalità mul@- threaded I metodi espos@ a chiamate remote devono essere thread- safe Ges@re la concorrenza è a carico del programmatore
Dynamic Class Loading Mediante il Dynamic Class Loading in Java è possibile caricare a run@me la definizione di classi Java Questa cara:eris@ca è usata con RMI Il client può ricevere da parte del server delle classi sconosciute per le quali è necessario scaricare la definizione corrispondente (file.class) Vediamo con un esempio pra@co un caso d uso di questa tecnologia
Warehouse V2 Vogliamo modificare il nostro proge:o Warehouse affinché cerchi un determinato prodo:o sul server sulla base di una lista di keyword e non più semplicemente grazie alla descrizione del prodo:o Ecco l interfaccia remota aggiornata: import java.rmi.*; import java.util.*; public interface Warehouse extends Remote { double getprice(string description) throws RemoteException; Product getproduct(list<string> keywords) throws RemoteException;
Stru:ura proge:o Il proge:o è diviso in tre compila(on unit Server e Client dipendono entrambi dalla definizione delle interfacce condivise Warehouse è l interfaccia dell ogge:o remoto Product è richiesto da Warehouse server/ WarehouseServer.class WarehouseImpl.class Book.class <<dependency>> download/ Warehouse.class Product.class client/ WarehouseClient.class <<dependency>>
Workflow dell applicazione Il client u@lizzerà una lista di stringhe per cercare un prodo:o Il prodo:o ritornato avrà un riferimento remoto alla Warehouse
La classe Product import java.io.*; public class Product implements Serializable { private String description; private double price; private Warehouse location; public Product(String description, double price) { this.description = description; this.price = price; public String getdescription() { return description; public double getprice(){ return price; public Warehouse getlocation() { return location; public void setlocation(warehouse location) { this.location = location; Questa classe sarà presente sia sul client che sul server Stabilisce cosa è un prodo:o e che funzionalità offre Non è un ogge:o remoto ma è serializzabile Il server dovrà inviare i prodo< al client
La classe Book public class Book extends Product { public Book(String title, String isbn, double price) { super(title, price); this.isbn = isbn; public String getdescription() { return super.getdescription() + " " + isbn; private String isbn;
WarehouseImpl public class WarehouseImpl extends UnicastRemoteObject implements Warehouse { private Map<String, Product> products; private Product backup; public WarehouseImpl(Product backup) throws RemoteException { products = new HashMap<String, Product>(); this.backup = backup; public void add(string keyword, Product product){ product.setlocation(this); products.put(keyword, product); public double getprice(string description) throws RemoteException { for (Product p : products.values()) if (p.getdescription().equals(description)) return p.getprice(); if (backup == null) return 0; else return backup.getprice(description); public Product getproduct(list<string> keywords) throws RemoteException { for (String keyword : keywords){ Product p = products.get(keyword); if (p!= null) return p; return backup;
WarehouseServer import java.rmi.*; import javax.naming.*; public class WarehouseServer { public static void main(string[] args) throws RemoteException, NamingException { System.out.println("Constructing server implementation..."); WarehouseImpl centralwarehouse = new WarehouseImpl( new Book( BackupBook", 123456", 66.99)); centralwarehouse.add("toaster", new Product("Blackwell Toaster", 23.95)); System.out.println("Binding server implementation to registry..."); Registry registry= LocateRegistry.getRegistry(); registry.bind( central_warehouse", centralwarehouse); System.out.println("Waiting for invocations from clients...");
WarehouseClient import java.rmi.*; import java.util.*; import javax.naming.*; import java.util.arraylist; public class WarehouseClient { public static void main(string[] args) throws NamingException, RemoteException { Context namingcontext = new InitialContext(); System.setProperty("java.security.policy", "client.policy"); System.setSecurityManager(new SecurityManager()); System.out.print("RMI registry bindings: "); Enumeration<NameClassPair> e = namingcontext.list("rmi://localhost/"); while (e.hasmoreelements()) System.out.println(e.nextElement().getName()); String url = "rmi://localhost/central_warehouse"; Warehouse centralwarehouse = (Warehouse) namingcontext.lookup(url); ArrayList<String> l=new ArrayList<String>(); l.add("toaster"); Product p=centralwarehouse.getproduct(l); System.out.println("Description: " + p.getdescription()); A run@me possiamo ricevere un book! Ma come fa il client a conoscerlo? Book è stato compilato solamente sul server
Dynamic Class Loading Il nostro server torna dei prodo< sulla base delle keyword che ha inviato il client Tu:avia se il prodo:o non è presente si è deciso di tornare un ogge:o di backup in questo caso un ogge:o di @po Book che estende Product Ma il client non ha idea di cosa sia un Book Book non è stato inserito tra le dipendenze del Client Quando abbiamo compilato il client Book non era richiesto (vedi la stru:ura del proge:o)
Dynamic Class Loading Il server comunica l URL della codebase al client Mediante l a:ributo java.rmi.server.codebase Il client conta:a quindi il server h:p e scarica il file Book.class in modo da poter eseguire il suo codice Tu:o questo avviene in maniera trasparente
Dynamic Class Loading e Security Policy Il Dynamic Class Loading richiede la definizione di alcune policy di sicurezza eseguire un file.class scaricato dall esterno non è sicuramente il massimo Non approfondiremo la scri:ura di una policy Tale policy va indicata nel codice sorgente di chi esegue il caricamento di classi a run@me
WarehouseClient import java.rmi.*; import java.util.*; import javax.naming.*; import java.util.arraylist; public class WarehouseClient { public static void main(string[] args) throws NamingException, RemoteException { Context namingcontext = new InitialContext(); System.setProperty("java.security.policy", "client.policy"); System.setSecurityManager(new SecurityManager()); System.out.print("RMI registry bindings: "); Enumeration<NameClassPair> e = namingcontext.list("rmi://localhost/"); while (e.hasmoreelements()) System.out.println(e.nextElement().getName()); String url = "rmi://localhost/central_warehouse"; Warehouse centralwarehouse = (Warehouse) namingcontext.lookup(url); ArrayList<String> l=new ArrayList<String>(); l.add("toaster"); Product p=centralwarehouse.getproduct(l); System.out.println("Description: " + p.getdescription());
import java.rmi.*; import javax.naming.*; WarehouseServer public class WarehouseServer{ public static void main(string[] args) throws RemoteException, NamingException { System.setProperty("java.security.policy", "server.policy"); System.setSecurityManager(new SecurityManager()); System.out.println("Constructing server implementation..."); WarehouseImpl centralwarehouse = new WarehouseImpl( new Book( BackupBook", 123456", 66.99)); centralwarehouse.add("toaster", new Product("Blackwell Toaster", 23.95)); System.out.println("Binding server implementation to registry..."); Context namingcontext = new InitialContext(); namingcontext.bind("rmi:central_warehouse", centralwarehouse); System.out.println("Waiting for invocations from clients...");