Java-Programmierung Remote Method Invocation - RMI
Entwicklungsmethoden Sockets Entwurf verteilter Anwendungen ist relativ aufwändig, da zunächst ein Kommunikationsprotokoll entwickelt werden muss aufwändig und fehleranfällig RMI Java-Klassen werden netzwerkübergreifend genutzt Kein Unterschied zwischen Aufruf entfernter Methoden zu lokalen Methoden Ortstransparenz Implizite Parallelität vorhanden 2
Remote Method Invocation - RMI Grundlegende Ideen: Höhere Abstraktion von den zugrundeliegenden Rechnernetzen Unterstützung der objektorientieren Programmierung Objekt 1 Aufruf Rückkehr Objekt 2 Prozess (Adressraum, JVM) Aufruf Objekt 1 Rückkehr Objekt 2 Rechner 1 Rechner 2 3
RMI Erzielung von Verteilungstransparenz mittels eines Stellvertreters (Stub) und eines Gerüsts (Skeleton) Programmierer entwirft bei RMI nur Client / Servermodule Stub /Skeleton werden automatisch generiert Marshalling: Umwandlung von strukturierten Daten in ein Format, welches die Übermittlung an andere Prozesse ermöglicht Client Server Stub Skeleton TCP/IP-Netz 4
Schritte 1. Client-Server-Schnittstelle definieren Ableitung aus Schnittstelle Remote des Packages java.rmi Kennzeichnung der Methoden mit throws RemoteException 2. Schnittstelle implementieren Klasse die die definierte Schnittstelle implementiert Ableitung aus der Klasse UnicastRemoteObject Expliziter Konstruktor mit throws RemoteException 3. Server programmieren Erzeugen der RMI-Objekte und Anmeldung beim Auskunftsdienst 4. Client programmieren Beschaffung eines Stub über den Auskunftsdienst (Kenntnis des Servers und des Objektnamens notwendig) Nutzung des Stub als Ersatz für das echte Objekt 5. Übersetzung und Ausführung (u.u. RMI-Registry starten) 5
Beispiel (Zähler) Schnittstelle definieren (Methoden increment und reset) Schnittstelle implementieren Expliziter Konstruktor notwendig import java.rmi.*; public interface Counter extends Remote public int reset() throws RemoteException; public int increment() throws RemoteException; Methoden sind durch synchronized für gleichzeitige Nutzung vorbereitet 6
Beispiel: Schnittstelle implementieren import java.rmi.*; import java.rmi.server.unicastremoteobject; public class CounterImpl extends UnicastRemoteObject implements Counter private int counter; public CounterImpl() throws RemoteException /* super(); counter = 0; */ public synchronized int reset() throws RemoteException System.out.println("Methode reset wurde aufgerufen"); counter = 0; return counter; public synchronized int increment() throws RemoteException counter++; return counter; 7
Beispiel: Server Erzeugung eines Objektes der Klasse CounterImpl Meldung unter dem Namen Counter mittels statischer Methode rebind Automatische Erzeugung eines Threads, welcher das Skeleton darstellt und auf Verbindungen wartet import java.rmi.*; public class Server1 public static void main( String args[]) try CounterImpl mycounter = new CounterImpl(); Naming.rebind("Counter", mycounter); System.out.println("Zähler-Server ist gestartet"); catch(exception e) System.out.println("Ausnahme: " + e.getmessage()); e.printstacktrace(); 8
Beispiel: Client Referenz auf Objekt durch Anfrage bei der Auskunft statische Methode lookup der Klasse Naming Argument: rmi://rechnername/ Objektname public class Client public static void main(string args[]) if ( args.length!= 2) System.out.println("Notwendige Kommandozeilenargumente: " + "<Name des Server-Rechners> <Anzahl>"); return; try Counter mycounter = (Counter) Naming.lookup("rmi://" + args[0] + "/Counter"); System.out.println("Zähler wird auf 0 gesetzt."); mycounter.reset(); System.out.println("Nun wird der Zähler erhöht."); int count = new Integer( args[1]).intvalue(); long starttime = System.currentTimeMillis(); int result = 0; for ( int i = 0; i < count; i++) result = mycounter.increment(); long stoptime = System.currentTimeMillis(); long duration = stoptime - starttime; System.out.println("Gesamtzeit = " + duration + " msecs"); if ( count > 0) System.out.println("Durchschnittszeit = " + ((duration) / (float) count) + " msecs"); System.out.println("Letzter Zählerstand: " + result); catch(exception e) System.out.println("Ausnahme: " + e); 9
Beispiel: Übersetzen und Ausführen Vor dem Starten des Servers muss die Auskunft über Befehl: rmiregistry gestartet werden Quelle: [2] 10
Erweiterung für Praktikum Grafische Benutzeroberfläche für den RMI-Client Tasten: Reset, Erhöhen, Anzeige des Wertes Nutzung des EventDispatcherThreads (EDT) 11
RMI-Registry: Klasse Naming bind Ausnahme, wenn Name bereits vorhanden (Server) rebind löscht evtl. vorhandenen alten Eintrag (Server) unbind Löschen eines Eintrages (Server) lookup liefert Referenz auf Objekt (Client) list Namen aller vorhandener Einträge (Client) public final class Naming public static void bind( String name, Remote obj) throws AlreadyBoundException, MalformedURLException, RemoteException public static void rebind( String name, Remote obj) throws MalformedURLException, RemoteException public static void unbind( String name) throws NotBoundException, MalformedURLException, RemoteException public static void lookup( String name) throws NotBoundException, MalformedURLException, RemoteException public static String[] list( String name) throws MalformedURLException, RemoteException 12
RMI: Kommunikation Client Server Standardport für Registry ist 1099 (andere über rmiregistry port) rmi://rechner:port/objekt Beliebige Ports: x, y1, y2 Quelle: [1] 13
Starten der RMI-Registry: Klasse LocateRegistry Methode: static Registry createregistry( int port) Alternative: Referenz auf lokale oder ferne RMI-Registry mittels Methode getregistry der Klasse LocateRegistry import java.rmi.registry.*; public class Server2 public static void main(string args[]) try Registry registry = LocateRegistry.createRegistry(1099); registry.rebind("counter", new CounterImpl()); System.out.println("Zähler-Server ist gestartet"); catch(exception e) System.out.println("Ausnahme: " + e.getmessage()); e.printstacktrace(); 14
Parallelität bei RMI-Methodenaufrufen Demonstration über Server mit Sleep Def. Schnittstelle public interface Sleep extends java.rmi.remote void sleep(int secs) throws java.rmi.remoteexception; Impl. Schnittstelle (synchronized ist nicht notwendig) import java.rmi.*; import java.rmi.server.*; public class SleepImpl extends UnicastRemoteObject implements Sleep public SleepImpl() throws RemoteException public synchronized void sleep(int secs) throws RemoteException System.out.println("Beginn von sleep(" + secs + ")"); try Thread.sleep(secs * 1000); catch(interruptedexception e) System.out.println(e); System.out.println("Ende von sleep(" + secs + ")"); 15
Server Erzeugung zweier Objekte der Klasse SleepImpl und Anmeldung unter verschiedenen Namen import java.rmi.*; public class Server public static void main( String[] args) try SleepImpl server; server = new SleepImpl(); Naming.rebind("SleepServer1", server); server = new SleepImpl(); Naming.rebind("SleepServer2", server); catch(exception e) System.out.println(e); e.printstacktrace(); 16
Client Wahl des Objektes über Kommandozeile import java.rmi.*; public class Client public static void main( String[] args) if ( args.length < 3) System.out.println( "Benötigte Kommandozeilenargumente: " + "<Name des Servers> <Name des Objekts> " + "<Sekunden 1>... <Sekunden N>"); return; try Sleep server = (Sleep) Naming.lookup("rmi://" + args[0] + "/" + args[1]); System.out.println("Kontakt zu Server hergestellt"); for ( int i = 2; i < args.length; i++) int secs = Integer.parseInt(args[i]); System.out.println( "Aufruf von sleep(" + args[i] + ")"); server.sleep( secs); catch( Exception e) System.out.println( e); e.printstacktrace(); 17
Ausführung Starten von mindestens zwei Clients mit versch. Objekten localhost SleepServer1 60 localhost SleepServer2 5 Effekt: Parallelität bei der Behandlung mehrerer Clients Starten von mindestens zwei Clients mit identischen Objekten localhost SleepServer1 60 localhost SleepServer1 5 Effekt: Parallelität wird über Schlüsselwort synchronized geregelt mit synchronized: Sequenzielle Abarbeitung ohne synchronized: Parallele Abarbeitung Frage nach paralleler Nutzung eines Objektes in einem Client 18