RMI Remote Method Invocation Wir haben Komponenten auf einem Rechner Komp1 Komp2 Wir wollen zwei Rechner Komp1 Komp2 TCP/IP aber wie ? Fallstudie Chat Einfluss der Architektur auf den Entwurf oder Man kann sich das Leben leicht oder schwer machen Ein Chat System Aufgabe des Chat Systems ist es, die Kommunikation einer Menge von Clients untereinander zu ermoeglichen. Der Normalfall ist, dass ein Client einen kurzen Text (Nachricht) an einen Server schickt, diese wird dann an alle angemeldeten Clients weitergeschickt. Zusaetzlich ist es moeglich, eine Nachricht nur an einen bestimmten Client zu schicken. Ein angemeldeter Client, der zeitweise nicht auf Nachrichten antworten kann, kann ein sogenanntes "away" setzen, und damit den Grund seiner Abwesenheit mitteilen (z.B. beim Essen,...). Die Clients werden ueber einen (selbst gewaehlten) eindeutigen Namen identifiziert. Die Kommunikation zwischen den Clients und dem Server erfolgt ueber TCP/IP, prinzipiell sollte es jedoch moeglich sein, auf ein anderes Protokoll (z.B. UDP,...) umzustellen. Use cases Entwurf Die Kommunikation erfolgt ueber TCP/IP, d.h. ueber sockets. Die Details ueber die Kommunikation sollten moeglichst gekapselt werden, da u.U. spaeter ein anderes Protokoll verwendet werden soll. Wegen der Plattformunabhaengigkeit und der einfachen Verwendung von Sockets wird das System in JAVA implementiert. Als Userinterface ist ein Kommandozeilenfenster ausreichend, es sollte aber spaeter ohne groessere Aenderungen in eine grafische Applikation umgewandelt werden koennen. Da der Server auch eine groessere Anzahl von Clients versorgen koennen soll, wird dieser mit einem Thread pro Client implementiert. Der Client muss wohl auch mehrere Threads haben (einen, der auf ankommende Meldungen vom Server wartet und einen, der mit dem Benutzer interagiert und Meldungen verschickt) Die prinzipielle Funktion ist die, dass Messages vom Client zum Server und vom Server an die Clients transportiert werden. Das System ist also letzlich so etwas wie eine "Nachrichtenverteilungsmaschine". Das zentrale Objekt ist also zum einen die Message selbst und zum zweiten die Verbindung, ueber die die Messages verschickt werden. Der Ablauf innerhalb des Systems (insbesondere innerhalb des Servers) wird also im wesentlichen vom Eintreffen (und Versenden) von Nachrichten bestimmt. Da sowohl Client als auch Server diese Funktionen in aehnlicher Weise (aber eben nicht ganz identisch, insbesondere was die Fehlerbehandlung,...) betrifft, wird die gemeinsame Funktionalitaet in der Klasse ChatConnection gekapselt, fuer Client und Server werden dann davon entsprechend spezialiserte Klassen abgeleitet. Die gesamte Kommunikation laeuft so ab, dass Message Objekte ueber die Verbindung (als Strings) uebertragen werden. Je nach Inhalt hat ein Message-Objekt einen entsprechenden Typ: MSG_UNKNOWN,MSG_CONNECT,MSG_DISCONNECT,MSG_SEND, MSG_BROADCAST,MSG_AWAY,MSG_REPLY Messages, die vom Server kommen, haben immer den Typ MSG_REPLY Klassen Nachricht empfangen senden (Client) senden (Server) the Source [iBook:aktuell/Chat/Source] admin% ls AwayDialog.java Client.java MessageTest.java ChatClient.java ClientChatConnection.java ServerChatConnection.java ChatConnection.java ClientList.java TextClient.java ChatServer.java GrafClient.java ChatUI.java Message.java [iBook:aktuell/Chat/Source] admin% wc *.java 40 69 686 AwayDialog.java 130 332 3336 ChatClient.java 100 266 2428 ChatConnection.java 149 436 4369 ChatServer.java 16 28 406 ChatUI.java 122 345 2850 Client.java 40 110 1167 ClientChatConnection.java 108 288 2580 ClientList.java 167 406 4000 GrafClient.java 138 402 3031 Message.java 28 59 823 MessageTest.java 39 84 949 ServerChatConnection.java 119 322 3221 TextClient.java 1196 3147 29846 total Ergebnis ? nicht zufriedenstellend unübersichtlich ziemlich aufwändig (über 1000 Zeilen) muss besser gehen ! aber wie ? nachdenken ...... Patterns Observer konkret: Chatsystem auf einem Rechner (Assoziation) ist eher langweilig, wie erfolgt Kommunikation ? Proxy remote proxy realisiert Zugriff über Netzwerk in JAVA mit RMI implementiert !! allgemein K1 Proxy Proxy K2 TCP/IP Client “spricht” nicht mit dem Server, sondern mit (Proxy-) Objekt, das die gleiche Schnittstelle hat wie das Server-Objekt. Für K2 sieht es aus, als ob ein Aufruf vom Proxy kommt --> K1 und K2 müssen nicht modifiziert werden (außer Verbindungsaufbau) Proxy kümmert sich um Kommunikation Dynamisches Modell Implementierung Client String line = r.readLine(); while (line != null) { ChatServer.broadcast(line); line = r.readLine(); } Server for(int i=0;i<Clients.size();i++) { myClient = (ChatClientInterface) Clients.elementAt(i); myClient.showMessage((String)Messages.elementAt(j)); } the Source [iBook:aktuell/Chat/rmichat] admin% ls ChatClient.java ChatServer.java ChatClientInterface.java ChatServerInterface.java [iBook:aktuell/Chat/rmichat] admin% wc *.java 52 104 983 ChatClient.java 4 13 139 ChatClientInterface.java 96 177 1927 ChatServer.java 5 19 221 ChatServerInterface.java 157 313 3270 total log Client ist hier primitiver (nur ein Thread) trotzdem: bedeutend einfacher !! Proxy überträgt Parameter (Objekte) übers Netz Serialisierung verschiedene Transportmechanismen möglich kümmert sich um Probleme wie: Verbindungsabbruch Übertragungsfehler Verschlüsselung Datenkonvertierung (hier nicht nötig, da alles Java) vgl. CORBA wird generiert: muß nicht programmiert werden Vorgehen Schnittstelle definieren Java Interface keine eigene Sprache (vgl. CORBA) Schnittstelle implementieren (Server) Proxy erzeugen Wie finden Clients das Server-Objekt ? registry Server muß angemeldet werden Beispiel: komplexe Berechnung Schnittstelle import java.rmi.*; public interface Rechner extends Remote { public int compute(int par) throws RemoteException; } interface, keine Klasse muß public sein erweitert Remote jede Methode kann RemoteException werfen (Fehler auf der anderen Seite) Implementierung import java.rmi.*; import java.rmi.server.*; public class RechnerImpl extends UnicastRemoteObject implements Rechner { // Default-Konstruktor muss implementiert werden // (kann RemoreException werfen public RechnerImpl() throws RemoteException { } } public int compute(int par) throws RemoteException { return par+1; } Proxy erzeugen $> rmic RechnerImpl $> ls Rechner.class Rechner.java RechnerImpl.class RechnerImpl.java RechnerImpl_Skel.class RechnerImpl_Stub.class *_Stub ist der Proy auf Client-Seite *_Skel ist der Dispatcher auf Server-Seite Quellcode behalten mit rmic -keep Dateien K1 Proxy Proxy K2 RMI RechnerClient RechnerImpl RechnerImpl_Stub RechnerImpl_Skel Stub public final class RechnerImpl_Stub extends java.rmi.server.RemoteStub implements Rechner, java.rmi.Remote { ... $method_compute_0 = Rechner.class.getMethod("compute", new java.lang.Class[] {int.class}); ... public int compute(int $param_int_1) throws java.rmi.RemoteException { Object $result = ref.invoke(this, $method_compute_0, new java.lang.Object[] {new java.lang.Integer($param_int_1)}, -537915219968796873L); return ((java.lang.Integer) $result).intValue(); ... Skel public final class RechnerImpl_Skel implements java.rmi.server.Skeleton { private static final java.rmi.server.Operation[] operations = { new java.rmi.server.Operation("int compute(int)") }; ... public void dispatch(java.rmi.Remote obj, java.rmi.server.RemoteCall call, int opnum, long hash) ... RechnerImpl server = (RechnerImpl) obj; switch (opnum) { case 0: // compute(int) { int $param_int_1; java.io.ObjectInput in = call.getInputStream(); $param_int_1 = in.readInt(); ... int $result = server.compute($param_int_1); java.io.ObjectOutput out = call.getResultStream(true); out.writeInt($result); Anmerkung: IDL #include "orb.idl" #ifndef __Rechner__ #define __Rechner__ interface Rechner { long compute( in long arg0 ); }; #pragma ID Rechner "RMI:Rechner:0000000000000000" #endif rmic -idl Rechner erzeugt (CORBA-) IDL RMI kann über IIOP (Internet Inter Orb Protocol) augerufen werden Registry+Server starten mit: rmiregistry Standard-Port: 1099 dann: Server registrieren: import java.rmi.*; import java.rmi.server.*; public class RechnerServer { public static void main(String args[]) { try { // Naming.bind() bindet nur einmal, sonst Fehler // Naming.rebind() ersetzt ggf. Bindung Naming.rebind("rmi://localhost/Rechner",new RechnerImpl()); } catch (Exception e) { e.printStackTrace(); } } } Client holt sich die Referenz zum Remote-Objekt von der registry und los gehts import java.rmi.*; public class RechnerClient { public static void main(String args[]) { try { Rechner r = (Rechner) Naming.lookup("rmi://localhost/Rechner"); int ergebnis = r.compute(3); System.out.println(ergebnis); } catch (Exception e) {e.printStackTrace(); } } } et voila und zur Kasse Schnittstelle definieren Bestand.java Schnittstelle implementieren BestandImpl.java Proxy erzeugen Server programmieren in BestandImpl.java (main) Client programmieren remoteKasse.java rmiregistry läuft schon (auf susi)