19.2.3 | Beispiel: Chat-Server
|
Am Beispiel eines Chat-Servers wird nun demonstriert, wie man mit RMI durch
die Verwendung von Callback-Objekten eine asynchrone, bidirektionale Kommunikation
realisieren kann. Die Chat-Anwendung besteht insgesamt aus drei Typen von Server-Objekten,
wobei zwei der Typen auf dem Chat-Server und ein Typ auf dem Client als Callback-Objekt
benutzt werden. Analog zur Kaufhaus-Anwendung gibt es auch beim Chat-Server
auf Serverseite ein zentrales Server-Objekt, das dazu benutzt wird, sich beim
Server anzumelden und somit neue Sitzungen zu erzeugen, sowie ein sitzungsorientiertes Objekt,
das für jeden Client den aktuellen Sitzungszustand speichert. Zum aktuellen Sitzungszustand
gehört in diesem Fall der Nickname des Benutzers und das Callback-Objekt, unter dem
Nachrichten asynchron zu einem einzelnen Client geschickt werden können.
Das Erzeugen einer neuen Sitzung wird auf Serverseite über die Methode createSession
durchgeführt:
public ChatSession createSession(String nickname,
ClientHandle handle)
throws RemoteException {
System.out.println("create session: "+nickname);
ChatSession s;
s = new ChatSessionImpl(this, nickname, handle);
sessions.add(s);
return s;
}
Der Client übergibt dieser Methode den Nickname für die Sitzung und einen
Verweis auf das Callback-Objekt, unter dem er Daten vom Server empfangen kann.
Über das zurückgelieferte ChatSession-Exemplar kann der Client
neue Nachrichten an alle aktuell verbundenen Clients schicken.
Alle aktuell verbundenen Clients werden im zentralen Server-Objekt
in einem List-Exemplar gespeichert.
Auf Client-Seite übernehmen folgende Zeilen den Aufbau einer neuen Verbindung:
ChatServer server;
server = (ChatServer)Naming.lookup("chat-server");
handle = new ClientHandleImpl(this);
session = server.createSession(nickname, handle);
Der einzige Unterschied zu einem herkömmlichen Aufruf ist, dass es sich bei
der Klasse ClientHandleImpl selbst wiederum um ein Server-Objekt handelt, das
in der Lage ist, Anfragen über Netzwerk entgegenzunehmen. Diese Eigenschaft bekommt die Klasse
nur dadurch, dass sie von UnicastRemoteObject abgeleitet ist. Somit bekommt der
Chat-Client eine Referenz auf den Chat-Server über den Namensdienst. Der Chat-Server bekommt
ein Callback-Objekt des Chat-Clients durch die Übergabe als Parameter.
Wenn ein Client eine neue Nachricht zum Server schickt, wird diese Nachricht durch
die Methode postMessage() im zentralen Server-Objekt verteilt:
public void postMessage(String message, ChatSessionImpl s) {
ChatSessionImpl tmp;
for(int i=0; i < sessions.size(); i++) {
tmp = (ChatSessionImpl)sessions.get(i);
try {
ClientHandler handle = tmp.getClientHandle();
handle.receiveMessage(s.getNickname(), message);
} catch(RemoteException ex) {
System.out.println("unabled to contact client "
+ s.getNickname());
System.out.println("removing.");
removeSession(tmp);
i--; // Da nun alle Clients in Liste einen
// Platz nach unten rutschen ...
}
}
}
Dabei wird die Liste mit allen aktuell verbundenen Clients sequenziell durchgegangen
und durch Aufruf der Methode receiveMessage() des clientseitigen
Callback-Objekts verschickt.
Material zum Beispiel
Distributed-Garbage-Collection (DGC)
Die
automatische Freigabe von nicht mehr benötigtem Speicher vereinfacht
in Java viele Aufgaben und macht den Code weniger fehleranfällig. Von
der automatischen Speicherfreigabe wird aber nicht nur in lokalen Anwendungen
Gebrauch gemacht, sondern auch in verteilten Anwendungen, die mit RMI entwickelt wurden.
Hierbei gilt grundsätzlich dasselbe Prinzip wie bei lokalen Anwendungen auch:
Server-Objekte, die nicht mehr referenziert werden, werden aus dem Speicher gelöscht.
Server-Objekte, die bei einem Namensdienst registriert sind können dabei nicht freigegeben werden,
da der Namensdienst jederzeit eine Referenz auf das Server-Objekt hält. Deshalb ist
die Freigabe im wesentlichen auf sitzungsorientierte Objekte beschränkt, die nicht
direkt bei einem Namensdienst angemeldet werden (z. B. die ChatSession-Exemplare
des Chat-Servers im vorangegangenen Abschnitt. Da diese Sitzungs-orientierten Objekte
nur einem einzigen Client zugeordnet sind, werden sie nicht mehr benötigt, sobald der
Client beendet wurde. Deshalb werden sie in solch einem Fall automatisch vom Distributed
Garbage Collector freigegeben.
Wie
schnell der Garbage Collector merkt, dass ein Objekt nicht mehr referenziert wird,
hängt von der Einstellung der Property java.rmi.server.dcg.leaseValue ab.
Per Voreinstellung ist diese Property nicht gesetzt, was einem Wert von 10 Minuten
entspricht. Wenn es erforderlich ist, Ressourcen schneller wieder freizugeben,
kann dieser Wert explizit angegeben werden, z. B. beim Starten der Java-Laufzeitumgebung.
Bei folgendem Aufruf wird der Wert auf 30 Sekunden gesetzt (Angabe in Millisekunden):
java -Djava.rmi.server.dcg.leaseValue=30000 ShopServer
Man sollte beachten, dass mehr Kommunikationsaufwand erforderlich ist, wenn der
Wert klein ist, da der Garbage Collector in diesem Fall öfter prüfen muss, ob
die Server-Objekte noch erreichbar sind.
Die
Implementierung des Interface Unreferenced ermöglicht es einem
Server-Objekt, selbst darüber informiert zu werden, wenn es nicht mehr referenziert wird
und somit aus dem Speicher gelöscht wird. Dieses Interface definiert lediglich die
Methode unreferenced(). In dieser Methode können z. B. Ressourcen
freigegeben werden, die nicht mehr benötigt werden (z. B. offene Datenbankverbindungen).
Im Beispiel der Chat-Anwendung wäre es denkbar, über diesen Mechanismus die
serverseitigen Verweise auf einzelne Clients in der internen Liste zu löschen:
public void ChatSessionImpl extends UnicastRemoteObject
implements ChatServer, Unreferenced {
ChatServer server;
...
public void unreferenced() {
server.removeSession(this);
}
}
Copyright © 2002 dpunkt.Verlag, Heidelberg. Alle Rechte vorbehalten.