Login |
 
 

Aufgabe 3, Abgabe spätestens am 08.06.2010, 8:00 Uhr

Einleitung

Im dritten Übungsblock sollt ihr die Vorlesungsverwaltung zur Client-Server Anwendung erweitern; d.h. als sogennante "Verteilte" oder "Distributed" Anwendung erweitern. Genauer gesagt muss der Manager später auf einem Rechner als Server laufen und mehrere Clients (ClientManager), die sowohl auf dem selben als auch anderen Rechnern laufen, sollen auf den zentralen Server zugreifen können.

Man benötigt dazu eine Server Klasse, die den normalen Manager hält und auf Client-Anfragen wartet. Jede Client Anfrage wird vom Server dann einem eigenen Thread (einer sogenannten Session) zugeteilt, der die Anfrage bearbeitet. Wenn mehrere ClientManager also gleichzeitig Anfragen stellen, werden diese parallel abgearbeitet. Für die Client Seite benötigt man also den ClientManager, der das IManager Interface implementiert, aber die add, get, edit, search und remove Methoden in eine Nachtricht (Message) umwandelt und diese an den Server schickt. Dieser ClientManager implementiert übrigens das Proxy-Pattern, das dem Benutzer auf der Client Seite das Gefühl gibt, als würde er direkt mit dem Manager kommunizieren. Die GUI kann dann später einfach diesen ClientManager benutzen als wäre es der normale Manager. Keine weiteren Code-Änderungen an der GUI sind dazu notwendig!

Alle Klassen sollen in das package de.unikn.bioml.mpi2.stuvove.implementation.clientserver, die Messages allerdings nochmal untergliedert in das sub-package messages also de.unikn.bioml.mpi2.stuvove.implementation.clientserver.messages.

 

Teil 1 - Die Messages

Beim Distributed Computing kommunizieren die teilnehmenden Rechner über Messages (Message Passing). In unserem Fall heißt das, eine Message teilt dem Server mit, dass z.B. ein neuer Raum oder eine neue Vorlesung hinzugefügt werden soll. Der Server wiederum sendet die Antwort in einer anderen Message an den Client zurück. Bei unserem Verwaltungsprogramm bedeutet das, dass Methoden wie addXXX, editXXX, ... jetzt nicht mehr direkt auf den eigentlichen Manager aufgerufen werden können und daher serialisiert werden müssen. Die "serialisierten Methoden" werden dann über das Netzwerk zum Server geschickt, dort wieder deserialisiert, um dann auf den eigentlichen Manager angewendet werden zu können. Eine Methode kann serialisiert werden, indem man den Methodennamen in die Message packt und die Parameter dazu serialisiert. Die Parameter sind bei uns Lecturer, Lectures, Rooms, etc. Die Serializer dafür habt ihr bereits in Übung 1 erstellt. Daher muss man "nur" noch die Parameter zusammen mit der Methodenbezeichnung "zusammen" serialisieren.

Ladet euch das JAR ue3.jar vom SVN-Repository auf euren Rechner. Darin sind schon Implementierungen für die MessageTimeSlot und MessageLecturer enthalten. Ihr sollt jetzt noch die Messages für das Hinzufügen, Editieren, Löschen, Suchen von Rooms (MessageRoom) und StudyCourses (MessageStudyCourse) implementieren. Dazu erweitert ihr die abstrakte Klasse Message (diese übernimmt auch schon das Serialisieren der Methode (edit, search, add,...)). Als Beispiele könnt ihr euch die im JAR bereits implementierten Messages anschauen.

Eine Ausnahme bildet die MessageStudyCourse. Hier sollt ihr nicht den bereits vorhandenen XML Serializer XMLStudyCourseDeserializer und XMLStudyCourseSerializer verwenden, sondern einen von uns im JAR mitgelieferten NetStudyCourseDeserializer und NetStudyCourseSerializer. Die Verwendung ist genau gleich zu den anderen, serialisiert aber auch die in einem StudyCourse vorhandenen Lecturer, TimeSlots und Rooms vollständig, also nicht nur deren ID. Das ist wichtig, da sonst falsche Suchergebnisse auftreten. (Wer weiss warum das so ist?)

 

Teil 2 - Der ClientManager

Nachdem ihr die Messages erstellt habt, sollt ihr nun den ClientManager implementieren, der die Messages verwendet, um "remote" Anfragen zu senden. Erweitert dazu den AbstractClientManager der im JAR mitgeliefert ist. Euer ClientManager muss noch die fehlenden Methoden des Interfaces IManager implementieren, wie immer die add, edit, search, remove, get Methoden für Room und StudyCourse. Euer ClientManager muss zusätzlich noch den Namen und den Port des Server-Rechners halten. Desweiteren muss er die Methode sendMessageReceiveGeneralAnswer implementieren, die im AbstractClientManager definiert ist. Diese Methode übernimmt das Senden einer Nachricht und das Empfangen der Antwort. Diese Methode verwendet dazu den Namen und den Port. Ihr müsst außerdem den InputStream des Sockets in einen von uns im JAR mitgelieferten UncloseableInputStream kapseln, da die XML Parser den Stream schließen nachdem sie mit dem Parsen fertig sind. Der folgende Code-Ausschnitt zeigt den Rumpf des von euch zu implementierenden ClientManagers

public class ClientManager extends AbstractClientManager {

private String m_remoteHost;

private int m_remotePort;

...

public ClientManager(String remoteHost, int remotePort) {
...
}

// hier kommen die add, edit, ... methoden
...

protected MessageGeneral sendMessageReceiveGeneralAnswer(Message message)
throws Exception {

... // UncloseableInputStream verwenden (liegt im ue3.jar)
}
}

Teil 3 - Die Session

Die Session kommuniziert mit dem ClientManager, genauer: eine Session bedient genau einen ClientManager. Eine Session ist dabei ein Thread, der mit anderen Sessions parallel laufen kann. Die Session empfängt die serialisierte Message über den Socket-Input-Stream (also über das Netzwerk), wandelt diese wieder in ein Message-Objekt um und führt die gewünschten Aktionen auf dem eigentlichen Manager aus. Ihr sollt nun diese Session implementieren und dazu die abstrakte Klasse AbstractSession erweitern. AbstractSession implementiert bereits die Klasse Thread. Session soll neben den beiden Methoden processRoom und processStudyCourse die Methode processMessage der Elternklasse überschreiben um eine generelle Message (MessageGeneral) zu interpretieren und dann an die beiden Methoden processRoom und processStudyCourse weiterzuleiten. In eurer überschriebenen Methode müsst ihr natürlich noch die Implementierung der Elternklasse aufrufen, um auch die anderen Messages, die ihr nicht implementiert habt zu prozessieren. Als Beispiel könnt ihr die Klasse AbstractSession heranziehen. Wie der AbstractSession zu entnehmen ist, erwartet der Konstruktor einen Socket, der die Verbindung zum ClientManager repräsentiert und den eigentlichen Manager auf dem die Operationen durchgeführt werden.

Der folgende Code-Ausschnitt zeigt den Rumpf der von euch zu implementierenden Session.

public class Session extends AbstractSession {

/**
* Creates a new session for a given client socket connection.
*
* @param communicationSocket the client socket connection for which to
* create the session
* @param manager the underlying manager to which the message requests are
* performed
*/
public Session(Socket communicationSocket, IManager manager) {
super(communicationSocket, manager);
}

/**
* Checks the type of the general message creates a specific one, performs
* the corresponding action on the manager and returns a result message.
*
* @param messageGeneral the general message received from a client
*
* @return the response to the incoming message after processing it
*/
protected Message processMessage(MessageGeneral messageGeneral) {
...
}

private Message processRoom(MessageRoom message) {
...
}

private Message processStudyCourse(MessageStudyCourse message) {
...
}
}

 

Teil 4 - Der Server

Der Server ist nun nicht mehr viel Arbeit. Er muss lediglich eine Verbindungsanfrage eines ClientManager entgegenehmen, eine neue Session mit dieser Anfrage erzeugen und weiter auf die nächste Anfrage warten. Der Server wird erzeugt mit dem eigentlichen Manager und dem Port, auf dem der Server auf eingehende Anfragen warten soll.

Da der Manager von mehreren Threads gleichzeitig verändert wird, ist es notwendig die Methoden des Managers zu synchronisieren. Da der Code des Managers nicht nachträglich veändert werden soll, schreibt einfach eine sogennante "Wrapper" Klasse SyncManager, die IManager implementiert und einen normalen Manager enthält. Die Methoden des SyncManager leiten einfach nur weiter zum eigentlichen Manager mit dem Unterschied, dass die Methoden synchronisiert sind. Dieses Design Pattern nennt man "Delegation Pattern", da der SyncManager die eigentliche Aufgabe an den gewrappten Manager delegiert.

Der Rumpf des Servers könnte wie folgt aussehen:

public class Server {

// Member Variablen

/**
* Creates a server for a given manager.
*
* @param manager the manager for which a server should be created
* @param serverPort the port to listen on
*/
public Server(final IManager manager, final int serverPort) {
...
}

/**
* Starts the server.
*/
public void start() {
// place here the actual code for the server loop
}

/**
* Main method to instantiate and start the server with an synchronized
* manager.
*
* @param args the argument vector from the command line
*/
public static void main(String[] args) {
// create a server object and start it
}
}

Folgede Abbildung zeigt die Gesamtarchitektur fuer die Client-Server-Funktionalität (Hier als Beispliel mit drei ClientManagern).

Client-Server Architektur

 

Teil 5 - Testen

Im SVN Repository findet ihr unter public das Testprojekt aus Übung 1. Im package de.unikn.bioml.mpi2.stuvove.implementation.clientserver befinden sich Testklassen, mit denen ihr eure Implementierung testen könnt. Denkt dran, ihr muesst immer vorher den Server starten. Der ClientManager wird mit den Einstellungen Name="localhost" und Port=12345 instanziiert. Falls ihr andere Einstellungen vornehmen wollt, z.B. weil ihr von einem anderen Rechner aus auf den Server zugreifen wollt, müsst ihr die Einstellungen im Konstruktor anpassen. Ausserdem sollt ihr eure GUI starten, aber diesmal mit dem ClientManager. Startet auch mal mehrere GUIs und schaut wie sich das System verhält. Viel Erfolg!