Socket Factory

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:

  • Eine Socket Factory, die einen einzigen Socket-Typ erzeugt,
  • Eine Socket Factory, die mehr als einen Socket-Typ erzeugen kann,
  • Die Verwendung von Socket Factories in Anwendungen.

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:

  1. Festlegung des Socket-Typs, der erzeugt werden soll.
  2. Schreiben einer Socket Factory auf Client-Seite, die das Interface RMIClientSocketFactory implementiert.
  3. Implementierung der Methode createSocket der Klasse RMIClientSocketFactory.
  4. Schreiben einer Socket Factory auf Server-Seite, die das Interface RMIServerSocketFactory implementiert.
  5. Implementierung der Methode createServerSocket der Klasse RMIServerSokketFactory.

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.

code 

package server4;
import java.io.*;
import java.net.*;
class KompressionsSocket extends Socket {

    // Input-Stream, den der Socket verwendet
    private InputStream in;

    // Output-Stream, den der Socket verwendet
    private OutputStream out;

    public KompressionsSocket() {

      super();

    }
    public KompressionsSocket(String host, int port) throws IOException {

      super(host, port);

    }
    public InputStream getInputStream() throws IOException {

      if (in == null) {

        in = new KompressionInputStream(super.getInputStream ());

      }
      return in;

    }
    public OutputStream getOutputStream() throws IOException {

      if (out == null)

        out = new KompressionOutputStream(super.getOutputStre am());

      return out;

    }

    //Fluten des Kompressions-Output-Streams vor dem Schliessen des Sockets
    public synchronized void close() throws IOException {

      OutputStream out1 = getOutputStream();
      out1.flush();
      super.close();

    }

}

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.

code 

package server4;
import java.io.*;
import java.net.*;
class KompressionsInputStream extends FilterInputStream {

    int bufPos = 0;
    public KompressionsInputStream(InputStream in) {

      super(in);

    }

    // Puffer fuer letzte 4 gelesene Byte
    int buf[] = new char[4];

    public int read() throws IOException {

      int code;
      try {

        do {

          code = readCode();

        } while (code != -1);
        //Dekodierung der Zeichen muss hier eingefuegt werden

      } catch (EOFException e) {

        // Return Code fuer End of File (EOF)
        return -1;

      }
      return code;

    }

    private int readCode() throws IOException {

      int b = in.read();
      if (b < 0)

        throw new EOFException();

      return b;

    }

}

Daten können nun mit Hilfe des Input-Streams eingelesen werden. Zur Ausgabe der Daten dient die Klasse KompressionOutputStream.

code 

package server4;
import java.io.*;
class KompressionsOutputStream extends FilterOutputStream {

    public KompressionsOutputStream(OutputStream out) {

      super(out);

    }
    public void write(int b) throws IOException {

      //An dieser Stelle muss die Kompression der Daten erfolgen
      writeCode(b);

    }
    private void writeCode(int c) throws IOException {

      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.

code 

package server4;
import java.io.*;
import java.net.*;
import java.rmi.server.*;
public class KompressionsClientSocketFactory implements  RMIClientSocketFactory, Serializable {

    public Socket createSocket(String host, int port) throws IOException {

      KompressionsSocket socket = new KompressionsSocket(host, port);
      return socket;

    }

}

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.

code 

package server4;
import java.io.*;
import java.net.*;
import java.rmi.server.*;
public class KompressionsServerSocketFactory  implements RMIServerSocketFactory, Serializable {

    public ServerSocket createServerSocket(int port) throws IOException {

      KompressionsServerSocket server = new KompressionsServerSocket(port);
      return server;

    }

}

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.

code 

package server4;
import java.io.*;
import java.net.*;
class KompressionsServerSocket extends ServerSocket {

    public KompressionsServerSocket(int port) throws IOException {

      super(port);

    }
    public Socket accept() throws IOException {

      Socket s = new KompressionsSocket();
      implAccept(s);
      return s;

    }

}

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.

code 

package server4;
import java.io.*;
import java.net.*;
import java.rmi.server.*;
public class MClientSocketFactory implements RMIClientSocketFactory, Serializable {

    //Standard-RMISocketFactory
    private static RMISocketFactory defaultFactory = RMISocketFactory.getDefaultSocketFactory();

    private String protokoll;
    private byte[] daten;
    public MClientSocketFactory(String protokoll) {

      this.protokoll = protokoll;

    }
    public Socket createSocket(String host, int port)throws IOException {

      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.

code 

package server4;
import java.io.*;
import java.net.*;
import java.rmi.server.*;
public class MServerSocketFactory  implements RMIServerSocketFactory, Serializable {

    //Standard-RMISocketFactory
    private static RMISocketFactory defaultFactory = RMISocketFactory.getDefaultSocketFactory();

    private String protokoll;
    private byte[] daten;
    public MServerSocketFactory(String protokoll) {

      this.protokoll = protokoll;

    }
    public ServerSocket createServerSocket(int port) throws IOException {

      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:

  1. In der Implementierung des entfernten Objekts muss ein Konstruktor zur Verfügung stehen, der den Konstruktor der Klasse UnicastRemoteObject (oder der Klasse Activatable) aufruft und der die Argumente RMIClientSocketFactory und RMIServerSocketFactory akzeptiert.
  2. Weiterhin muss eine Datei java.security.policy entwickelt werden, die es einer Anwendung gestattet, Sockets zu erzeugen.

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:

code 

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.

code 

package server4;
import java.rmi.*;
public class Klient {

    public static void main (String args[]) {

      DatabaseUpdate du = null;
      if (args.length != 2) {

        System.err.println("Eingabe: java server.Klient <Server> <Port>");
        System.exit(1);

      }
      String server = args[0];
      int port = Integer.parseInt(args[1]);

      //Neuer Security Manager
      System.setSecurityManager(new RMISecurityManager());

      try {

        //Binden der Objektinstanz an entfernte Registry
        String url="//"+server+":"+port+"/DatabaseUpdate";

        du = (DatabaseUpdate)Naming.lookup(url);

      } catch (Exception e) {

        System.err.println("Keine Verbindung"+e);

      }

      try {

        //Schicke 1, also Spieler hat gewonnen
        int spielAntwort = du.updateDatenbank(1);
        System.out.println("Rückgabewert des Servers: "+spielAntwort);

      } catch (Exception e) {

        System.err.println("Problem in Remote-Methode");

      }

    }

}

Das Interface, das anschließend implementiert werden muss, sieht wie folgt aus:

code 

package server4;
import java.rmi.*;
public interface DatabaseUpdate extends Remote {

    //Aktualisierung des Spielstands
    public int updateDatenbank(int spiel) throws RemoteException;

}

Während Client und Interface gleich bleiben, muss die Implementierung des Interfaces deutlich verändert werden. Die neue Implementierung sieht dann wie folgt aus:

code 

package server4;
import java.rmi.*;
import java.rmi.server.*;
import java.io.*;
public class DatabaseUpdateImpl extends UnicastRemoteObject  implements DatabaseUpdate{

    public DatabaseUpdateImpl (String protokoll) throws RemoteException {

      super(0, new MClientSocketFactory(protokoll),new MServerSocketFactory(protokoll));

    }
    public int updateDatenbank (int spiel) throws RemoteException {

      //Hier wird noch keine Funktion realisiert
      return spiel;

    }

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.

code 

    public static void main(String args[]) {

      //Security Manager einrichten
      if (System.getSecurityManager() == null)

        System.setSecurityManager(new RMISecurityManager());

      try {

        DatabaseUpdateImpl obj = new DatabaseUpdateImpl("Kompression");
        Naming.rebind("/DatabaseServer", obj);
        System.out.println("Datenbank-Server in Registry");

      } catch (Exception e) {

        System.out.println("Fehler: " + e.getMessage());
        e.printStackTrace();

      }

    }

}


SPNavRight SPNavRight SPNavRight
BuiltByNOF