RMI Remote Method Invocation - Luciano Baresi · 2020. 10. 27. · Verso RMI •Quello che...
Transcript of RMI Remote Method Invocation - Luciano Baresi · 2020. 10. 27. · Verso RMI •Quello che...
RMIRemote Method Invocation
da materiale diCarlo Ghezzi e Alfredo Motta
Verso RMI
• Un client esegue una determinata richiesta• Tale richiesta viaggia lungo la rete verso un
determinato server destinatario• Il server processa la richiesta e manda indietro la
risposta al client per essere analizzata• Con i socket però dobbiamo gestire il formato dei
messaggi e la gestione della connessione
Verso RMI• Quello che cerchiamo è un meccanismo con il
quale il programmatore del client esegua 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 oggetto– ma maschera tutto il processo di utilizzo della rete per
eseguire il metodo sul server
• Allo stesso modo il programmatore che implementa il servizio non vuole preoccuparsi della gestione della comunicazione con il client – e per questo installa anche lui un proxy sul server
Verso RMI…
Le basi di RMI
• Oggetto remoto– Oggetto i cui metodi possono essere invocati da una Java
Virtual Machine diversa da quella in cui l’oggetto risiede• Interfaccia Remota
– Interfaccia che dichiara quali sono i metodi che possono essere invocati da una diversa Java Virtual Machine
• Server– Insieme di uno o più oggetti remoti che, implementando
una o più interfacce remote, offrono delle risorse (dati e/o procedure) a macchine esterne distribuite sulla rete
• Remote Method Invocation (RMI)– Invocazione di un metodo presente in una interfaccia
remota implementata da un oggetto remoto– La sintassi di una invocazione remota è identica a quella
locale
Architettura interna
• Il client colloquia con un proxy locale del server detto stub– Lo stub rappresenta il server lato client– Implementa l'interfaccia del server– È capace di fare forward delle chiamate ai suoi metodi
• Esiste anche un proxy del client sul lato server, detto skeleton– È una rappresentazione del client– Chiama i servizi del server– Sa come fare forward dei risultati
• Da Java 2 gli skeleton non sono più necessari
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 oggetto remoto– Il client potrà quindi ottenere lo stub utilizzando 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 differenti è
necessario utilizzare un server HTTP per permettere al registro di spedire lo stub
Riassumendo…
• Lato client– Viene richiesto a un registro RMI lo stub per l’invocazione
di un determinato oggetto remoto– I parametri in ingresso all’invocazione remota vengono
serializzati (tale processo è chiamato marshalling)– L’invocazione remota viene inviata al server
• Lato server– Il server localizza l’oggetto remoto che deve essere
invocato– Chiama il metodo desiderato passandogli i parametri
ricevuti dal client– Cattura il valore di ritorno o le eventuali eccezioni– Spedisce allo stub del client un pacchetto contenente i dati
ritornati dal metodo
Esempio
• Vogliamo realizzare un’applicazione che gestisce un magazzino Il magazzino contiene un insieme di prodotti e ogni prodotto è identificato da– Una stringa che identifica il prodotto– Un prezzo
Interfaccia condivisa client-server
• Interfaccia condivisa dal client e dal server, entrambi sanno che si tratta di un interfaccia remota
• I client saranno costretti a gestire gli errori che possono sorgere durante l’invocazione di un oggetto remoto
public interface WarehouseI extends Remote {double getPrice(String description) throws RemoteException;
}
Warehouse Server
• Il server implementa l’interfaccia remota• Estende la classe UnicastRemoteObject che rende
l’oggetto accessibile da remoto
Warehouse
public class Warehouse extends UnicastRemoteObject implements WarehouseI {private static final long serialVersionUID = 1L; //defaultprivate Map<String, Double> prices;
public Warehouse() throws RemoteException {prices = new HashMap<String, Double>();prices.put("Blackwell Toaster", 24.95);prices.put("ZapXpress Microwave Oven", 49.95);
}
public double getPrice(String description) throws RemoteException {Double price = prices.get(description);return price == null ? 0 : price;
}}
Pubblicare l’oggetto remoto
• All’avvio il server pubblica sul registro RMI l’oggetto remoto
• In questo modo il client potrà cercare gli oggettiremoti disponibili e ottenere un riferimento
• Il registro RMI deve essere online prima di avviare il server– Di default il registro si trova su localhost alla porta 1099– Stiamo facendo un binding tra il nome
“central_warehouse” e l’oggetto remoto centralWarehouse
Codice completo
public class WarehouseServer {public static void main(String[] args) throws RemoteException, NamingException {
System.out.println("Constructing server implementation");try {
Warehouse centralWarehouse = new Warehouse();System.out.println("Binding server impl. to registry");Registry registry= LocateRegistry.getRegistry();registry.bind("centralWarehouse", centralWarehouse);
}catch (Exception e) {
System.err.println("WarehouseServer exception");e.printStackTrace();
}System.out.println("Waiting for clients");
}}
Codice completo (II)
public class WarehouseServer {public static void main(String[] args) throws RemoteException, NamingException {
System.out.println("Constructing server implementation");try {
Warehouse centralWarehouse = new Warehouse();System.out.println("Binding server impl. to registry");Registry registry= LocateRegistry.createRegistry(1099);registry.bind("centralWarehouse", centralWarehouse);
}catch (Exception e) {
System.err.println("WarehouseServer exception");e.printStackTrace();
}System.out.println("Waiting for clients");
}}
module-info.java
module RMIexample {requires java.rmi;requires java.naming;exports shared to java.rmi;
}
bind()
• Per ragioni di sicurezza un’applicazione può associare, deassociare o riassociare un oggetto 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 richiedere tutti gli oggetti remoti
String[] remoteObjects = registry.list();
getRegistry
• getRegistry() ritorna un riferimento all’oggetto remoto Registry su local host e alla porta 1099
• getRegistry(int port) ritorna un riferimento all’oggetto remoto Registry su local host e alla porta port
• getRegistry(String host) ritorna un riferimento all’oggetto remoto Registry su host e alla porta 1099
• getRegistry(String host, int port) ritorna un riferimento all’oggetto remoto Registry su host e alla porta port
Il registro
• Il registro è anch’esso un oggetto remoto• Il metodo bind() appartiene all’interfaccia remota
implementata dal registro, • I parametri della chiamata dovranno essere
serializzati/deserializzati
Warehouse Client
• Lato client possiamo ottenere un riferimento allo stub purché– Il registro sia online– L’oggetto remoto sia stato già pubblicato dal server
• Notare il cast a WarehouseI– Perché non usiamo Warehouse?– Client e Server hanno in comune solo l’interfaccia
remota WarehouseI– Il client non sa nemmeno cosa sia Warehouse
Codice
public class WarehouseClient {public static void main(String[] args)throws NamingException, RemoteException, NotBoundException {Registry registry= LocateRegistry.getRegistry();System.out.print("RMI registry bindings: ");String[] e = registry.list();
for (int i=0; i<e.length; i++)System.out.println(e[i]);
String remoteObjectName = "centralWarehouse";WarehouseI centralWarehouse = (WarehouseI) registry.lookup(remoteObjectName);String descr = "Blackwell Toaster";double price = centralWarehouse.getPrice(descr);System.out.println(descr + ": " + price);
}}
Esecuzione
Registry (tecnicamente) remoto
RMIInterface.java
public interface RMIInterface extends Remote {public String helloTo(String name) throws RemoteException;
}
ServerOperation.javapublic class ServerOperation extends UnicastRemoteObject implements RMIInterface {private static final long serialVersionUID = 1L;
protected ServerOperation() throws RemoteException {super();
}@Overridepublic String helloTo(String name) throws RemoteException{
System.err.println(name + " is trying to contact!");return "Server says hello to " + name;
}
public static void main(String[] args){try {
Naming.rebind("//localhost/MyServer", new ServerOperation()); System.err.println("Server ready");
} catch (Exception e) {System.err.println("Server exception: " + e.toString());e.printStackTrace();
}}
}
ClientOperation.java
public class ClientOperation {
private static RMIInterface lookUp;
public static void main(String[] args)throws MalformedURLException, RemoteException, NotBoundException {
lookUp = (RMIInterface) Naming.lookup("//localhost/MyServer");Scanner in = new Scanner(System.in);System.out.println("What is your name?");String txt = in.nextLine();in.close();
String response = lookUp.helloTo(txt);System.out.println(response);
}}
rmiregistry
Esecuzione
Passaggio di oggetti
• Un oggetto non-remoto, passato come parametro, o restituito come risultato da un metodo remoto, è sempre passato per copia– Ovvero serializzato, scritto nello stream, e ricaricato
all’altro estremo dello stream, ma come un oggetto differente
– Modificare quindi un oggetto ricevuto mediante invocazione remota non ha alcun effetto sull’istanza originale di chi l’ha inviato
• Un oggetto remoto, già esportato, passato come parametro, o restituito come risultato da un metodo remoto è passato mediante il suo stub– Un oggetto remoto passato come parametro può solo
implementare interfacce remote
Referential Integrity
• Se due riferimenti ad un oggetto sono passati da una JVM ad un’altra utilizzando una singola chiamata remota, questi riferimenti punteranno allo stesso oggetto anche nella JVM ricevente
• All’interno di una stessa chiamata remota il sistema RMI mantiene la referential integrity tra gli oggetti passati come parametro o come valori di ritorno
Concorrenza
• La specifica RMI prevede che il server possa eseguire le invocazioni dei metodi remoti in modalità multi-threaded– I metodi esposti a chiamate remote devono essere
thread-safe– Gestire la concorrenza è a carico del programmatore
Dynamic Class Loading
• Mediante il Dynamic Class Loading in Java è possibile caricare a runtime la definizione di classi Java
• Questa caratteristica è usata da RMI – Il client può ricevere da parte del server delle classi
sconosciute per le quali è necessario scaricare la definizione corrispondente (file .class)
Warehouse evoluto
• Vogliamo modificare il nostro progetto Warehouse affinché cerchi un determinato prodotto sul server sulla base di una lista di keyword e non più semplicemente grazie alla descrizione del prodotto
• Vogliamo anche usare il dynamic class loading
WarehouseI
public interface WarehouseI extends Remote {double getPrice(String description) throws RemoteException;Product getProduct(List<String> keywords) throws RemoteException;
}
Dynamic Class Loading
• Il nostro server torna dei prodotti sulla base delle keyword che ha inviato il client
• Tuttavia se il prodotto non è presente si è deciso di tornare un oggetto di backup– in questo caso un oggetto di tipo 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 struttura del progetto)
Dynamic Class Loading
• Il server comunica l’URL della codebase al client• Il client contatta quindi il server http e scarica il
file Book.class in modo da poter eseguire il suo codice
• Il Dynamic Class Loading richiede la definizione di alcune policy di sicurezza
• Non approfondiremo la scrittura di una policy
Struttura progetto
server.policy
grant codeBase "file:/-" {permission java.security.AllPermission;
};
Product
public class Product implements Serializable {private static final long serialVersionUID = 1L;
private String description;private double price;private WarehouseI location;
public Product(String description, double price) {this.description = description;this.price = price;
}public String getDescription() {return description;
}public double getPrice(){return price;
}public WarehouseI getLocation() {return location;
}public void setLocation(WarehouseI location) {this.location = location;
}}
Book
public class Book extends Product {private static final long serialVersionUID = 1L;private String isbn;
public Book(String title, String isbn, double price) {super(title, price);this.isbn = isbn;
}
public String getDescription() {return super.getDescription() + " " + isbn;
}}
Warehouse
public class Warehouse extends UnicastRemoteObject implements WarehouseI {private static final long serialVersionUID = 1L;
private Map<String, Product> products;private Product backup;
public Warehouse(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();
}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
public class WarehouseServer{public static void main(String[] args) throws RemoteException, NamingException{
System.setProperty("java.security.policy", "file:./server/server.policy");System.setSecurityManager(new SecurityManager());
System.out.println("Constructing server implementation");Warehouse centralWarehouse = new Warehouse(new Book("book", "123456", 66.99));centralWarehouse.add("toaster", new Product("Blackwell Toaster", 23.95));
try {System.out.println("Binding server impl. to registry");Registry registry= LocateRegistry.createRegistry(1099);registry.bind("centralWarehouse", centralWarehouse);
}catch (Exception e) {
System.err.println("WarehouseServer exception");e.printStackTrace();
}System.out.println("Waiting for clients");
}}
WarehouseClient
public class WarehouseClient {public static void main(String[] args)throws NamingException, RemoteException, NotBoundException {
System.setProperty("java.security.policy", "file:./client/client.policy");System.setSecurityManager(new SecurityManager());
Registry registry= LocateRegistry.getRegistry();System.out.print("RMI registry bindings: ");String[] e = registry.list();
for (int i=0; i<e.length; i++)System.out.println(e[i]);
String remoteObjectName = "centralWarehouse";WarehouseI centralWarehouse = (WarehouseI) registry.lookup(remoteObjectName);
ArrayList<String> l=new ArrayList<String>();l.add("milk");Product p=centralWarehouse.getProduct(l);System.out.println("Description: " + p.getDescription());
}}