7 Entfernter Methodenaufruf mit RMI In diesem Kapitel stellen wir ein System vor, das die Kommunikation zwischen Objekten, die auf verschiedenen Rechnern erzeugt sind, ermöglicht Im Gegensatz zur XML-RPC-Spezifikation aus Kapitel 6 sind die Datentypen der Methodenaufrufparameter nicht eingeschränkt Neben (fast) beliebigen Objekten kann auch das Verhalten (Bytecode) übertragen werden, so dass flexible Anwendungen realisiert werden können. Allerdings muss sowohl der Client als auch der Server in Java programmiert sein. 7.1 Remote Method Invocation Das Protokoll Remote Method Invocation (RMI) setzt auf TCP/IP auf und verbirgt die Details einer Netzverbindung. RMI hat folgende Eigenschaften, • Mit RMI können Methoden für Objekte aufgerufen werden, Eigenschaften die von einer anderen virtuellen Maschine QVM) erzeugt und von RMI verwaltet werden - in der Regel auf einem anderen Rechner. • Für den Entwickler sieht der entfernte Methodenaufruf wie ein ganz normaler lokaler Aufruf aus . • Entfernt aufrufbare Methoden werden in einem Interface deklariert. Nur hierüber kann der Client mit einem entfernten Objekt kommunizieren. Das Interface stellt einen so genannten Vertrag zwischen Client und Server dar. • Netzspezifischer Code, der die Codierung und übertragung von Aufrufparametern und Rückgabewerten ermöglicht, wird ab Java SE 5 dynamisch zur Laufzeit generiert • Um für den ersten Aufruf einer entfernten Methode eine "Referenz" auf das entfernte Objekt, das diese Methode anbietet, zu erhalten, kann der Client einen so genannten Namensdienst (Registry) nutzen, • RMI bietet Mechanismen sowohl für die Übertragung von Objekten als auch für die Übertragung und das Laden des Bytecodes der zugehörigen Klassen, falls diese lokal nicht vorhanden sind. • RMI ist eine rein Java-basierte Lösung, d. h. Client und Server müssen in Java programmiert sein. Mit Hilfe eines Namensdienstes können Dienste zentral veröffent- Begriffserklätung licht werden, sodass Clients diese über ein Netz finden und nut- Namensdienst zen können. Namensdienste ordnen den Adressen von Ressourcen (beispielsweise entfernten Objekten) eindeutige Namen zu. 7 250 Entfernter Methodenaufruf mit RMI Die Adresse einer Ressource enthält alle Informationen, die ein Client braucht, um mit der Ressource zu kommunizieren. Um eine "Referenz" auf die gewünschte Ressource zu erhalten, übergibt der Client dem Namensdienst den Namen, unter dem die Ressource angemeldet ist. Als Ergebnis erhält er die Referenz, mit der nun eine Verbindung zur Ressource aufgebaut werden kann. Bild 7.1 zeigt den allgemeinen Fall einer mit RMI realisierten Client-Server-Anwendung. Bild 7.1· Rechner B RMI-Anwendung J Rechner A Client ~~ Registry I ~r 0 0 0 Server 0 -, Der Client erhält über die Registry eine "Referenz" auf ein entferntes Objekt. das der Server vorher dort registriert hat. Für dieses Objekt ruft der Client eine Methode auf. Gegebenenfalls kann das RMI-System auch einen Webserver nutzen, um Bytecode für Objekte vom Server zum Client bzw. umgekehrt zu übertragen. Die Zusammenhänge. Begriffe und die erforderlichen Klassen und Methoden zur Entwicklung einer RMI-Anwendung werden nun im Folgenden anhand eines ersten Beispiels schrittweise erläutert. Programm 7.1 Programm 7.1 zeigt die Implementierung eines Echo-Dienstes. Remote Interface Das Remote Interface definiert die Sicht des Client auf das entfernte Objekl. Dieses Interface enthält die Methoden. die für dieses Objekt entfernt aufgerufen werden können. Ein Remote Interface ist von dem Interface ja va. rrri , RelTDte abgeleitet: RelTDte dient dazu, Interfaces zu kennzeichnen, deren Methoden entfernt aufgerufen werden sollen. Remot e muss von allen Interfaces erweitert werden, die entfernte Methoden deklarieren. 7.1 Remote Method Invocation 251 java. rmi . RemoteExceptl on ist von java.l o. IO Exceptlon abgeleitet RemoteException und ist Superklasse einer Reihe von Ausnahmen, die beim Aufruf einer entfernten Methode bei Netz- bzw. ProtokoIIfehlern ausgelöst werden können. Jede Methode eines von Remote abgeleiteten Interfaces (Remote Interface) muss diese Klasse in der throwsKlausel aufführen. lmport java.rmi.Re mote; import java.rm i.Re mote Exception; Remote Interface Echo public interface Echo extends Remote ( String get Echo(String s) throws RemoteException; Jedes Objekt, dessen Klasse ein Remote Interface implementiert, Remote Object ist ein so genanntes entferntes Objekt (Remote Objeci), d.h. es implementiert die vorgeschriebenen entfernt aufrufbaren Methoden. Diese Methoden und die Konstruktoren müssen RemoteExcept i on in der throws-Klausel enthalten. Damit eine Verbindung zwischen Client und Server aufge- Exportieren nommen werden kann und Methoden des Objekts entfernt aufgerufen werden können, muss das entfernte Objekt exportiert und damit remote-fäbig gemacht werden. Dies geschieht durch Ableiten von der Klasse ja va. rmi .server. UnlcastRemoteObject. Bei Erzeugung des entfernten Objekts wird der Konstruktor dieser Superklasse aufgerufen. der das Objekt exportiert. Das exportierte Objekt kann nun über TCP/IP eingehende Nachrichten erhalten. Es hat sich die Konvention durchgesetzt, dass die Implementie- Implementierung rung des Interface Xxx in der Klasse Xxx lmp 1 erfolgt. des Remote Interface. EchoImpl lmport java.rmi.Re moteExceptlon; lmport java.rm i.server.UnlcastRemoteObject; public class Echol mpl extends UnicastRemoteObject implements Echo ( public Echol mpl ( ) throws RemoteException ( ) 7 252 Entfemter Methodenaufruf mit RMI public String getEcho(String s) throws RemoteException { return 5; Stub und Skeleton Die Codierung bzw. Decodierung der Aufrufparameter und Rückgabewerte von Methodenaufrufen und die Übermittlung der Daten zwischen Client und Server wird vom RMI-System und von generierten Klassen, beim Client Stub und beim Server Skeleton genannt, geregelt (siehe Bild 7.2). Bild 72· Aufruf einer entfernten Methode Server Client Server-Programm Client-Programm Ergebnisrückgabe Ergebnis- Methodenaufruf rückgabe Skeleton Stub Aufrufcodierung Ergeb nisdecodierung RMI-System Ergebnisempfang Ergebniscodierung Aufrufdecodierung Netzwerkeode Netzwerkeode Aufrufübertragung Methodenaufruf ( Netz 'I Ergebnisübertragung Aufrufempfang Java Remote Method Protocol (JRMP) Ein Stub-Objekt fungiert als lokaler Stellvertreter (Proxy) des entfemten Objekts. Ein Stub implementiert dasselbe Remote Interface, das auch die Klasse des entfemten Objekts implementiert. Remote Reference Ein Client erhält Zugriff auf ein entfemtes Objekt durch eine so genannte enifernte Referenz (Remote Reference). Diese wird beim Erzeugen des lokalen Stub-Objekts bereitgestellt. Sie kapseIt Informationen, die für den Zugriff auf das entfernte Objekt benötigt werden. Der Client ruft eine Methode des Stub-Objekts auf, die dann für den Methodenaufruf des entfemten Objekts auf der Serverseite sorgt (siehe Bild 7.3). Kurz gesagt wird eine entfernte Referenz durch eine Referenz auf ein entsprechendes Stub-Objekt realisiert. 7.1 Remote Method Invocation 253 Client Bild 73· Remote Reference St ring st r = p.getEcho (s); Aufrufparameter verpacken Rückgabewert auspacken Server Skeleton Stub Remote Reference Aufrufparameier auspacken Rückgebewert verpacken public s t r i no ge t Echo(Stdng s ) t. h rows RemoteException { r e tu r n :3; Das Java Remote Method Protocol ORMP) wird vom Stub genutzt. Generierung von um mit dem Server zu kommunizieren. Es liegt in zwei Versio- Stub und Skeleton nen vor, 1.1 und 1.2. • Version 1.1 kommuniziert mit einem Skeleton (wird genutzt für Clients, die unter ]DK 1.1 laufen), • Version 1.2 benötigt keine Skeleton-Klasse. Das Tool rmic erzeugt Stubs und Skeletons aus den kompilierten Klassen, die die Implementierung des Remote Interface enthalten. • rmic -vl.l erzeugt Stubs und Skeletons für JRMP 1.1, • rmic -vl.2 erzeugt nur Stubs für JRMP 1.2. Ab Java SE 5 werden Stubs zur Laufzeit dynamisch generiert, sodass rmic nicht mehr gebraucht wird. rmic steht aber weiterhin zur Verfügung, um Clients unter früheren Java-Versionen zu unterstützen. Der Aufruf von rmic ohne weitere Option verhält Um Stubs und Skeletons, die mit JRMP sich wie rmic -vl.2 . 1.1 und 1.2 kompatibel sein sollen, zu generieren, muss rmic -vcompat .. . genutzt werden. Die Klasse EchoServer (siehe unten) erzeugt ein entferntes Objekt vom Typ EchoImpl und registriert dieses bei einem Namensdienst (Registry). Das vom JDK bereitgestellte Programm rmiregistry stellt einen Registry einfachen Dienst zur Verfügung, der es dem Client erlaubt, eine erste Referenz auf ein entferntes Objekt als Einstiegspunkt zu 7 254 Entfemter Methodenaufruf mit RMI erhalten. Weitere Referenzen auf andere entfernte Objekte können von hier aus dann anwendungsspezifisch, z. B. als Rückgabewerte von entfernten Methodenaufrufen, geliefert werden. Jeder Eintrag in der Registry besteht aus einem Namen und einer Objektrejerenz. Der Name hat die Form eines URL: //host:port/ Dienstname Bis auf Dienstname können alle Bestandteile entfallen. Der Rechnemame ist in diesem Falllocalhost und die Fortnummer 1099. Soll für die Registry eine andere als die standardmäßig vorgesehene Portnummer 1099 benutzt werden, so muss sie als Aufrufparameter beim Start von rmiregistry angegeben werden. Naming Die Klasse java . rmi . Naml ng wird von Clients und Servern benutzt, um mit der Registry zu kommunizieren. bind static voi d bind(String name. Remote obj) throws ja va. rmi .Alrea dyBJund Exceptl cn, java.net.MalformedU RL Exceptlon, java.rml.Re moteExceptlon registriert einen Eintrag für ein entferntes Objekt. name wird an obj gebunden. Alr eadyBJundExceptl on wird ausgelöst, wenn narre bereits eingetragen ist. Ha1formedURL Exceptl on wird ausgelöst, wenn der Aufbau von narre nicht korrekt ist. rebind statlc vOl d reblnd(Strlng name, Remot e obj) throws ja va.net.MalformedURLException. java.rml.RemoteExceptlon registriert einen Eintrag für ein entferntes Objekt. name wird an obj gebunden. Besteht bereits ein Eintrag zu diesem Namen, so wird der bestehende Eintrag überschrieben. Mal formedURLExcept l on wird ausgelöst, wenn der Aufbau von narre nicht korrekt ist. unbind statlc vOld unblnd(Strlng name) throws ja va.rml . NotBJund Exceptlon , java.net.HalforrredU RL Exceptlon, java.rml.RemoteExceptlon entfernt den Eintrag zu name. Not BJundExceptlon wird ausgelöst, wenn zu name kein Eintrag vorhanden ist. Ha lforrredURL Exceptl on wird ausgelöst, wenn der Aufbau von narre nicht korrekt ist. Diese drei Naml ng-Methoden können nur auf dem Rechner ausgeführt werden. auf dem auch der Namensdienst läuft. 7.1 255 Rernote Method Invocation lmport java.rml .Namlng; lmport java.rmi.Re mote; Der Server: EchoServer public class EchoSer ver ( public static void main(String args[]) throws Except i on ( Remote remote ~ new Echo lmpl (); Nami ng. rebi nd( "echo", re mJte); System.out.print ln("EchoSer ver gestartet ... "); Das RMI-Systern sorgt dafür. dass der Server läuft. auch wenn die Ausführung der ma in-Methode beendet ist. import java.rm i.Naming; public class EchoCl i ent ( public static voi d main(String args[]) throws Exception if (args.length !~ 2) ( Der Client: EchoClient System.err.println("java EchoClient <hast> <text>"); System. exi t (1) ; String host String text args[O]; args[l]; Echo remote (Echo) Naming.lookup("11" + host + "Iecho"); String received = remote.getEcho(text); System.out.println (recei ved); Mittels der Nami ng-Methode lookup erhält der Client das Stub- lookup Objekt zum entfernten Objekt, das unter dem Namen "echo" in der Registry eingetragen ist. static Remot e lookup(String name) throws java.rmi . NotBound Exception, java. net .Mal formedURL Excepti cn, java. rmi . RemoteExcepti on liefert die Referenz auf das Stub-Objekt für das unter name eingetragene entfernte Objekt. Ma lformedURL Excepti on wird ausgelöst. wenn der Aufbau von name nicht korrekt ist. NotBoundException wird ausgelöst, wenn zu name kein Eintrag vorhanden ist. 7 256 Test Entfemter Methodenaufruf mit RMI Aufruf der Registry: start /0 bln rmlreglstry oder start /0 bi n rmlreglstry -J ~Djava.rml.server. logCalls=true (Dies bewirkt, dass der Kommunikationsfluss zwischen Client und Server protokolliert wird.) Aufruf des Servers, start java -cp bln EchoServer Mit der Systemeigenschaft ja va.rml.server.hostnarre kann der Hostname bzw. die IP-Adresse des Servers explizit festgelegt werden. Beispiel (in einer Zeile einzugeben} start java - Dj ava .rmi EchoServer .server.hostna me~ 169 254.53. 192 -cp bin Aufruf des Client: java -cp bi n EchoCl i ent localhost Hal l o Um sich gegen Einschleusen schädlichen Codes in den Client zu schützen, kann die Ausführung von einem Security Manager kontrolliert werden. Hierzu muss eine Policy-Datei mit beispielsweise folgendem Inhalt angelegt werden, policy.txt grant { perml ss l on java. net .SocketPermlss l on "* : 1024-". "connect"; }; Der Client ist dann wie folgt aufzurufen (in einer Zeile einzugeben} java -Djava.securlty .manager -Djava securlty.pollcy=pollcy.txt -cp bin EchoCl ie nt localhost Hal l o Client und Server können natürlich auch auf unterschiedlichen Rechnern installiert und getestet werden. LocateRegistry rmiregistry startet die Registry auf einem Rechner, die dann für mehrere RMI-Server genutzt werden kann. Eine individuelle Registry kann aber auch innerhalb des Servers gestartet werden. 7.1 Remote Method Invocation Dazu steht die Klasse LocateReglstry im Paket 257 ja va.rml. regi st ry zur Verfügung. Die Methode statlc Reglstry createRegistrY(lnt port) throws java.rml . RemoteExceptlon erzeugt eine Registry die auf dem Port port Anfragen akzeptiert. Programm 7.2 demonstriert den Einsatz von Locat eRegls try und Programm 7.2 zeigt, wie die Nummer des Ports, an dem das entfernte Objekt Methodenaufrufe empfängt. vorgegeben werden kann. Hierzu wird der folgende Konstruktor der Klasse UnicastRemoteObject genutzt: protected UnicastRemoteObject(lnt port) throws Remote Exceptlon Gegenüber Programm 7.1 werden Echolmpl . EchoServer und EchoCl i ent wie folgt angepasst lmport java.rmi.Re moteExcept ion; import java.rm i.server.UnicastRe moteObject; Echolmpl public class Echolmpl extends UnicastRemoteObject i mpl ement s Echo ( public Echolmpl (i nt port) throws RemoteException ( super(port) ; public Str ing getEcho(String s) throws RemoteException { return 5; import java.rmi.Naming; import java.rm i.Re mote; import java.rm i.registry LocateRegistry; public class EchoServer ( public static void main(String args[]) throws Except i on int registryPort ~ Integer .parselnt (args [ O]) ; int port ~ Int eger .parsel nt (args [l ] ) ; LocateRegistry.createRegistry(registryPort); Remote remote ~ new Echolmpl (port); Nami ng .rebi nd(" /I;" + regi stryPort + "Iecho". remote); System .out.println("EchoServer gestart et ... "); EchoServer 7 258 EchoClient Entfemter Methodenaufruf mit RMI lmport ja va.rml.Namlng; publ ic class EchoCl i ent ( public stat ic voi d main(String args[J) throws Except i on { if (args.length !- 3) ( System. err.println( "ja va EchoCl l ent <hast> <port> <text>"); System.exit(l); String host - args[OJ; int port - Int eger . parsel nt (args [ l J ) ; Str ing text args[2J ; Echo relTDte (Echo) Naming.lookup( "lI" + host + '"." + port + "Iecho"); Strlng recelved = remote.getEcho(text); Syst em.out.println(received); Bei Einsatz des Security Managers müssen dann nur die Portnummer für die Registry und die Portnummer für das Remote Object freigegeben werden. Beispiel (policy. txi). grant { perml ss l on java net SocketPermlss l on "*: 40000", "connect"; perml ss l on java net SocketPermlss l on "*: 50000", "connect"; }; Aufrufparameter und Rückgabewert Die Aufrufparameter und der Rückgabewert einer entfernten Methode können von einem einfachen Datentyp, Referenzen auf "normale" lokale Objekte oder Referenzen auf entfernte Objekte sein. Übenragungsregeln Für entfernte Methoden gelten die folgenden Übertragungsregeln Werte von einfachem Datentyp (z.B. i nt, doubl e) werden wie bei lokalen Methodenaufrufen hy value übertragen. Lokale Objekte werden serialisiert, übertragen und vom Server deserialisiert. Dafür sorgen Stub und Skeleton. Lokale Objekte werden, anders als beim lokalen Methodenaufruf, als Kopie hy value übertragen. Diese Objekte müssen also das Interface java.lo.Serlallzable implementieren. 7.2 Dienstauskunft 259 Exportierte entfernte Objekte werden by reference übertragen. d. h. es werden die Stub-Objekte, nicht Kopien der Originale übertragen. Die folgende Tabelle fasst die Regeln zusammen, Typ lokale Methode entfernte Methode einfacher Typ by value by value Objekt by reference by value CSerialisierung) entferntes Objekt by reference by remote reference (Stub-Objekt) 7.2 Dienstauskunfl Das folgende Programm gibt eine Liste aller in der Registry gebundenen Namen aus. Die Nami ng-Methode static Str ing[J list(Str ing name) throws java.rml . Remote Exceptlon, ja va.net.MalformedURLException liefert ein Array von Namen, die an entfernte Objekte gebunden sind. name spezifiziert die Registry in der Form / /host: port. lmport java. rml. Naming; Programm 73 public class ListRegistry ( public static void main(String args[J) throws Except i on ( String registryNam e ~ args[OJ; String list[J ~ Naming .list (registryName); for (String name list) ( System.out.println (name); java -cp bin ListRegistry //localhost: 40000 Die Ausgabe hat die Form //host:port/Djenstname Test 7 260 7.3 Entfernter Methodenaufruf mit RMI Transport by reference Beim Aufruf entfernter Methoden werden lokale Objekte als Kopie in serialisierter Form übertragen. Das folgende Beispiel zeigt eine entfernte Methode, die als Rückgabewert eine enifernte Referenz liefert, mit deren Hilfe eine entfernte Methode eines weiteren entfernten Objekts vom Client aufgerufen werden kann. Programm 7.4 Der RMI-Server soll Konten mit den Attributen Kontonummer Gd), PIN und Saldo verwalten, Der Client kann ein neues Konto mit Angabe von Kontonummer und PIN anlegen oder ein bereits unter seiner Kontonummer eingerichtetes Konto öffnen. Für dieses Konto kann er dann einen Betrag einzahlen oder abheben sowie sich seinen Kontostand anzeigen lassen. Für dieses Beispiel nutzen wir eine eigene Except l on-Klasse : KontoException publlc class Konto Exceptlon extends Except l on { public Konto Exception() { } public Konto Exception (Str ing msg) super(msg) ; Remote Interface Konto l mpor t ja va rmi .Remote; lmport ja va rml.RemoteExceptlon; publ ic interface Konto extends Remote ( int getSaldo () throws Remote Excep t ion; vOld add(lnt betrag) thro ws RemoteExceptlon, Konto Exceptlon; Remote Interface KontoManager l mport ja va rmi .Remote; lmport ja va rml.RemoteExceptlon; publ l c l nt erface KontoManager extends Remote { Kont o getKonto(lnt ld, l nt pl n) throws Remote Exceptlon, Konto Exceptlon; Die entfernte Methode getKonto gibt eine entfernte Referenz auf ein Konto-Objekt zurück 7.3 Transport by reference 261 Wird versucht, das Konto zu überziehen, so wird eine Ausnahme Kontolmpl vom Typ KontoExcepti on ausgelöst. import java.rmi . RemoteException; import java.rmi .server.UnicastRemoteObject; public class Kontolmpl extends UnicastRemoteObject implements Konto { private int pin; private int saldo; public Konto lmpl (int pin) throws RemoteException ( this.pin = pin; public int getSaldo() throws RemoteException ( return saldo; public void add (int betrag) throws RemoteException, KontoException { if (saldo + betrag< 0) ( throw new KontoException( "Das Konto kann nicht ueberzogen werden. "); } saldo +~ betrag; public int getPin() return pi n: import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.ut i l .Hashtab.e: public class KontoManagerImpl extends UnicastRemoteObject implements KontoManager { pri vate Hashtable<Integer, Kontolmpl> hashtable; public KontoManagerlmpl() throws RemoteException ( hashtable ~ new Hashtable<Integer, Kontolmpl>(); public Konto getKonto(int id, int pin) throws RemoteException, KontoException KontoManagerlmpl 7 262 Entfernter Methodenaufruf mit RMI KontoImpl konto ~ hashtable.get(id); i f (konto ~~ null) ( konto ~ new KontoImpl(pin); hashtable.put(id. konto); System.out.println("Konto " + id + " wurde elngerlchtet. "); return konto; else ( if (konto.getPin() ~~ pin) return konto; else throw new KontoException ( "PIN ist unguelt i 9 . " ) ; Die Konten werden in einer Hashtable unter der jeweiligen Kontonummer gespeichert. Ist bereits unter der angegebenen Kontonummer ein Konto vorhanden, so wird geprüft, ob die angegebene PIN gültig ist. Ist die PIN nicht korrekt, wird eine Ausnahme ausgelöst. Ist die Kontonummer neu, so wird ein neues Konto eingerichtet. Bei fehlerfreier Verarbeitung wird in beiden Fällen die entfernte Referenz auf ein Konto-Objekt zurückgeliefert. BankServer lmport java rml.Namlng; l mpor t java rmi . Remote; public class BankServer ( public static void main(String args[]) throws Exception ( Remote remote = new KontoManagerImpl(); Naming. rebind("bank". remote); System. out. pri nt1n( "BankServer gestartet ... "); Das "Einstiegsobjekt" vom Typ KontoManagerImpl wird registriert. Beim Aufruf des Client werden als Parameter u. a. Kontonummer und PIN rrtitgegeben. Das Programm ermöglicht die Ausführung verschiedener Aktionen: get +nnn - nnn q Anzeige des Saldos Betrag nnn einzahlen Betrag nnn auszahlen Beenden 7.3 Transport by reference lmport java.io.BufferedReader; import j ava. i 0 . I nputSt reamReader ; import java.rmi.Naming; publie elass BankClient ( publie statie void main(String args[J) throws Exeeption { if (args.length !- 3) ( System.err.println("java BankClient <hast> <id> <p i n>"}: System. exi t (1) ; String host - args[OJ; int id - Integer.parseInt(args[lJ); int pin - Integer.parseInt(args[2J); KontoManager manager - (KontoManager) Naming lookup("/ /" + host + "/bank"); Konto konto - manager.getKonto(id. pin); BufferedReader in - new BufferedReader( new InputStreamReader(System.in)); String input; whi 1e (true) { try { System. out .println("Kommando eingeben (get 1 <zahl »] q);"); input - in.readLine(); if (input -- null 11 input length() -- 0 11 input.equals("q")) break; if (input.equals("get")) { System. out. pri nt 1n("Aktuell er Kontostand; + konto.getSaldo()); else ( int betrag - Integer parseInt(input); konto.add(betrag); } eateh (NumberFormatExeeption e) { eateh (KontoExeeption e) ( System.out.println(e.getMessage()); Mit 1ookup erhält der Client Zugriff auf das entfernte KontoManager-Objekt. Hierfür ruft er die Methode getKonto auf und erhält als Rückgabewert eine entfernte Referenz auf ein Konto- Objekt, für das er dann die entfernten Konto-Methoden aufruft. 263 BankClient 7 264 Test Entfemter Methodenaufruf mit RMI Registry und Server werden wie in Kapitel 7.1 aufgerufen. Aufruf des Client: java -cp bin BankCl i ent localhost 1 1234 Kommando eingeben (get <zahl>1 q): 1000 Kommando eingeben (get <zahl>1 q): get Aktueller Kontostand: 1000 Kommando eingeben (get I <zahl>1 q): -2000 Das Konto kann nlcht ueberzogen werden. Kommando eingeben (get I <zahl>1 q): q 7.4 Mobile Agenten Ein (serialisierbares) Objekt kann auch dann vom Client zum Server transportiert werden, wenn der Server den Bytecode der zugehörigen Klasse noch nicht zur Verfügung hat. Der Code muss dann ebenfalls zur Laufzeit ad hoc übertragen werden. Wir nutzen hier diese Möglichkeit, um Methoden dieser Klasse auf dem Server lokal auszuführen. Derartige Nachrichten entsprechen von ihrem Wesen her einem mobilen Agenten, der im Auftrag eines Client bestimmte Aufgaben auf einem anderen Rechner erledigt und danach zurückkehrt. Programm 7.5 Wir entwickeln einen Server, der beliebige Aufgaben vom Client entgegennimmt, diese ausführt und die Ergebnisse zurückliefert. Das ist z. B. dann hilfreich, wenn der Server auf einer sehr schnellen Maschine läuft und komplexe mathematische Berechnungen ausgeführt werden müssen. Eine Aufgabe kann durch ein beliebiges Objekt repräsentiert werden, dessen Klasse das Interface Agent implementiert: Agent package agent; publlC l nt erf ace Agent ext ends java.lo.Serlallzable { VOl d execute ( ) ; 7.4 Mobile Agenten package agent; lmport java.rmi.Re mote; import java.rmi.Re moteException; 265 Remote Interface SeroerAgent public interface ServerAgent extends Remote { Agent execute(Agent agent) throws Remote Exception; Die entfernte Methode execute des Remote Interface Ser verAgent initiiert den Transport des Agent-Objekts und ruft die AgentMethode execute auf package server; SeroerAgentlmpl import java.rmi.Re moteException: import java.rmi.server.UnicastRe moteObject; import agent.Agent; import agent.ServerAgent; public class ServerAgentlmpl extends UnicastRemoteObject implements ServerAgent { public ServerAgentlmpl () throws Remote Exception { } public Agent execute (Agent agent) throws Remote Exception { agent. execute ( ) ; return agent; package server; import java.rmi.Naming; import java.rmi.Re mote; public class Server ( public static voi d main(String args[]) throws Exception ( Remote remote = new ServerAgent Impl(); Nami ng. rebi nd(" agent", remote); System.out.printl n("Server gestartet ... "); Server 266 7 Entfernter Methodenaufruf mit RMI Die Klasse DemJÄgent implementiert das Interface Agent. Die Methode execute berechnet die Summe der Zahlen von 1 bis zu einer vorgegebenen Zahl n. Diese Klasse soll später über das Netz zum Server transportiert werden. DemoAgent package cII ent; l mport agent.Agent ; publ i c class DemoAgent impl ements Agent ( pr i vat e l nt n: prlvate lnt sum; public DemoAgent (i nt n) ( thlsn=n; public void execute() { for (int i ~ 1; i <~ r: i++) ( sum += i : public int getResult() r eturn sum: Client package cII ent; lmport java.rml.Nam lng; l mport agent.Agent ; lmport agent.ServerAgent; public class Cli ent ( public static void main(String args[J) throws Except i on { String hos t ~ arg5[OJ; ServerAgent remote ~ (ServerAgent ) Nami ng.l ookup ("/ /" + host + "/agent"); Agent demo ~ new DemoAgent (l OO) ; OemoAgent result ~ (OemoAgent) remote.execute(demo); System.out.println(result.getResult()); 7.4 Mobile Agenten 267 Zum Test werden für Client und Server verschiedene Projektverzeichnisse eingerichtet. Die Klasse DemoAgent ist so für den Server über seinen CLASSPATH nicht erreichbar. Um den Bytecode von D2mJAgent herunterladen zu können, nutzt der Server einen HTTP-Server. Hier kann ein beliebiger Webserver genutzt werden, z. B. der Mini-Webserver aus Kapitel 5.4. Für den Start des Client ist der URL für den zu übertragenden Bytecode als Wert der Property ja va.rml.server.codebase anzugeben. Diese Information wird zum Client übertragen, sodass dieser dann die geeignete HTTP-Anfrage stellen kann. Der Server muss einen Security Manager nutzen, da das Laden von Bytecode im Allgemeinen eine unsichere Aktivität ist. Die Policy-Datei hat für unsere Zwecke den folgenden Inhalt grant { permission java.net.SocketPermission "*' 1024-", policy.txt "connect, acce pt " ; permission java.net.SocketPermiss ion "*'8080", "connect" ; }; Unser HTTP-Server wird an die Portnummer 8080 gebunden. Bild 7.4 zeigt die Konfiguration. Insbesondere geht aus der Abbildung hervor. welche Klassen dem Client, welche dem Server lokal zur Verfügung stehen. -------------------1 HTTP-Server 1---Download , De moA g e n t. c L a sa java.rmi .serve r.codebase= h t t p : / /lo c alh os t : 8 0 8 0 / RMI-Client 1 r erno te . execute (demo) RMI-Server I Lokal veriugbare Interfaces und Klassen: agent.Agen t agent.Ser:ve rAgent c l ient. De moAg e n t c lient .Cl ient vom Typ DemoAg ent Lokal verfügbare Interfaces und Klassen: agen t.Agent ag ent. Se rve r Aqen t ae rve r . se r v e r:Ag e ntl mpl se rve r vse r v e r Bild 7.4· Dynamisches Laden einer Klasse 7 268 Test Entfemter Methodenaufruf mit RMI Aufruf von Registry und Server (jeweils in einer Zeile einzugeben} start /0 bin rmiregistry start java -Dj ava .secur i t y.manager -Djava.securi ty .pol icy= policy. txt -cp bin server .Server Aufruf des H'I'Tl-<Servers. start java -cp . . /Prog0504/bin Mini WebServer 8080 bin Aufruf des Client (in einer Zeile einzugeben} java -Ojava.rmi .server.codebase~htt p://loca lhost:8080/ -cp bin client.Client loca lhost 7.5 Callbacks In diesem Kapitel sehen wir, dass ein Client auch zeitweise selbst Dienste anbieten kann. Der Server ruft eine entfernte Methode des Client auf. Polling oder Callback Eine typische Anwendungssituation ist: Der Client will Ereignisse beobachten, die auf dem Server eintreten. Statt nun regelmäßig in bestimmten Abständen eine Anfrage an den Server zu stellen, ob das interessierende Ereignis eingetreten ist oder nicht (Polling), lässt sich der Client vom Server über das Eintreten des Ereignisses informieren (Callback). Damit dieser Callback-Mechanismus funktioniert, muss der Client sich beim Server registrieren (der Server speichert eine entfernte Referenz auf ein Remote-Objekt des Clieni). Dann kann der Server bei Eintreten des Ereignisses eine entfernte Methode des Client aufrufen. Bei dieser Lösung fällt im Vergleich zum PoIling unnötige Rechenzeit und Netzlast weg. Programm 7.6 Zur Veranschaulichung dieses Mechanismus entwickeln wir eine Anwendung (Client und Server), mit der Textnachrichten, die ein so genannter Publisher veröffentlicht, an interessierte Abonnenten (Subscriber) gesendet werden können. Der Server hat die Aufgabe, eine an ihn gerichtete Nachricht sofort an alle Abonnenten weiterzuleiten. Zu diesem Zweck muss er diese "kennen". Textnachrichten werden in ein rvess age-Ob jekt verpackt, das zusätzlich den Zeitpunkt der Veröffentlichung enthält. 7.5 Callbacks publlC class Message implements java.io.Serializable { private lang timestamp; private String text; 269 Message public void setTimestamp(long ti mestamp) ( this.timestamp = timestamp; public long getTimestamp() return timestamp; public void setText(String text) ( this.text ~ text; public String getText() { return text; Das entfernte Objekt des Servers (vom Typ MessageManager) hat drei Methoden: • void setMessageListener(MessageListener listener) meldet einen Subscriber an. • voi d removeMessageListener(MessageListener listener) meldet einen Subscriber ab. • void send(Message msg) sendet eine Nachricht. Das entfernte Objekt des Client (vom Typ Messagelistener) hat die Methode: • voi d onMessage( Message msg) gibt die Nachricht aus. import java.rmi . Remote; import java.rmi . RemoteException; public interface MessageListener extends Remote { void onMessage(Message msg) throws RemoteException; MessageListener 7 270 MessageManager Entfemter Methodenaufruf mit RMI lmport ja va.rml. Remote; lmport ja va.rmi .RemoteException; public interface Mess ageManager extends Remote { voi d setMessageListener(MessageListener listener) throws Remote Exception; voi d remo veMessageListener( MessageListener listener) t hrows Remote Exception; voi d send(Message msg) throws Remote Exception; Bild 7.5 zeigt den Zusammenhang. Bild 7.5· CallbackMechanismus remote Publisher send (msg) MessageManager Li ste der entfemten Referenzen auf die Me s s a g eListe n e r -Obje kte der Subsenbor MessageServer onMessage (msg ) MessageListener setMessage Listene r( Li s t.ener ) Subscriber r e mov eMe s s a g eLi s t e n er ( li s t e n er ) Wir implementieren zunächst den RMI-Server. Das Vector-Objekt 1i steners ist die Liste, die die entfernten Referenzen auf die MessageLi stener-Objekte der Abonnenten verwaltet, Die Methode send ruft für jede in der Liste gespeicherte Referenz die entfernte MessageLi stener-Methode onMessage auf. Wird hierbei (z. B. aufgrund des Abbruchs eines Subscribers) eine Remote Excepti on ausgelöst, so wird die entsprechende Referenz aus der Liste entfernt. Ein Thread gibt alle 5 Sekunden die Anzahl der Abonnenten am Bildschirm aus. MessageManagerImpl l mport java. rmi .RemoteExceptl cn: l mport java. rmi .server. um castRemoteObj ect; l mport java.utll .Vector; publlC class Mess ageManagerI mpl extends UnlcastRemoteObject lmpl ements Mess ageManager { 7.5 Callbacks 271 prl vate Vector<MessageLlstener> llsteners; public MessageManager lmpl() throws RemoteException llsteners = new Vector<MessageLlstener>(); new ControlThread().start(); publlC vOl d setMessageLlstener(MessageLlstener llstener) throws Remote Exceptlon listeners.add(listener); publlC vOld remove MessageLlstener( MessageLlstener llstener) throws Remote Exception ( llsteners.remove(llstener); publlC synchronlzed vOld send( Message msg) t hrows Remote Exception { for (int i ~ listeners.size() I; i >~ 0; i--) { MessageL l stener llstener = II steners. get (l ); try ( llstener.onMessage(msg); catch (RemoteException e) ( llsteners.remove(llstener); private class ControlThread extends Thread ( public void run() { wh i 1e (true) { try ( Thread .s l eep(5000) ; catch (Inter rupted Exception e ) { System. out. pri nt 1n("Anzah1 Subscri bcrs : + llsteners.slze()); lmport ja va.rml .Namlng; lmport java.rml . Remote; publlC class MessageServer { public static void main(String args[]) throws Except i on { MessageSeroer 7 272 Remote remote = Entfernter Methodenaufruf mit RMI new MessageManager Impl(); Naml ng. rebi nd (" rressage", re lTDte); System. out. pri nt l n( "MessageSer ver gestartet ... "); Nun implementieren wir Publisber und Subscriber. Publisher i mpor t java.rmi .Naming; public class Publish er ( public static voi d main(String args[]) throws Except i on i f (args.length !- 2) ( System. err .println("java Publ i s her <hast> <text>"); System.exit(l); String host Stri ng tex t args[O]; args[l] ; MessageManager manager = (MessageManager) Nami ng .lookup("/I" + host + "/message"); Message msg = new Message(); msg.setTi mestamp(System.currentTi meMillis()); msg.setText(text); manager .send(msg); MessageListener- Impl import import import import ja va ja va ja va ja va rmi .RemoteExcepti on; rmi .server.UnicastRemoteObject; text.SimpleDateFormat; util.Date; public class Mess ageLi st enerI mpl extends UnicastRemoteObject implements Mess ageLl st ener { prlv at e SlmpleDateFormat formatter; publ i c Mess ageLi st enerl mpl ( ) throws Remote Exception ( formatt er = new Slmpl eDateFormat( " E. MMM d , yyyy HH ;mm ;ss z"); publlC vOld onMessage(Message msg) t hrows Remote Exceptlon Strlng tl mestamp = formatter.format(new Date(msg .getTimestamp ())) ; 7.5 Callbacks 273 System.out println (timestamp); System.out print ln(msg.getText()); lmport ja va.rml .Namlng; Subscriber public class Subscriber ( public static void main(String args[J) throws Except i on { if (args .length !- 2) ( System.err.prlntln ("ja va Subscrlber <hast> <mlllls>"); System. exi t (1) ; String host - args[OJ; int millis - Int eger .parsel nt (args [l J ) ; Mess ageManager manager = (MessageManager) Namlng .lookup("/I" + host + "/ rressage"); Mess ageL l stener llstener = new MessageLl stener Imp1( ) ; manager.setMessageLlstener(llstener); try ( Thread.sleep(millis) ; catch (Interrupted Exception e) ( manager.remove MessageLlstener(llstener); System .exi t( 0) ; Der Subscriber wird nach einer vorgegebenen Anzahl Millisekunden beendet. Aufruf von Registry und Server: start /0 bin rmiregistry start ja va -cp bin MessageServer Aufruf zweier Abonnenten: start java -cp bin Subscriber localhos t 30000 start java -cp bin Subscriber localhost 30000 Aufruf eines Publishers: java -cp bin Publisher localhost "Das ist ein Test" Test 7 274 7.6 Entfemter Methodenaufruf mit RMI Exkurs: RMI mit 1I0P CORBA CORBA (Common Object Reqnest Broker Arcbitecturei, eine Spezifikation der Object Management Group (OMG), stellt eine Kommunikations- und Dienstinfrastruktur für verteilte objektorientierte Anwendungen bereit Die Methodenaufrufe sind spracbunabbängig, d. h. Client und Server können mit unterschiedlichen Programmiersprachen implementiert werden. Schnittstellen werden mit der speziellen Beschreibungssprache IDL (Interface Definition Language) unabhängig von der Implementierungssprache definiert, Im Vergleich zu RMI ist CORBA jedoch komplizierter und aufwändiger in der Umsetzung. IIOP CORBA nutzt das Kommunikationsprotokoll IIOP (Internet InterORB Protocol) auf der Basis von TCP/IP, Neben dem Protokoll JRMP für eine reine Java-Umgebung (siehe Kapitel 7,1) unterstützt RMI auch IIOP (Java RMI over IIOF) und hat damit Zugang zu anderen CORBA-Anwendungen, Enterprise javaBeans (EjB) der Plattform Java EE kommunizieren in der Regel über RMI!IIOP , Programm 7.7 Im Folgenden wird gezeigt, wie eine einfache verteilte Anwendung auf der Basis von RMI mit IIOP implementiert werden kann, Analog zur RMI-Registry wird hier ein IIOP-fähiger Namensdienst, der vom Object Request Broker Daemon (orbd) angeboten wird, eingesetzt. Interface l mport ja va rmi . Remote; lmport ja va rml.RemoteExceptlon; LagerSeroice publlC lnterface LagerServlce extends Remote { Lager getLager(String id) throws RemoteException; Zu einer Artikelnummer id liefert die Methode getLager das zugehörige Lager-Objekt Die Klasse Lager muss das Interface java. l o. Serl al i zabl e implementieren. Die Klasse Lager lmport ja va lO.Ser ializabl e; import ja va util.Date; publ ic class Lager implements Serializable { prlvate Strlng ld; prlv ate lnt bestand; prlv ate Date datum; 7.6 Exkurs, RMI mit IIOP 275 pub1i c Lager(Stri ng i d , i nt bestand. Dat e datum) ( this.id ~ i d ; this.bestand ~ bestand; this.datum ~ datum; public Str ing get ld() return l d: public voi d set ld(String id) ( this.id~id; pub1i c i nt get Bestand() return bestand; public void setBestand(int bestand) this.bestand ~ bestand ; publ ic Date getDatum() return datum; public voi d setDatum(Date datum) this.datum ~ datum; lmport java.rmi.RemoteException; import java.utll.Date; import java.util. Hashtable; import j avax. rmi . Portabl eRemoteObj ect; public class LagerService lmpl ext ends PortableRemoteObject implements LagerService { pri vate Hashtable<String . Lager> table; public LagerService lmpl() throws RemoteException table ~ new Hashtable<String. Lager>() ; Date datum ~ new Date(); Lager[] list ~ new Lager[] ( newLager(" 471I". 100. dat um). new Lager("4712". 80. datum). new Lager("4713". 55. datum) l: Implementierung LagerServicelmpl 7 276 Entfernter Methodenaufruf mit RMI f or (int i ~ 0; i < 1ist.length; i++) tab le. put(l ist[i] .getld(). 1i st[i]); public Lager getLager(String id) throws RemoteException ( return table.get(id); Da Client und Server über IIOP kommunizieren sollen, muss die Klasse von j avax. rmi . Portab1eRemoteObject abgeleitet werden. Der Server LagerServer lmport javax.namlng Context; lmport javax.namlng. InitialContext; public class LagerSer ver ( publ i c static void ma in(String args[]) throws Except i on LagerSer vice service = new LagerSer vice Impl (); // Referenz im Naming Service mit JND I veröffentli chen ~ new Ini t i al Cont ext () ; Context ctx ctx. rebi nd( "LagerServi ce". servi ce) ; System. out. printl n("LagerServer gestartet ... "); Das Programm erzeugt ein Service-Objekt und meldet dieses beim Namensdienst an. Auf den Namensdienst wird mit Hilfe des API JNDI (Java Naming and Directory Interface) zugegriffen. Hierzu werden das Interface Context und die Klasse Ini t i al Context, beide aus dem Paket j avax. nami ng, genutzt. Der Service wird unter dem Namen "LagerServl ce" eingetragen. Konkrete Angaben zum gewählten Namensdienst werden beim Aufruf des Programms über Properties eingestellt (siehe unten). Der Client LagetClient lmport ja vax.namlng.Context; lmport ja vax.namlng. InltlalContext; lmport ja vax.rml .Portabl eRemoteObject; public class LagerCl ient ( public static voi d main(String args[]) throws Exception if (args.length !~ I ) ( System.err.prlntln("java LagerCllent <td>"): System.exit(l); 7.6 Exkurs, RMI mit IIOP String id ~ 277 args[O]; // Referenz vom Namlng Service mit JNDI erfragen Context ctx ~ new InitialContext(); Object objref ~ ctx.lookup("LagerService"); // Referenz casten LagerService service = (LagerServlee) PortableRemoteObject .narrow(objref, LagerService.class); Lager lager = service getLager(id); if (lager ~~ null) ( System. out. pri nt 1n("Lager ni cht vorhanden"); else ( System.out println(lager getld()); System.out println(lager getßestand()); System.out println(lager getOatum()); Der Client kontaktiert den Namensdienst und erfragt mit lookup das als "LagerServi ce" registrierte Service-Objekt. Die gelieferte Referenz muss mit narrow auf den entsprechenden Interface-Typ "gecastet" werden. Nach Compilierung der Sourcen mit javac müssen mit rmic ein Compiliemng Stub und eine so genannte Tie-Klasse generiert werden: rmic -classpath bin -d bin -iiop LagerServiceImpl Mit Hilfe der zusätzlichen Option - i dl können nach Bedarf auch die IDL-Beschreibungen zum Interface erzeugt werden. Der Namensdienst wird wie folgt auf Port 50000 gestartet start orbd -ORBlniti al Port 50000 Namensdienst starten start java -cp bln LagerServer Start des Servers Die im bin-Verzeichnis abgelegte Datei jndi.properties enthält den Namen der JNDI-Treiberklasse für den Namensdienst sowie dessen URL, java. nami ng.factory. i ni t i al =com. s un .j ndi .cosnami ng.CNCtxF actory java.naming.provider.url=iiop://localhost:50000 7 278 Statt des Client Entfemter Methodenaufruf mit RMI java -cp bin LagerCl ient 4711 Hier werden auch die Angaben in der Datei jndi.properties genutzt. Befinden sich Client und Server auf unterschiedlichen Rechnern im Netz, muss beim Client l ocal hast durch den Hostnamen bzw. die IP-Adresse des Servers ersetzt werden. Zur Ausführung werden die folgenden Bytecode-Dateien benötigt: Lager. cl ass, LagerServl ce cl ass, LagerCll ent . cl ass und LagerSer Vlce_Stub class. 7.7 1. Aufgaben Programmieren Sie einen RMI-Dienst, dessen entfernte Methode Str ing getDayti me() die aktuelle Systemzeit des Servers liefert. 2. Aus einer Bücher-Datenbank sollen zu einer vorgegebenen Buchnummer Angaben zum Buch (Autor und Titel) über SQL abgefragt werden. Entwickeln Sie einen RMI-Dienst (inkl, Server) mit der Methode Buch get Buch (int id) und einen Client, der diese entfemte Methode aufruft. Die Klasse Buch soll die Angaben zum Buch als Attribute mit den entsprechenden set- und get-Methoden enthalten (vgI. auch Aufgabe 5 in Kapitel 4). 3. Erstellen Sie für den RMI-Server aus Kapitel 7.4 einen neuen AgenlRn. der zu einer vorgegebenen Zahl n die Fakultät n! ermittelt. Zur Berechnung kann die Klasse java. math. Blg Integer genutzt werden. 4. Programmieren Sie ein Applet, das als Subscriber im Rahmen von Programm 7.6 Nachrichten in einer Textfläche (JTextArea) anzeigt. Hierzu muss ~ s s a g e L l stener Imp laus Programm 7.6 so angepasst werden, dass die Ausgabe in der Textfläche erscheint. 5. Entwickeln Sie nach der Vorlage in Kapitel 4.5 (Programm 4.3) ein auf RMI basierendes Chat-Programm RMI-Server. RMI-Client (als eigenständige Applikation und als Applet), Der Client soll die gleiche Anwendungsfunktionalität und Benutzungsoberfläche haben wie das in Kapitel 4.5 entwickelte Programm. Nutzen Sie den Callback-Mechanismus. 7.7 Aufgaben 6. Erstellen Sie einen universellen RMI-Server (Multi Ser ver). der mehrere Dienste gleichzeitig anbieten kann. Zur Laufzeit sollen bestehende Dienste beendet und neue Dienste hinzugefügt werden können. Ebenso soll der Server kontrolliert beendet werden können. 279 Der Server soll das Interface Multi ServerManager mit den folgenden Methoden implementieren: voi d shutdo wn () vOl d reconflgure() Die neu hinzuzufügenden Dienste sind in einer Textdatei in folgender Form (Beispiel) gespeichert echo daytime Echol mp1 Oayti me lmpl Jede Zeile entspricht einem Dienst. Sie enthält den Dienstnamen und den Namen der Klasse, die den Dienst erbringt. Diese Datei ist bei der Rekonfiguration einzulesen. Für jeden Eintrag soll mittels Reflection (Nutzung der Cl assMethoden forName und new Instance) ein neues entferntes Objekt erzeugt und bei der Registry angemeldet werden. Des Weiteren ist ein Client MultlServerManagerCll ent zu erstellen. der den Aufruf der Methoden s hutdown und reconf l gure ermöglicht.