Agenti Mobili in Java RMI Un agente è una computazione che agisce per conto di un host presso un altro host. Gli agenti in Java RMI sfruttano due peculiarità di Java: Serializzazione, attraverso cui gli oggetti vengono trasmessi per valore (per deep copy). Polimorfismo, che consente di utilizzare una sottoclasse B laddove è previsto l’utilizzo di una superclasse A (con B<A). Questo perchè la segnatura di una sottoclasse estende quella di una sua superclasse, sebbene l’implementazione dei metodi può essere completamente diversa. In Java RMI, un agente può essere passato dal client al server come parametro di un’invocazione remota, oppure può essere il valore tornato dal server al client attraverso un’invocazione remota. Massimo Merro Programmazione di Rete 194 / 213 In Java RMI è possibile modellare per lo meno tre diverse forme di agenti: Mobile Agents Callbacks Mobile Servers. Massimo Merro Programmazione di Rete 195 / 213 Mobile Agents Un mobile agent è un oggetto locale serializzabile passato attraverso un’invocazione remota. Poichè è serializzabile esso verrà trasmesso per deep copy ed andrà in esecuzione a destinazione. Ovvero, presso il server, se l’agente è stato passato come parametro dal client, presso il client, se l’agente è stato passato come risultato dal server. L’utilizzo di agenti mobili richiede che entrambi, mittente e destinatario, siano a conoscenza di una classe astratta o di un’interfaccia dell’agente mobile. Questa condizione è simile a quella per cui un server ed un client devono condividere l’interfaccia remota del server remoto. Vediamo uno schema dell’entità coinvolte quando si lavora con agenti mobili. In questo esempio, l’agente viene passato dal client al server come parametro di un’invocazione remota. L’agente viene perciò eseguito presso il server: Massimo Merro Programmazione di Rete 196 / 213 public interface MobileAgent { public void act(); } public interface Receiver extends Remote { public void receive(Agent agent); } public class ConcreteAgent implements MobileAgent, Serializable { public void act() { ...... } } public class Sender { public void send(Receiver rcvr) { rcvr.receive(new ConcreteAgent());} ..... } public class ConcreteReceiver extends UnicastRemoteObject implement Receiver { public void receive(MobileAgent agent) { agent.act(); } ....} Massimo Merro Programmazione di Rete 197 / 213 Callback Un callback è un server remoto ed esportato di cui viene passata come parametro una referenza remota, oppure ritornata come risultato di un’invocazione remota. Poichè un server remoto esportato è passato per referenza remota (si passa lo stub), l’agente vero e proprio “rimane dietro” (cioè non si muove) ed agisce alla sorgente. Vediamo un esempio di callback: Massimo Merro Programmazione di Rete 198 / 213 public interface Callback extends Remote { public void callback() throws RemoteException; } public interface Receiver extends Remote { public void receive(Callback agent) throws RemoteException; } public class CallbackClient extends UnicastRemoteObject implements Callback { public void callback () { ....}; public void main(String[] args) { Receiver rcv = ....lookup... rcv.receive(this);} ..... } public class ServerReceiver extends UnicastRemoteObject implements Receiver { public void receive(Callback agent) { agent.callback(); } ...........} Massimo Merro Programmazione di Rete 199 / 213 Notate che il client invoca un metodo remoto del server che a sua volta reinvoca il client, attraverso il metodo callback. Situazioni del genere sono auspicabili quando durante l’esecuzione del metodo remoto del server si rende necessario un’interazione col cliente per ricavare informazioni che possono essere state acquisiti successivamente. Oppure informazioni in possesso del client che all’inizio dell’invocazione del metodo del server non si ritenevano necessarie. Le callback funzionano senza problemi ogni qual volta si lavora con oggetti locali. Se invece lavoriamo con server remoti, allora possono insorgere problemi di stallo. Ad esempio, consideriamo la variante dello schema precedente in cui l’agente callback contiene due metodi run e callback entrambi synchronized. Massimo Merro Programmazione di Rete 200 / 213 public interface Callback extends Remote { public void callback() throws RemoteException; } public interface Receiver extends Remote { public void receive(Callback agent) throws RemoteException; } public class CallbackClient extends UnicastRemoteObject implements Callback { public synchronized void run(Receiver rcv) {........ rcv.receive(this);} public synchronized void callback () { ....}; } public class ServerReceiver extends UnicastRemoteObject implements Receiver { public void receive(Callback agent) { agent.callback(); } public void main (String[] args) Callback cb =...lookup...; cb.run(this); } Massimo Merro Programmazione di Rete 201 / 213 In una tale situazione, se ServerReceiver fosse un oggetto locale, allora il metodo callback verrebbe invocato nello stesso thread del metodo run, senza che ciò provochi problemi di deadlock. Ma visto che ServerReceiver è un server remoto, il sistema RMI invoca il metodo callback all’interno di un nuovo thread. Tale thread si blocca nel tentativo di entrare nel metodo callback visto che il thread originale è ancora in attesa all’interno del metodo synchronized run. In questo modo viene bloccato anche il thread originale sulla run. Quindi abbiamo uno stallo! Massimo Merro Programmazione di Rete 202 / 213 Mobile Server In questo caso l’agente in questione è un server remoto serializzabile ma non esportato nel momento in cui viene passato. In tal caso sappiamo che il server viene passato per deep copy. A destinazione, ci sono due possibilità: Se il mobile server è un UnicastRemoteObject de-esportato, allora al momento della deserializzazione il server viene automaticamente esportato sulla medesima porta dov’era esportato originalmente e funziona come un server remoto presso l’host in cui è stato ricevuto. Se invece il mobile server è un qualche server remoto che non è stato esportato al momento della spedizione. Allora a destinazione bisognerà esportarlo esplicitamente. Massimo Merro Programmazione di Rete 203 / 213 La struttura di un mobile server è, in un certo qual modo simile a quella vista per le callback eccetto per il fatto che l’oggetto CallbackClient non è esportato nel momento in cui si invoca il metodo receive. Vediamo un esempio di Mobile Server del secondo tipo. Massimo Merro Programmazione di Rete 204 / 213 public interface MobileServer extends Remote { public void remoteMethod() throws RemoteException;} public interface Receiver extends Remote { public void receive(Client agent) throws RemoteException;} public class ImplMobileServer extends UnicastRemoteObject implements MobileServer,Serializable{ public void remoteMethod () { ....}; } public class ClientSender { public void main(String[] args) { ImplMobileServer ms = new ImplMobileServer(); unexport(ms,true); Receiver rcv = ...lookup..; rcv.receive(ms);}} public class ServerReceiver extends UnicastRemoteObject implements Receiver { public void receive(MobileServer agent){ agent.remoteMethod();} } Massimo Merro Programmazione di Rete 205 / 213 Si noti che l’invocazione agent.remoteMethod() avviene presso l’host in cui gira ServerReceiver ed in cui è stato esportato un server remoto ImplMobileServer. In pratica, il client lancia un server remoto presso un’altra JVM (su un’altra macchina). Similmente un server può lanciare un’altro server presso il client. Massimo Merro Programmazione di Rete 206 / 213 Proxy In Java RMI uno schema di agente molto diffuso è quello del proxy. Un proxy è un server che viene interposto tra il client ed il vero server. Un proxy ed il suo server sono compatibili dal punto di vista dei tipi, ovvero un proxy può implementare una delle interfacce implementate dal server o estendere una delle classi base del server. Un proxy mantiene sempre una referenza al server con cui comunicherà in maniera trasparente al client, il quale crederà di intergire direttamente col server. I proxy vengono usati ogni qual volta sia necessario introdurre un’entità intermedia di controllo. Vediamo adesso alcune forme di proxies. Massimo Merro Programmazione di Rete 207 / 213 Remote Proxy Un remote proxy è un rappresentante locale di un server remoto (che si trova in un altro host). Uno stub RMI è esattamente un remote proxy. Infatti uno stub contiene (o meglio nasconde al client) il meccanismo attraverso cui è possibile comunicare col server remoto. Uno stub implementa la medesima interfaccia del server remoto. In tal modo il client pensa di interagire direttamente col server remoto. Massimo Merro Programmazione di Rete 208 / 213 Smart Proxy Uno smart proxy può essere usato per nascondere al client la presenza di un’interazione remota. Il client infatti interagisce con un oggetto locale (privo di interfacce remote) senza sapere che questo è in realtà uno smart proxy che delega certe funzionalità ad un server remoto mentre altre sono svolte localmente. Questo è un meccanismo efficiente ed elegante per distribuire l’implementazione tra macchina locale e server remoto, in maniera del tutto trasparente al client. Massimo Merro Programmazione di Rete 209 / 213 Smart Remote Proxy In tale schema il client, invece di interagire con uno stub interagisce con un proxy locale che implementa l’interfaccia remota e che opera da proxy verso lo stub al server remoto. Quando il client richiede una referenza ad un server remoto, invece di ricevere uno stub, riceve per deep copy, uno smart remote proxy. Uno smart remote proxy è un oggetto serializzabile che contiene al suo interno una referenza al server remoto, ovvero uno stub. Quindi, al momento della serializzazione il client riceverà sia il proxy che lo stub remoto; sebbene potrà direttamente interagire solo col primo. Questo schema è utile quando il server vuole che alcune operazioni siano eseguite localmente presso il client mentre altre siano inoltrate al server. Questo schema ha delle ovvie ripercussioni sulla sicurezza visto che l’implementazione del proxy è fornita interamente dal server, ed il client deve fornire permessi adeguati per le operazioni eseguite dal proxy. Massimo Merro Programmazione di Rete 210 / 213 Schemi di programmazione distribuita Client-server Client e server giocano un ruolo diverso. Il client interroga il server richiedendo un servizio. Il server riceve richieste e ritorna risultati. Solitamente è il client ad iniziare la connessione ed anche quello che chiude la connessione quando ha ottenuto quanto desiderato. Client-dispatcher-server In questo caso esiste un dispatcher che media tra client e server. Il client comunica col dispatcher per accedere al server indicandone il nome. Il dispatcher si prende cura di inoltrare la richiesta al server con quel nome. Il sistema RMI implementa tale schema utilizzando stubs e skeletons. Massimo Merro Programmazione di Rete 211 / 213 Peer to Peer In tale schema non vi è distinzione tra client e server. Tutte le entità coinvolte possono fornire e richiedere servizi. Singleton Una classe viene detta Singleton se ne esiste una sua sola istanza. Una classe singleton viene spesso usata per incapsulare un processo che deve essere sequenzializzato tra più utenti (ad esempio, lo spooler di una stampante) oppure per rappresentare una risorsa di cui esiste una sola istanza (ad esempio un file systems o un database). La classe java.lang.Runtime è un esempio di singleton, che rappresenta il sistema a runtime di Java. In generale una classe singleton è priva di stato. I server registrati sul registro RMI sono in generale singleton perchè la medesima istanza viene usata da più clients. Massimo Merro Programmazione di Rete 212 / 213 Abstract Remote In tale schema vi è un classe astratta che implementa un’interfaccia remota: public interface RemoteService extends Remote { .... } public abstract class AbstractService implement RemoteServer { public AbstractService() throws RemoteException {...} } public class ConcreteService extends AbstractService {....... } Una classe astratta non deve fornire il codice dell’implementazione, eccezion fatta per il costruttore. In particolare non deve reiterare la dichiarazione dei metodi remoti. Una classe astratta remota può essere compilata da rmic. Le implementazioni concrete non vengono toccate da rmic. In fase di sviluppo posso cambiare l’implementazione del server senza dover ricompilare con rmic. La compilazione rmic dovrà essere rieseguita solo se si cambia l’interfaccia remota. Massimo Merro Programmazione di Rete 213 / 213