![]() |
|
Im Folgenden soll die Theorie der Factory-Design-Patterns anhand eines Beispiels umgesetzt werden, einer RMI-Socket Factory. Eine Socket Factory sollte immer dann verwendet werden, wenn Client und Server in RMI über Sockets kommunizieren sollen, die Daten verschlüsseln oder komprimieren und für verschiedene Verbindungen unterschiedliche Socket-Typen verwendet werden sollen. Indem eine eigene Socket Factory in RMI installiert wird, kann die Transportschicht von RMI ein Protokoll über IP verwenden, das nicht zwangsläufig das in der Klasse java.net.Socket definierte TCP ist. Standardmäßig verwendet RMI allerdings TCP. Bevor Java in der Version 1.2 verfügbar war, konnte man eine eigene java.rmi.RMISocketFactory-Subklasse schreiben, die einen Socket-Typ für den RMI-Transport erzeugte, der sich von dem in der Klasse java.net.Socket definierten Typ unterschied. Allerdings war es einer installierten Socket Factory unmöglich, auf einer Objekt-zu-Objekt-Basis verschiedene Socket-Arten zu erzeugen. Seit Java in der Version 1.2 verfügbar ist, kann eine eigene Socket Factory in RMI implementiert werden, die die Art von Socket-Verbindung erstellt, die der Benutzer wünscht (auch auf einer Objekt-zu-Objekt-Basis, falls erforderlich). Im Folgenden werden die Einsatzmöglichkeiten von Socket Factories betrachtet:
Einzelne Socket-Arten in der Socket Factory Wenn eine Socket Factory in RMI erstellt werden soll, die einen einzelnen Socket-Typ erzeugt, sind die folgenden Schritte zu durchlaufen:
Der Socket-Typ, der entwickelt werden soll, hängt stark von der Art der Anwendung ab. Häufig verwendete Socket-Typen verschlüsseln Daten vor der Übertragung bzw. komprimieren Daten. Im Folgenden soll als Beispiel eine Socket Factory entwickelt werden, die Sockets erstellt, die Daten bei der Übertragung komprimieren. Zunächst muss der Socket-Typ implementiert werden.
package server4; // Input-Stream, den der Socket verwendet // Output-Stream, den der Socket verwendet public KompressionsSocket() { super(); } super(host, port); } if (in == null) { in = new KompressionInputStream(super.getInputStream ()); } } if (out == null) out = new KompressionOutputStream(super.getOutputStre am()); return out; } //Fluten des Kompressions-Output-Streams vor dem Schliessen des Sockets OutputStream out1 = getOutputStream(); } } Diese Klasse verwendet zwei weitere Klassen (KompressionInputStream und KompressionOutputStream), die das Einlesen und die Ausgabe der Daten regeln. Zunächst soll die Klasse KompressionInputStream beschrieben werden.
package server4; int bufPos = 0; super(in); } // Puffer fuer letzte 4 gelesene Byte public int read() throws IOException { int code; do { code = readCode(); } while (code != -1); } catch (EOFException e) { // Return Code fuer End of File (EOF) } } private int readCode() throws IOException { int b = in.read(); throw new EOFException(); return b; } } Daten können nun mit Hilfe des Input-Streams eingelesen werden. Zur Ausgabe der Daten dient die Klasse KompressionOutputStream.
package server4; public KompressionsOutputStream(OutputStream out) { super(out); } //An dieser Stelle muss die Kompression der Daten erfolgen } out.write(c); } } Nachdem der Socket-Typ definiert ist, kann die Implementierung der Socket Factory auf der Client-Seite erfolgen, indem das Interface RMIClientSocketFactory implementiert wird. Die Socket Factory dieses Beispiels wird im Folgenden als KompressionsClientSocketFactory bezeichnet.
package server4; public Socket createSocket(String host, int port) throws IOException { KompressionsSocket socket = new KompressionsSocket(host, port); } } Da die Funktion einer Socket Factory in RMI darin besteht, die RMI-Laufzeitumgebung mit Sockets zu versorgen, muss die Klasse KompressionsClientSocketFactory eine Implementierung der Methode createSocket des Interfaces RMIClientSocketFactory anbieten, mit der Sockets des korrekten Typs angelegt und zurückgegeben werden können (KompressionsSocket). Im Anschluss daran muss die Implementierung der Socket Factory auf der Server-Seite vorgenommen werden, indem das Interface RMIServerSocketFactory implementiert wird. Die Socket Factory dieses Beispiels wird im Folgenden mit KompressionsServerSocketFactory bezeichnet.
package server4; public ServerSocket createServerSocket(int port) throws IOException { KompressionsServerSocket server = new KompressionsServerSocket(port); } } Die Implementierung der Methode createServerSocket ist fast identisch mit der der Methode createSocket bis auf die Tatsache, dass die Methode createServerSocket ein Objekt vom Typ KompressionsServerSocket erzeugen muss.
package server4; public KompressionsServerSocket(int port) throws IOException { super(port); } Socket s = new KompressionsSocket(); } } Nachdem nun bekannt ist, wie eine Socket Factory für einen Socket-Typ anzulegen ist, wird im Folgenden beschrieben, wie Socket Factories entwickelt werden müssen, die mehr als einen Socket-Typ unterstützen. Mehrere Socket-Arten in der Socket Factory Um eine Socket Factory erstellen zu können, die mehr als eine Socket-Art unterstützt, werden grundsätzlich dieselben Schritte durchlaufen wie bei der Generierung einer Socket Factory, die nur einen Socket-Typ anbietet. Diese Schritte müssen allerdings um einen Wrapper für die verschiedenen Socket-Typen erweitert werden. Im folgenden Beispiel wird die Socket Factory mit MClientSocketFactory bezeichnet, da mehrere Socket-Typen unterstützt werden. Jede der Socket Factories hat einen Konstruktor, der angibt, welches Protokoll für die jeweilige Instanz eines Objekts unterstützt werden soll. Die folgende Socket Factory unterstützt zwei Socket-Arten: KompressionsSokket-Objekte und die standardmäßigen Objekte der Klasse java.net.Socket. Schritt 1 der Regeln (Festlegung des Socket-Typs) muss an dieser Stelle nicht wiederholt werden. Die Entwicklung der Klasse KompressionsSocket wurde bereits erläutert, Sockets stehen ohne weitere Implementierung direkt zur Verfügung. Im Folgenden ist die Klasse MClientSocketFactory angegeben, die die Auswahl des Protokolls auf der Client-Seite realisiert.
package server4; //Standard-RMISocketFactory private String protokoll; this.protokoll = protokoll; } if (protokoll.equals("Kompression")) return new KompressionsSocket(host, port); return defaultFactory.createSocket(host, port); } } Das Anlegen des geeigneten Socket-Typs ist hier unmittelbar ersichtlich. Im Anschluss ist die Implementierung der Socket Factory auf der Server-Seite angegeben.
package server4; //Standard-RMISocketFactory private String protokoll; this.protokoll = protokoll; } if (protokoll.equals("Kompression")) return new KompressionsServerSocket(port); return defaultFactory.createServerSocket(port); } } Auch die Auswahl des Protokolls auf der Server-Seite sollte leicht zu verstehen sein. Nach Durchlauf dieser fünf Schritte steht ein Mechanismus zur Verfügung, mit dem ein beliebiger Socket-Typ realisiert und in einer Anwendung eingesetzt werden kann. Die Verwendung dieser Typen im Rahmen einer Anwendung wird im nächsten Abschnitt erläutert. Verwendung der Socket Factory in Anwendungen Soll eine Socket Factory für ein entferntes Objekt eingesetzt werden, so sind die folgenden zwei Schritte zu durchlaufen:
Auch in diesem Beispiel wird eine Policy-Datei verwendet, die jeder Anwendung globale Berechtigungen einräumt. Es sei nochmals darauf hingewiesen, dass die Verwendung einer derartigen Datei in einer realen Umgebung ein erhebliches Sicherheitsrisiko darstellt. Wenn eine Socket Factory eingerichtet wird, so muss der RMI-Laufzeitumgebung mitgeteilt werden, welcher Socket-Typ verwendet werden soll. Angenommen, der Server erweitert die Klasse UnicastRemoteObject, so muss diese Benachrichtigung einhergehen mit der Generierung eines Konstruktors eines entfernten Objekts, der die folgende Version des UnicastRemoteObject-Konstruktors aufruft:
protected UnicastRemoteObject(int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf) Im Folgenden wird das Beispiel, das bereits zur allgemeinen Verwendung von RMI vorgestellt wurde, wieder aufgegriffen, hier allerdings unter Einbezug der Socket Factory. Zunächst wird die Client-Seite vorgestellt, die das entfernte Objekt aufruft.
package server4; public static void main (String args[]) { DatabaseUpdate du = null; System.err.println("Eingabe: java server.Klient <Server> <Port>"); } //Neuer Security Manager try { //Binden der Objektinstanz an entfernte Registry du = (DatabaseUpdate)Naming.lookup(url); } catch (Exception e) { System.err.println("Keine Verbindung"+e); } try { //Schicke 1, also Spieler hat gewonnen } catch (Exception e) { System.err.println("Problem in Remote-Methode"); } } } Das Interface, das anschließend implementiert werden muss, sieht wie folgt aus:
package server4; //Aktualisierung des Spielstands } Während Client und Interface gleich bleiben, muss die Implementierung des Interfaces deutlich verändert werden. Die neue Implementierung sieht dann wie folgt aus:
package server4; public DatabaseUpdateImpl (String protokoll) throws RemoteException { super(0, new MClientSocketFactory(protokoll),new MServerSocketFactory(protokoll)); } //Hier wird noch keine Funktion realisiert } Im Konstruktor wird nun mittels super eine neue Instanz der Klasse UnicastRemoteObject erzeugt. Anschließend wird eine main-Methode dazu verwendet, das Protokoll auszuwählen.
public static void main(String args[]) { //Security Manager einrichten System.setSecurityManager(new RMISecurityManager()); try { DatabaseUpdateImpl obj = new DatabaseUpdateImpl("Kompression"); } catch (Exception e) { System.out.println("Fehler: " + e.getMessage()); } } } |
|
|