RMI Wir haben

Werbung
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)
Herunterladen