Netzprogrammierung: Java RMI - Remote Method Invocation (Teil 1) Robert Tolksdorf und Peter Löhr Überblick 1. Fernaufrufbare Objekte 2. Das Objektverzeichnis rmiregistry 3. Parametersemantik Dokumentation RMI: http://java.sun.com/javase/6/docs/platform/rmi/spec/rmiTOC.html http://java.sun.com/docs/books/tutorial/rmi/ Robert Tolksdorf und Peter Löhr 2 Fernaufrufbare Objekte Robert Tolksdorf und Peter Löhr Ein hypothetischer Ansatz Einfaches Beispiel - Modul im Gewand einer Klasse : public class Counter { static int c = 0; public static int inc(int i) public static int value() } { return c += i; } { return c; } Testprogramm: public class Inc { public static void main(String[] arg) { int i = Counter.inc(Integer.parseInt(arg[0])); System.out.println("counter is " + i); } } Robert Tolksdorf und Peter Löhr 4 Ein hypothetischer Ansatz Szenario für hypothetisches (!) verteiltes Java mit hochgradiger Zugriffs- und Ortsabstraktion für Klassen: 1. Generator erzeugt für Counter einen Treiber, der mit Counter und einem Adapter CounterMain ein lauffähiges Programm ergibt; dieses wird auf Rechner X gestartet, wählt einen Port p und macht diesen bekannt. 2. Generator erzeugt - mit X und p als Parametern für Counter eine gleichnamige Vertreter-Klasse Counter, die mit Inc ein lauffähiges Programm ergibt. 3. Beim Ablauf von Inc wird Counter.inc als Fernaufruf ausgeführt. Robert Tolksdorf und Peter Löhr 5 Ein hypothetischer Ansatz 4. Counter ist öffentlich, die Lebensdauer ist unabhängig von den Lebensdauern der Aufrufer. --> Achtung Nichtsequentialität: • Wenn mehrere Aufrufer tätig sind, sind überlappende Ausführungen von inc möglich (jedenfalls sollte das Fernaufrufsystem dies unterstützen - mit Threading). • Daher muss beim Aufgerufenen eine geeignete Ausschlußsynchronisation vorgenommen werden (die hier der Einfachheit halber weggelassen wurde). Robert Tolksdorf und Peter Löhr 6 Ein hypothetischer Ansatz Hinzunahme von Objekten: public class Counter { static int c = 0; public static int inc(int i) { return c += i; } public static int value() { return c; } int cc = 0; public int add(int i) { return cc += i; } } public class Inc { public static void main(String[] arg) { Counter counter = new Counter(); int i = counter.add(Integer.parseInt(arg[0])); System.out.println("counter is " + i); } } Robert Tolksdorf und Peter Löhr 7 Ein hypothetischer Ansatz new Counter(): Vertreter-Klasse hat einen Konstruktor, der über eine geeignete Nachricht den Treiber zur Erzeugung des eigentlichen Counter-Objekts veranlasst. Als Ergebnis dieser Aktion wird ein Fernverweis auf das Objekt zurückgeschickt und als new Counter() abgeliefert. Robert Tolksdorf und Peter Löhr 8 Ein hypothetischer Ansatz Hinzunahme von Schnittstellen: interface Counter { int add(int i); int value(); } public class CounterA implements Counter { int cc = 0; public int add(int i) { return cc += i; } public int value() { return cc; } } public class CounterB implements Counter { int cc = 0; public int add(int i) { cc += i; return cc; } public int value() { return cc; } } Robert Tolksdorf und Peter Löhr 9 Ein hypothetischer Ansatz public class Inc { public static void main(String[] arg) { Counter[] counters = new Counter[10]; ..... //initialize array ..... //with local and/or remote references for(Counter c : counters) { c.add(.....); } ..... } } Probleme: 1. statische Bindung einer Klasse an einen Rechner 2. lokale und entfernte Objekte können nicht von ein und derselben Klasse sein Robert Tolksdorf und Peter Löhr 10 Elemente der RMI Die Lösung der RMI-Autoren bei Sun Microsystems: 1. Keine Fernaufrufe von statischen Methoden, d.h. nicht Klassen, sondern nur Objekte sind fernaufrufbar. 2. Keine Fernerzeugung von Objekten mit new. 3. Original-Klasse und Vertreter-Klasse haben verschiedene Namen, aber implementieren die gleiche Schnittstelle. 4. Diese Schnittstelle muss von java.rmi.Remote erben und Ausnahmen vom Typ java.rmi.RemoteException extends java.io.IOException vereinbaren (für verteilungsbedingte Fehler). Robert Tolksdorf und Peter Löhr 11 Elemente der RMI import java.rmi.*; interface Counter extends Remote { int inc(int i) throws RemoteException; int value() throws RemoteException; } public class CounterImpl implements Counter { int c = 0; public int inc(int i) { return c += i; } // throws entbehrlich public int value() { return c; } // throws entbehrlich } Achtung - nichtsequentielle Benutzung solcher Objekte (hier wiederum ignoriert) Robert Tolksdorf und Peter Löhr 12 Bemerkungen • Remote ist ein „marker interface“, das vom Fernaufrufsystem gefordert wird, ebenso wie die throwsKlauseln. Ausnahmen vom Typ RemoteException werden durch verteilungsbedingte Fehler verursacht. • Ein Objekt der Klasse CounterImpl ist sowohl fernaufrufbar als auch lokal aufrufbar, d.h. am Ort seiner Erzeugung. • Um einen Fernaufruf durchführen zu können, muss sich der Aufrufer einen Fernverweis auf das Objekt beschaffen. • Das kann er aber nur durch einen Fernaufruf ... --> Henne-und-Ei-Problem? (siehe unten, „rmiregistry“) Robert Tolksdorf und Peter Löhr 13 Stub-Erzeugung Historisch: Vertreter-Generator rmic (RMI compiler) erzeugt zu einer vorgegebenen Klasse (!) einen Vertreter, genannt stub, und einen Treiber, genannt skeleton. $ rmic CounterImpl erzeugt im aktuellen Verzeichnis die Dateien CounterImpl_Stub.class und CounterImpl_Skel.class Aktuell: Vertreter und Treiber werden zur Laufzeit erzeugt, wenn der Erzeuger eines Objekts dieses dem Fernaufrufsystem als fernaufrufbar bekannt macht. Robert Tolksdorf und Peter Löhr 14 Benutzung eines entfernt vorhandenen Objekts: public class Inc { public static void main(String[] arg) { Counter x = .......... // Fernverweis beschaffen int i = x.inc(Integer.parseInt(arg[0])); System.out.println("counter is " + i); } } Die Lebensdauer des Objekts ist hier unabhängig von den Lebensdauern der aufrufenden Prozesse! (Nichtsequentialität!) Wie entsteht das Objekt und wie erhält man den Fernverweis? Robert Tolksdorf und Peter Löhr 15 Das Objektverzeichnis rmiregistry Robert Tolksdorf und Peter Löhr rmiregistry • Das rmiregistry (dt. Register) ist ein Netzdienst, der ein Objektverzeichnis verwaltet. Typischer Start mit rmiregistry <port> & (und zu gegebener Zeit kill !) • Dieses Verzeichnis bildet mnemonische Objektnamen auf Fernverweise ab. Zu den angebotenen Operationen gehören: • bind: fügt einen neuen Eintrag hinzu • lookup: liefert zu einem Namen den zugehörigen Fernverweis • Ein rmiregistry wird mit host und port identifiziert und ist über geeignete Bibliotheksklassen benutzbar. • Ein rmiregistry verwaltet ausschließlich lokale Objekte. Robert Tolksdorf und Peter Löhr 17 rmiregistry mein.rechner.de dein.rechner.de JVM1 rmiregistry mein.rechner.de JVM1 JVM2 dein.rechner.de lookup rmiregistry mein.rechner.de JVM1 bind JVM2 dein.rechner.de doit Robert Tolksdorf und Peter Löhr rmiregistry JVM2 18 rmiregistry import java.rmi.server.UnicastRemoteObject; Statische Methode exportObject(x,port) macht das Objekt x der RMI-Verwaltung als fernaufrufbar bekannt, erzeugt einen unsichtbaren Treiber und einen Vertreter und liefert einen Verweis auf den Vertreter. Der zu verwendende Port kann angegeben werden; bei Angabe von 0 wird irgendein Port gewählt. import java.rmi.registry.LocateRegistry; Statische Methode getRegistry(host,port) liefert Fernverweis auf ein (lokales oder entferntes) rmiregistry angesprochen werden kann. import java.rmi.registry.Registry; Schnittstelle mit Methoden bind, lookup, ..... Robert Tolksdorf und Peter Löhr 19 vo rmiregistry import java.rmi.*; interface Registry extends Remote { void bind(String name, Remote object) throws AlreadyBoundException, RemoteException, AccessException; Remote lookup(String name) throws RemoteException NotBoundException, AccessException; ... } Robert Tolksdorf und Peter Löhr // weitere Operationen: rebind, unbind, list 20 Registrieren im rmiregistry import java.rmi.server.UnicastRemoteObject; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class CounterImpl implements Counter { int c = 0; public int inc(int i) { return c += i; } public int value() { return c; } public static void main(String[] arg) throws Exception { Counter x = new CounterImpl(); Counter stub = (Counter) UnicastRemoteObject. exportObject(x, 0); Registry registry = LocateRegistry.getRegistry(); registry.bind("mycounter", stub); // main thread dies; hidden thread waits for invocation } } Robert Tolksdorf und Peter Löhr 21 Suchen im rmiregistry import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class Inc { public static void main(String[] arg) throws Exception { Registry registry = LocateRegistry.getRegistry(); Counter x = (Counter) registry.lookup("mycounter"); int i = x.inc(Integer.parseInt(arg[0])); System.out.println("counter is " + i); } } Typanpassung - kann scheitern! Robert Tolksdorf und Peter Löhr 22 Lokaler Test: $ javac Counter.java $ javac CounterImpl.java $ javac Inc.java $ java CounterImpl Exception in thread "main" java.rmi.ConnectException: Connection refused to host: 192.168.2.20 rmiregistry läuft nicht! $ rmiregistry & [1] 8774 $ Robert Tolksdorf und Peter Löhr 23 $ java CounterImpl & [2] 8777 $ java Inc 10 counter is 10 $ java Inc 10 counter is 20 $ java Inc -20 counter is 0 $ kill 8777 CounterImpl-Prozess löschen $ kill 8774 rmiregistry-Prozess löschen Robert Tolksdorf und Peter Löhr 24 Test im Netz: lohr@troll: rmiregistry & [1] 8774 lohr@troll: java CounterImpl & [2] 8777 lohr@troll: Klasse Inc (S. 21) ändern: ....getRegistry("troll.mi.fu-berlin.de“); kpl@human: java Inc 10 counter is 10 kpl@human: Robert Tolksdorf und Peter Löhr 25 Parametersemantik Robert Tolksdorf und Peter Löhr Parameterübergabe • Zur Erinnerung: Java kennt nur einen Parametermechanismus - Wertparameter (call by value) • ! Das ist gut für die Verteilung (vgl. 04-Fernaufrufe, S.27/28) ! • ! Aber Achtung bei Parametern mit Verweistyp: • Wenn die Klasse des aktuellen Parameters Remote implementiert: Übergabe eines Fernverweises. • Wenn die Klasse des aktuellen Parameters nicht Remote, aber Serializable implementiert: Übergabe einer Objektkopie. • Sonst: Laufzeitfehler MarshalException • In Objekte eingebettete Verweise werden ebenso behandelt. • Dies gilt für Argumente und Ergebnisse von Fernaufrufen. Robert Tolksdorf und Peter Löhr 27 Serializable • Zur Erinnerung: die Schnittstelle java.io.Serializable dient zur • • • • Markierung von Klassen, deren Objekte für die unformatierte (binäre) Ein/Ausgabe in E/A-Strömen vorbereitet sein sollen. Problem in Java: Felder sind Objekte - können aber nicht fernaufrufbar sein, weil sie über Indizierung statt über Methoden angesprochen werden. Felder sind aber Serializable. Für ein als Parameter eines Fernaufrufs übergebenes Serializable Objekt erhält der Empfänger keinen Fernverweis, sondern einen lokalen Verweis auf eine Kopie des Objekts. Merke: dies ist eine subtile Verletzung der Zugriffsabstraktion! Folgerung: alle nicht aufrufbaren Objekte, die als Parameter übergeben werden sollen, müssen Serializable sein. Beispiel: class Complex implements Serializable { re, im: Float; } Auch String-Objekte sind Serializable. Das ist aber nicht problematisch, da sie ohnehin konstant sind. Robert Tolksdorf und Peter Löhr 28 Subtile Fehler Beispiel: Aufruf zweier Remote Objekte soll jeweils Feld liefern Fernaufruf Feld Feld Feld lokaler Aufruf Feld Robert Tolksdorf und Peter Löhr 29 Subtile Fehler import java.rmi.*; interface RemoteStack extends Remote { void push(int elem) throws RemoteException; void pop() throws RemoteException; int top() throws RemoteException; int[] dump() throws RemoteException; } import java.rmi.server.UnicastRemoteObject; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class RemoteStackImpl implements RemoteStack { int[] x = new int[10]; // exceptions ignored for brevity int sp = 0; public void push(int elem) { x[sp++] = elem; } public void pop() { sp--; } public int top() { return x[sp-1]; } public int[] dump() { return x; } } Robert Tolksdorf und Peter Löhr 30 Subtile Fehler Erzeugung und Registrierung eines fernaufrufbaren Objekts: import java.rmi.server.UnicastRemoteObject; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class CreateRemoteStack { public static void main(String[] arg) throws Exception { RemoteStack s = new RemoteStackImpl(); RemoteStack stub = (RemoteStack) UnicastRemoteObject. exportObject(s, 0); Registry registry = LocateRegistry.getRegistry(); registry.bind("remote", stub); } } Robert Tolksdorf und Peter Löhr 31 Subtile Fehler Weiteres Programm: Erzeugung eines fernaufrufbaren Objekts, lokaler Aufruf dieses Objekts und Fernaufruf des anderen: import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.util.*; public class RMITest { public static void main(String[] arg) throws Exception{ Map<String,RemoteStack> stacks = new HashMap<String,RemoteStack>(); stacks.put("this", new RemoteStackImpl()); Registry registry = LocateRegistry.getRegistry(); RemoteStack x = (RemoteStack) registry.lookup("remote"); stacks.put("that", x); // jetzt 2 Objekte im Verzeichnis stacks Robert Tolksdorf und Peter Löhr 32 Subtile Fehler RemoteStack s1, s2; s1 = stacks.get("that"); s2 = stacks.get("this"); s1.push(3); s2.push(3); int[] there = s1.dump(); int[] here = s2.dump(); there[0] = 100; here[0] = 100; System.out.print("there: " + s1.top() + " "); System.out.print("here: " + s2.top() + "\n"); } } Test nach Start von rmiregistry und CreateRemoteStack: $ java RMITest there: 3 here: 100 $ Robert Tolksdorf und Peter Löhr Warum das?? --> Bild S. 29 33 Zusammenfassung Robert Tolksdorf und Peter Löhr Zusammenfassung • Fernaufrufbare Objekte • müssen eine Schnittstelle implementieren; • diese Schnittstelle muss von java.rmi.Remote erben; • client stub implementiert diese Schnittstelle. • Das Objektverzeichnis rmiregistry • verzeichnet fernaufrufbare Objekte; • diese werden vom jeweiligen Erzeuger eingetragen • und stehen beliebigen Klienten zur Verfügung. • Parametersemantik • Für fernaufrufbare Parameter wird Netzverweis übergeben. • Für serialisierbare Parameter wird Objektkopie übergeben, • was die Verteilungsabstraktion schwächt! Robert Tolksdorf und Peter Löhr 35 Literatur Sun Microsystems: Java Remote Method Invocation Specification http://java.sun.com/javase/6/docs/platform/rmi/spec/rmiTOC.html Sun Microsystems: Java Remote Method Invocation Tutorial http://java.sun.com/docs/books/tutorial/rmi/ Robert Tolksdorf und Peter Löhr 36