Galileo Computing <openbook>
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 1.4
Buch: Java ist auch eine Insel - Zum Katalog
gp Kapitel 19 RMI
  gp 19.1 Entfernte Methoden
    gp 19.1.1 Wie entfernte Methoden arbeiten
    gp 19.1.2 Stellvertreter (Proxy)
    gp 19.1.3 Wie die Stellvertreter die Daten übertragen
    gp 19.1.4 Probleme mit entfernten Methoden
  gp 19.2 Nutzen von RMI bei Middleware-Lösungen
  gp 19.3 Die Lösung für Java ist RMI
    gp 19.3.1 Entfernte Objekte programmieren
    gp 19.3.2 Entfernte und lokale Objekte im Vergleich
    gp 19.3.3 RMI und CORBA
  gp 19.4 Definition einer entfernten Schnittstelle
  gp 19.5 Das entfernte Objekt
    gp 19.5.1 Der Bauplan für entfernte Objekte
    gp 19.5.2 Der Konstruktor
    gp 19.5.3 Implementierung der entfernten Methoden
    gp 19.5.4 UnicastRemoteObjekt, RemoteServer und RemoteObject
  gp 19.6 Stellvertreterobjekte erzeugen
    gp 19.6.1 Das Dienstprogramm rmic
  gp 19.7 Der Namendienst (Registry)
    gp 19.7.1 Der Port
  gp 19.8 Der Server: Entfernte Objekte beim Namensdienst anmelden
    gp 19.8.1 Automatisches Anmelden bei Bedarf
  gp 19.9 Einen Client programmieren
    gp 19.9.1 Einfaches Logging
  gp 19.10 Aufräumen mit dem DGC
  gp 19.11 Entfernte Objekte übergeben und laden
    gp 19.11.1 Klassen vom RMI-Klassenlader nachladen
    gp 19.11.2 Sicherheitsmanager
  gp 19.12 Registry wird vom Server gestartet
  gp 19.13 RMI über die Firewall
    gp 19.13.12 RMI über HTTP getunnelt
  gp 19.14 Java API für XML Messaging (JAXM)
  gp 19.15 Java Message Service (JMS)

Kapitel 19 RMI

Ich denke, daß es einen Weltmarkt für vielleicht fünf Computer gibt.
– Thomas Watson, Vorsitzender von IBM, 1943


Galileo Computing

19.1 Entfernte Methoden  downtop

Bei Funktionsaufrufen nutzen wir die Intelligenz der Methode, die zu gegebenen Eingabeparametern Ausgangswerte liefert oder einen Systemzustand verändert. Die Methode ist in diesem Fall Anbieter einer ganz speziellen Dienstleistung. Wenn wir zu einer Eingabeanfrage eine Antwort bekommen wollen, die Implementierung dieser Intelligenz aber auf einer anderen Maschine liegt, so bleibt die Frage nach einer eleganten Implementierung. Der klassische Weg führt über Client-Server-Systeme. Der Client formuliert eine Anfrage, die vom Server verstanden und interpretiert wird. So sieht dies etwa in einfacher Form mit einem Datenbankserver aus. Der Client möchte zum Beispiel den Umsatz an Comics herausfinden. Er schickt dann eine Anfrage und das Ergebnis wird zurückgeschickt und ausgewertet. Diese Kommunikation zwischen verteilten Prozessen muss aber aufwendig bei Client-Server-Systemen implementiert werden. Wünschenswert ist eine Sicht auf entfernte Dienste wie auf Methoden eines Einprozessorsystems. Ideal wäre es, wenn ein Funktionsaufruf auf einen Server so aussieht, als ob es eine lokale Funktion wäre.


Galileo Computing

19.1.1 Wie entfernte Methoden arbeiten  downtop

Glücklicherweise haben Birrel und Nelson schon 1984 ein Modell vorgestellt, das entfernte Serverfunktionen wie lokale Funktionen aussehen lassen. Die grundlegende Idee dabei ist, eine Stellvertreterfunktion anzubieten, die den tatsächlichen Übertragungsvorgang verdeckt. Wir wollen uns dies an einem Beispiel klarmachen. Nehmen wir an, der Client möchte die Funktion String crackPasswort(String) nutzen, die in Wirklichkeit auf einen ganz anderen Rechner implementiert wird. Da wir aber die Vorgabe haben, eine entfernte Funktion so aussehen zu lassen wie eine lokale, wird nun crackPasswort() wie gewohnt aufgerufen.

System.out.println( crackPasswort( 
"v4ghd,sjh324" ) );

Auf dem Server muss nun die Implementierung von crackPasswort() zu finden sein.

String crackPasswort()
{
// Durch Tabellen und vielen Schleifen brute-force Attacke.
}

Galileo Computing

19.1.2 Stellvertreter (Proxy)  downtop

Jetzt fehlt nur noch die Verbindung von Client und Server. Dies wird durch die so genannten Stellvertreterobjekte (engl. Proxys) realisiert. Diese existieren auf der Client- und auf der Server-Seite. Der Client spricht demnach nicht mit dem Server, sondern mit einer Funktion, die so aussieht wie eine Server-Funktion. In Wirklichkeit nimmt sie die Parameter und verpackt sie in eine Server-Anfrage und schickt sie weg. So finden wir auf der Client-Seite etwa Folgendes:

String crackPasswort( String passwort 
)
{
// Das Passwort nehmen und zum Server schicken
}

Und dies ist das klassische Client-Server-Konzept, welches wir schon von Sockets her kennen. Der Client mit dem Funktionsaufruf initiiert die Anfrage, und der Server wartet, bis ein williger Kunde eintrifft. Der Server nimmt die Anfragen vom Client entgehen, entnimmt aus dem Anforderungspaket die Daten und ruft die lokale Funktion auf. Dazu kann auch auf der Server-Seite ein Stellvertreter existieren. Es reicht aber auch ein Server aus, der zunächst auf eingehende Anfragen wartet und dann die entsprechenden Funktionen ohne einen eigenen Stellvertreter aufruft.

Abbildung

Natürlich wäre es müßig, diese Stellvertreter selbst zu programmieren. Dies macht glücklicherweise ein Hilfsprogramm. Der Client merkt also nicht, dass der Stellvertreter die Daten weiterleitet und der Server merkt nicht, dass er in Wirklichkeit nicht mit lokalen Daten gefüttert wird.


Galileo Computing

19.1.3 Wie die Stellvertreter die Daten übertragen  downtop

Für RMI gibt es wie bei TCP/IP ein Schichtenmodell, das aus mehreren Ebenen besteht. Die oberste Ebene mit dem höchsten Abstraktionsgrad nutzt einfach einen Transportdienst der darunter liegenden Ebene. Dann ist es seine Aufgabe, wie die Daten wirklich übermittelt werden, und die Stellvertreter realisieren, die Parameter irgendwie von einem Ort zum anderen zu bewegen. Sie setzen also die Transportschicht um.

Eine Implementierung über Sockets

Wir können uns vorstellen, dass die Stellvertreter eine Socket-Verbindung nutzen. Der Server horcht dann in accept() auf einkommende Anfragen und der Stellvertreter vom Client baut dann die Verbindung auf. Sind die Parameter der Funktion primitive Werte, dann können sie in unterschiedliche write()- und read()-Methoden umgesetzt werden. Doch auch bei komplexen Objekten, wie Listen, hat Java keine Probleme, da es ja eine Serialisierung gibt. Objekte werden dann einfach plattgeklopft, übertragen und auf der anderen Seite wieder ausgepackt. Bei entfernten Methodenaufrufen wird neben der Serialisierung auch der Begriff marshalling verwendet. Somit ist das Verhalten wie bei lokalen Methoden fast abgebildet, insbesondere der synchrone Charakter. Die lokale Funktion blockiert so lange, bis das Ergebnis der entfernten Methoden ankommt.

RMI Transport Protocol

Die Übertragung mittels Sockets ist nur eine Möglichkeit. Neben den Sockets implementiert Java-RMI für Firewalls auch die Übermittlung über HTTP-Anfragen. Wir werden in einem späteren Kapitel darauf zurückkommen. Zusammen nennen sich die Protokolle RMI Wire Protocol. Bei Sockets wird hier TCP genutzt. Über eine eigene RMI-Transportschicht könnten auch andere Protokolle genutzt werden, etwa über UPD oder gesicherte Verbindungen mit SSL. Dann müssen wir allerdings selbst Hand anlegen, denn diese Funktionen befinden sich nicht in der Standard-Bibliothek.


Galileo Computing

19.1.4 Probleme mit entfernten Methoden  downtop

Das Konzept scheint entfernte Methoden abzubilden wie lokale. Doch es gibt einige feine Unterschiede, sodass wir nicht alle lokale Methoden anfangen müssen zu verteilen, weil gerade mal ein entfernter Rechner schön schnell ist.

gp  Zunächst einmal müssen wir ein Kommunikationssystem voraussetzen. Damit fangen aber die unter Client bzw. Server bekannten Probleme an. Was passiert, wenn das Kommunikationssystem zusammenbricht? Was passiert mit verstümmelten Daten?
gp  Da beide Rechner eigene Lebenszyklen haben, ist nicht immer klar, dass beide Partner miteinander kommunizieren können. Wenn der Server nicht ansprechbar ist, muss der Client darauf reagieren. Hier bleibt nichts anderes übrig, als über einen Zeitablauf (timeout) zu gehen.
gp  Da Client und Server über das Kommunikationssystem miteinander sprechen, ist die Zeit für die Abarbeitung eines Auftrages um ein vielfaches höher als bei lokalen Methodenaufrufen. Zu den Kommunikationskosten über das Rechennetzwerk kommen die Kosten für die Serialisierung hinzu, die besonders in den ersten Versionen des JDKs viel Zeit kosteten.

Parameterübergabe bei getrenntem Speicher

Doch der wirkliche Unterschied zwischen lokalen und entfernten Methoden ist das Fehlen des gemeinsamen Kontexts. Die involvierten Rechner führen ihr eigenes Leben mit ihren eigenen Speicherbereichen. Stehen auf einer Maschine zum Beispiel statische Variablen jedem zur Verfügung, so ist dies bei entfernten Maschinen nicht der Fall. Ebenso gilt dies für Objekte, die von mehreren Partnern geteilt werden. Die Daten auf einer Maschine müssen also erst übertragen werden, und somit arbeitet der Server mit einer Kopie der Daten. Bei primitiven Daten ist das kein Thema, schwierig wird es erst bei Objektreferenzen. Mit der Referenz auf ein Objekt kann der andere Partner nichts anfangen. Aber mit der Übertragung der Objekte fangen wir uns zwei weitere Probleme ein.

gp  Erstens muss der Zugriff exklusiv erfolgen, da andere Teilnehmer den Objektzustand ja unter Umständen ändern können. Wenn wir also eine Referenz übergeben, und das Objekt wird serialisiert, könnte der lokale Teilnehmer Änderungen vornehmen, die unter Umständen vom Server beim Zurückspielen verloren gehen würden.
gp  Damit haben wir zweitens den Nachteil, dass nicht einfach eine Referenz reicht. Große Objekte müssen immer wieder komplett serialisiert werden. Und mit dem Mechanismus des Serialisierens fangen wir uns ein Serialisierungsproblem ein: Nicht alle Objekte sind per se serialisierbar. Gerade die Systemklassen lassen sich nicht so einfach übertragen. Bei einer Trennung von Datenbank und Applikation wird das deutlich. Eine hübsche Lösung wäre etwa ein RMI-Programm für die Datenbankanbindung einzusetzen und eine Applikation, die mit dem RMI-Programm spricht, um unabhängig von der Datenbank zu sein. (RMI nimmt hier die Stelle als so genannte Middleware ein.) Bedauerlicherweise implementiert keine der Klassen im Paket java.sql die Schnittstelle Serializable. Die Ergebnisse müssen in einem neuen Objekt verpackt und verschickt werden.

Wenn die Daten übertragen werden, müssen sich die Partner zudem über das Austauschformat geeinigt haben. Die Daten müssen von beiden verstanden werden. Traditionell bieten sich zwei Verfahren an.

gp  Zunächst ein symmetrisches Verfahren. Alle Parameter werden in einem festen Format übertragen. Auch wenn Client und Server die Daten gleich darstellen, werden sie in ein neutrales Übertragungsformat konvertiert.
gp  Dem gegenüber steht das asymmetrische Verfahren. Hier schickt jeder Client die Daten in einem eigenem Format, und der Server hat verschiedene Konvertierungsfunktionen, um die Daten zu erkennen.
gp  Da wir uns innerhalb von Java und der Konventionen bewegen, müssen wir uns über das Datenformat keine Gedanken machen. Java konvertiert die Daten unterschiedlichster Plattformen immer gleich. Daher handelt es sich um ein symmetrisches Übertragungsprotokoll.

Galileo Computing

19.2 Nutzen von RMI bei Middleware-Lösungen  downtop

Der Begriff Middleware ist im vorhergehenden Kapitel schon einmal gefallen. Ganz platt gesagt, handelt es sich dabei um eine Schicht, die zwischen zwei Prozessen liegt. Die Middleware ist sozusagen der Server für den Client und der Client für einen Server, vergleichbar mit einem Proxy. Das großartige bei Middleware-Lösungen ist die Tatsache, dass es eine starke Kopplung von Systemen entzerrt und eine bessere Erweiterung ermöglicht. Sprach vorher etwa eine Applikation direkt mit dem Server, so würde durch den Einsatz der Middleware die Applikation mit der Zwischenschicht reden und diese dann mit dem Server. Die Applikation weiß dann gar nichts vom Server.

Ein oft genanntes Einsatzgebiet für Middleware sind Applikationen, die mit Datenbanken arbeiten. Systeme der ersten Generation verbanden sich direkt mit der Datenbank, lasen Ergebnisse und modifizierten die Tabellen. Der Nachteil ist offensichtlich. Das Programm ist unflexibel bei Änderungen, und diese Änderungen müsste bei einer groß angelegten verbreiteten Version allen Kunden zugänglich gemacht werden. Erschwerend kommt ein Sicherheitsproblem hinzu. Wenn das Programm mit der Datenbank direkt spricht, etwa in Form von JDBC, dann gelangen Informationen über die internen Tabellen über die Abfragen leicht nach außen. Bei unsachgemäßer Programmierung kann auch ein Bösewicht das Programm dekompilieren und die Tabellen vielleicht mit unsinnigen Werten füllen – denkbar schlecht für den kommerziellen Dauerbetrieb. Die Antwort auf das Problem ist der Einsatz einer Middleware. Dann verbinden sich die Applikation mit der Zwischenschicht, die dann die Daten besorgt, zum Beispiel von der Datenbank. Im Programm sind dann nur noch verteilte Anfragen, und JDBC ist nicht mehr zu entdecken. Als Applikationsentwickler können wir ruhigen Gewissens die Datenbank verändern, und wir müssen »nur« die Middleware anpassen. Der Kunde mit der Applikation sieht davon nichts. Das Sicherheitsproblem ist damit auch vom Tisch. Die Middleware kann zur Performance-Steigerung auch noch mehrgleisig fahren und die schnellste Datenbank nutzen. Lastenverteilung kann nachträglich implementiert werden und die Software beim Client bleibt schlank.


Galileo Computing

19.3 Die Lösung für Java ist RMI  downtop

Java RMI ist nun der Mechanismus, um entfernte Objekte und dessen Angebote zu nutzen. Auch schon der Vorgänger in der prozeduralen Welt, RPC (Remote Procedure Call), ist eine Entwicklung von Sun. Mit RMI lässt sich somit auf hohem Abstraktionsniveau arbeiten. Damit dies funktioniert, sind drei Teile mit der Kommunikation beschäftigt:

1. Der Server stellt das entfernte Objekt mit der Funktion bereit. Die Funktion läuft im eigenen Adressraum, und der Server leitet Anfragen an diese Funktion weiter.

Galileo Computing

19.3.1 Entfernte Objekte programmieren  downtop

Um entfernte Objekte mit ihren Methoden in Java-Programmen zu nutzen, müssen wir einige Schritte machen, die im Folgenden kurz skizziert werden. An den Schritten spiegelt sich der Programmieraufwand wieder:

1. Wir geben eine entfernte Schnittstelle an, die die Methode(n) definiert.

Galileo Computing

19.3.2 Entfernte und lokale Objekte im Vergleich  downtop

Vergleichen wir entfernte Objekte und ihre Methode, so fallen Gemeinsamkeiten ins Auge. Die Referenzen auf entfernte Objekte lassen sich wie gewohnt übertragen. Sie können als Parameter oder als Rückgabewert angegeben werden. Dabei ist es egal, ob die Methode mit den Parametern oder Rückgabewerten lokal oder entfernt sind. Die Unterschiede zu lokalen Objekten sind aber deutlicher. Da ein Client immer über eine entfernte Schnittstelle das Objekt repräsentiert, hat es nichts mit der tatsächlichen Implementierung zu tun und daher ist auch eine Typumwandlung unmöglich. Die einzige Umwandlung von einer entfernten Schnittstelle ist in Remote und noch eventuellen Obertypen von Remote. Damit ist auch deutlich, dass instanceof auch nur testen kann, ob das Objekt entfernt ist oder nicht; die echte Vererbung auf der Server-Seite bleibt verborgen.


Galileo Computing

19.3.3 RMI und CORBA  downtop

Neben der reinen Java-Lösung RMI gibt es auf dem großen Land der Standards das komplexe CORBA. Im Gegensatz zu RMI definiert CORBA ein großes Framework für unterschiedliche Programmiersprachen. Die Definition von CORBA geht in das Jahr 1991, also vor RMI. Die OMG (Object Management Group) hat bei der reinen Java-Implementierung Sun vorgeworfen, einen zweiten Standard zu schaffen. Die Frage nach dem Sinn von RMI ist also erlaubt. Die Antwort liegt jedoch in der Einfachheit und Integration von RMI. In den letzen Jahren hat Sun jedoch RMI an den defacto Standard CORBA angepasst. Die Stellvertreterobjekte sprechen mittlerweile nicht nur das eigene Protokoll, sondern können sich auch mit dem Inter-ORB Protocol (IIOP) von CORBA unterhalten. Diese Lösung heißt RMI/IIOP (»RMI über IIOP«). Damit lässt sich auch über RMI eine Verbindung zwischen Java-Programmen und nicht Java-Programmen herstellen. Wollten wir auf IIOP verzichten, müsste die Übermittlung mit CORBA oder einem eigenen Protokoll erfolgen. Dann können wir aber nicht von der einfachen Nutzung profitieren und müssen uns mit Fragen wie Bit-Anzahl der Datentypen oder Byte-Ordnungen (Big-Endian/Litte-Endian) herumschlagen.


Galileo Computing

19.4 Definition einer entfernten Schnittstelle  downtop

Damit der Client eine entfernte Methode nutzen kann, muss er ein Stellvertreterobjekt befragen. Dieses packt die Daten ein und übermittelt sie. Wir haben gesagt, dass diese Hilfsfunktionen automatisch generiert werden. Damit der Generator korrekten Quellcode für die Übertragung erstellen kann, ist eine Beschreibung nötig. Die Definition muss die Signatur eindeutig spezifizieren, und damit weiß der Client, wie die Funktion aussieht, die er aufrufen kann und der Server kann die Methode dann beschreiben. Normalerweise gibt es für die Spezifikation der entfernten Funktionen spezielle Beschreibungssprachen und auch CORBA verfolgt diesen Weg, doch bei RMI reicht es, ein Interface anzugeben, da in Java die Schnittstelle alles Wesentliche erfasst.

Listing 19.1   Adder.java
import java.rmi.*;
public interface Adder extends Remote
{
public int add(int x, int y) throws RemoteException;
}

An diesem Beispiel können wir mehrere wichtige Eigenschaften der Schnittstelle ablesen:

gp  Die entfernte Schnittstelle ist öffentlich. Wenn sie nur paketsichtbar oder eingeschränkter ist, kann der Client die entfernte Methode nicht finden, wenn er danach verlangt.
gp  Die eigene Schnittstelle erweitert die Schnittstelle Remote. Nur die Klassen, die Remote implementieren, können entfernte Methoden anbieten. Remote ist allerdings leer und damit eine Markierungsschnittstelle.
gp  Die angebotenen Methoden können nicht beabsichtigte Fehler auslösen, zum Beispiel, wenn das Transportsystem zusammenbricht. Für diesen Fall muss jede Methode RemoteException in einer throws-Anweisung aufführen.
gp  Eine entfernte Funktion darf Parameter besitzen. Sind dies primitive Werte, so werden diese einfach übertragen. Handelt es sich um Objekte, so müssen diese serialisierbar sein.

Galileo Computing

19.5 Das entfernte Objekt  downtop

Die Methoden des Servers werden letztendlich vom Client über die Stellvertreter genutzt. Der Server muss unterschiedliche Vorgaben erfüllen: Er muss eine spezielle Klasse erweitern, einen Konstruktor anbieten und die entfernte Schnittstelle implementieren. Dann kann ein Server-Objekt angemeldet werden. Das Serverobjekt für die Schnittstelle sieht dann wie folgt aus:

Listing 19.2   AdderImpl.java
import java.rmi.*;
import java.rmi.server.*;

public class AdderImpl extends UnicastRemoteObject implements Adder
{
public AdderImpl() throws RemoteException
{
}

public int add( int x, int y ) throws RemoteException
{
return x + y;
}
}

Da die Klasse eine Implementierung der Schnittstelle ist, geben wie ihr die Endung Impl.


Galileo Computing

19.5.1 Der Bauplan für entfernte Objekte  downtop

Zuerst fällt in der Implementierung auf, dass wir die Klasse UnicastRemoteObject erweitern. Sie liegt im Paket java.rmi.server und zeigt so die grobe Richtung für die Verwendung an.

public class AdderImpl extends 
UnicastRemoteObject implements Adder
{
...
}

Diese Klasse bietet Hilfe bei der Übertagung der Daten mittels Standard-TCP-Sockets an. Der Server kann so auf eingehende Anfragen reagieren und diese bearbeiten. Weiterhin zeigt UnicastRemoteObject an, dass ein Exemplar (nicht repliziert) unserer Klasse existieren soll.


Galileo Computing

19.5.2 Der Konstruktor  downtop

Für den Konstruktor eines entfernten Objekts gelten zwei Eigenschaften: Wir müssen einen Standard-Konstruktor anbieten und dieser muss ebenso wie die Methoden RemoteException anzeigen.

public AdderImpl() throws RemoteException
{
}

Der Standard-Konstruktor ist notwendig, da eine Unterklasse genau diesen aufrufen möchte. Unser Konstruktor muss nichts machen. Er ruft aber automatisch den Konstruktor der Oberklasse auf, also den von UnicastRemoteObject. Wir haben schon beschrieben, dass er bei der Übertragung hilft. Wenn ein entferntes Objekt nun konstruiert wird, dann bindet er diesen Dienst an einen anonymen Port und horcht an einkommende Aufrufe. Wollten wir einen speziellen Port nutzen, müssten wir im Konstruktor unserer Unterklasse einen parametrisierten Konstruktor von UnicastRemoteObject aufrufen, der einen Port annimmt.

class java.rmi.server.UnicastRemoteObject 
extends RemoteServer

gp  protected UnicastRemoteObject() throws RemoteException
Erzeugt und exportiert ein neues UnicastRemoteObject und bindet es an einen unbekannten Port. Konnte es nicht exportiert werden, löst der Konstruktor eine RemoteException aus.
gp  UnicastRemoteObject( int port ) throws RemoteException
Erzeugt ein UnicastRemoteObject und bindet es an den angegeben Port. Ist dieser Null, so wird er vom System zugewiesen.

Vererbung nicht möglich. Was dann?

Es kann passieren, dass eine entfernte Klasse schon von einer anderen Klasse erbt und wir wegen der fehlenden Mehrfachvererbung ein Problem bekommen. Wenn wir uns dafür entscheiden, keine Objekte über UnicastRemoteObject anzubieten, aber eine existierende Klasse trotzdem Anbieter sein möchte, so muss das Objekt im Konstruktor ein entferntes Objekt, welches Remote implementiert, mit UnicastRemoteObject.exportObject(Remote) anmelden.

class java.rmi.server.UnicastRemoteObject 
extends RemoteServer

gp  static RemoteStub exportObject( Remote obj )
Exportiert das entfernte Objekt und macht es empfänglich für einkommende Aufrufe. Es wird ein willkürlicher Port verwendet.
gp  static Remote exportObject( Remote obj, int port )
Wie exportObject(Remote), nur wird der angegebene Port und nicht der Standard Port 1099 verwendet.
Beispiel Der Server implementiert die Schnittstelle Adder und registriert sich selbst über exportObject().
import java.rmi.*;
import java.rmi.server.*;

class Server implements Adder
{
public int add ( int x, int y) throws RemoteException
{ ... } public static void main( String args[] ) throws Exception { Server server = new Server(); UnicastRemoteObject.exportObject( server ); Naming.rebind( "echo", server ); } }

Beispiel Die Erweiterung von UnicastRemoteObject ist nur eine Abkürzung für den Weg über exportObject(). Die Methode wird aber sowieso genommen, wie der Ausschnitt aus dem Quellcode für den Konstruktor zeigt.
protected UnicastRemoteObject() throws RemoteException
{
  this(0);
}

 protected UnicastRemoteObject(int port) throws RemoteException
{
  this.port = port;
  exportObject((Remote)this, port);
}


Galileo Computing

19.5.3 Implementierung der entfernten Methoden  downtop

Im nächsten Schritt müssen die Methoden der Schnittstelle implementiert werden. Es steht frei, andere Methoden anzugeben, die nicht in der Schnittstelle vorgegeben sind, doch diese sind dann natürlich nicht nach außen sichtbar.

public int add( int x, int y ) throws RemoteException
{
  return x + y;
}

Die Argumente und Rückgabewerte können von jedem beliebigen Datentyp sein. Bei primitiven Datentypen werden spezielle read()- und write()-Folgen generiert. Objekte müssen die Schnittstelle Serializable implementieren. Dann werden die lokalen Objekte als Kopie übertragen. Über die Serialisierung werden alle nicht statischen und nicht transienten Attribute übermittelt. Ist der Parameter wiederum instanceof Remote, dann wird dieser Verweis als einfache Referenz übergeben. In Wirklichkeit ist sie ein Verweis auf den Stellvertreter.


Galileo Computing

19.5.4 UnicastRemoteObjekt, RemoteServer und RemoteObject  downtop

Entfernte Objekte erweitern oft die Klasse UnicastRemoteObjekt. Sie selbst ist jedoch eine Unterklasse von java.rmi.server.RemoteServer, eine abstrakte Klasse, die wiederum die abstrakte Klasse java.rmi.server.RemoteObject beerbt.

RemoteObject ein verteiltes Objekt

RemoteObject ist nichts anderes als ein verteiltes Objekt, welches die Schnittstellen Remote und Serializable implementiert. Da beides aber nur Markierungsschnittstellen sind, taucht keine ausprogrammierte Methode auf. RemoteObject ersetzt die Klasse Object für verteilte Objekte. Die Unterscheidung tritt bei den Methoden hashCode(), equals() und toString() auf. Ebenso implementiert RemoteObject die Methoden writeObject() und read Object(), damit die verteilten Objekte serialisiert werden können.

abstract class java.rmi.server RemoteObject 
implements Remote, Serializable

gp  protected RemoteObject()
Erzeugt remote-Objekt.
gp  protected RemoteObject(RemoteRef newref) {
Erzeugt remote-Objekt, welches mit angegebener remote-Referenz initialisiert ist.
gp  RemoteRef getRef()
Liefert remote-Referenz für das Objekt.
gp  public static Remote toStub( Remote obj ) throws NoSuchObjectException
Liefert den Stub für das remote-Objekt obj. Die Operation kann nur durchgeführt werden, wenn das Objekt schon exportiert wurde. Sonst wird eine NoSuchObjectException ausgelöst.
gp  int hashCode()
Liefert den Hashcode. Zwei entfernte Stubs, die auf dasselbe Objekt verweisen, sollten auch den gleichen Hashwert liefert. Die Methode ruft auf entfernten RemoteRef-Objekten die Methode remoteHashCode() auf und auf lokale Objekte einfach hashCode().
gp  boolean equals( Object obj )
Vergleicht, ob zwei entfernte Objekte gleich sind. Ist obj kein entferntes Objekt, so wird ein normaler Vergleich mit equals() gemacht. Der Vergleich mit dem entfernten Objekt wird mit remoteEquals() auf dem RemoteRef-Objekt vorgenommen.
gp  String toString()
Liefert eine String-Repräsentation.
gp  private void writeObject( ObjectOutputStream out ) throws IOException,
ClassNotFoundException
Bereitet ein serialisiertes Objekt vor, in dem es den Namen des Objektes in UTF-8 schreibt. Dann wird das Objekt serialisiert.
gp  private void readObject( ObjectInputStream in ) throws IOException, ClassNotFound Exception
Liest das serialisierte Objekt wieder ein.

RemoteServer

RemoteServer erweitert RemoteObject und fügt die interessante Methoden getClientHost() hinzu. Damit kann der Server den Hostnamen vom Client als String erfahren. Damit der Server jedoch Informationen an den Client zurückschicken kann, muss der Client selbst als Server auftreten. Die Kommunikation ist bei RMI in der Regel immer einseitig.

abstract class java.rmi.server.RemoteServer 
extends RemoteObject

gp  protected RemoteServer()
Erzeugt für die Unterklassen ein RemoteServer()-Objekt.
gp  protected RemoteServer( RemoteRef ref )
Erzeugt für die Unterklassen ein RemoteServer()-Objekt mit der Referenz ref.
gp  static String getClientHost() throws ServerNotActiveException
Liefert den Hostnamen des aktuellen Clients. Die Ausnahme ServerNotActiveException wird ausgelöst, wenn der Aufruf außerhalb eines Servers stattfindet, der RMI anbietet.

Galileo Computing

19.6 Stellvertreterobjekte erzeugen  downtop

Die Stellvertreter sind Methoden auf der Client- und Server-Seite, die die tatsächliche Kommunikation betreiben. Sie müssen für jede Methode oder jede Parameteränderung neu angegeben werden. Da die Implementierung per Hand zu aufwendig und inflexibel wäre, erstellt ein Hilfsprogramm diese Klassen. Das Dienstprogramm heißt für Java »rmic«. Der Compiler generiert selbstständig aus einer Methodenbeschreibung die Stellvertreter, die »Stubs« und »Skeleton« heißen. Ein Stub ist ein Stellvertreter (client-seitiger Proxy) für das entfernte Objekt auf der Client-Seite, der die RMI-Anfragen an den Skeleton (server-seitig) weitergibt. Der Skeleton richtet die Client-Anfrage an die wirkliche Methodenimplementierung und schickt das Ergebnis wieder zurück.


Galileo Computing

19.6.1 Das Dienstprogramm rmic  downtop

Bevor rmic zum Zuge kommt, müssen die entfernten Klassen und Schnittstellen übersetzt sein. Danach schreiben wir

$rmic AdderImpl

Die erzeugten Klassen werden standardmäßig im aktuellen Verzeichnis platziert. Mit der Option -D lässt sich der Zielort ändern.

RMI gibt es mittlerweile in unterschiedlichen Version. Mit dem Schalter -vXXX bzw. -iiop lässt sich dies genauer angeben.

gp  -v1.1
Erzeugt Stub und Skeleton für das Protokoll unter JDK 1.1.
gp  -v1.2
Erzeugt den Stub für das JDK 1.2. Skeletons werden nicht benötigt.
gp  -y
Das Sandardprotokoll unter dem JDK 1.2. Es ist kompatibel mit dem neuen 1.2 Stub-Protokoll und dem älteren von 1.1.
gp  -iiop
Erstellt für CORBA die passenden Bausteine.

Mit der Option -idl kann zusätzlich für CORBA eine Spezifikationsdatei erstellt werden. Möchten wir zu den generierten Klassen den Quellcode sehen, so müssen wir -keep angeben.

Tipp Obwohl mit der Zeile alles in Ordnung aussieht, muss unter einigen Systemen der CLASSPATH angepasst werden – er muss auf das aktuelle Verzeichnis zeigen. Andernfalls produziert das Programm einen Fehler, nämlich, dass die Klassen nicht gefunden werden, obwohl sie im Pfad stehen.

Abbildung 19.1   Zusammenhang der generierten Stellvertreter und der entfernten Schnittstelle
Abbildung


Galileo Computing

19.7 Der Namendienst (Registrydowntop

Mit dem Namensdienst können die Server ihre entfernten Objekte mit einem Namen anmelden. Er hilft außer den Clients, die entfernten Objekte zu finden. Für den Namensdienst können unterschiedliche Programme eingesetzt werden; beim JDK ist ein einfaches Programm dabei. Der beigefügte Namensdienst ist ein vereinfachter Object Request Broker (ORB) bei CORBA. Unter Windows starten wir den Dienst im Hintergrund mit folgender Zeile:

$start rmiregistry

Unter Unix-Systemen dann so:

$rmiregistry &
 
 
Tipp Da rmiregisty selbst ein RMI-Client ist, muss dieser Zugriff auf die Server-Klassen haben; andernfalls könnte der Server seine Objekte von einem fremden Typ gar nicht anmelden. Daher sollte im CLASSPATH ein Verweis auf die Server-Klassen stehen.


Galileo Computing

19.7.1 Der Port  downtop

Der Namensdienst läuft standardmäßig auf dem Port 1099 auf. Für Dienste hinter einer Firewall ist es bedeutend, dass dieser Port auch anders lauten kann. Eine andere Portnummer lässt sich einfach als Parameter angeben:

$start rmiregistry 2001

Der angegebene Port dient nur der Vermittlung vom Client zum Namensdienst. Die Kommunikation von Client und Server läuft über einen anderen Port.


Galileo Computing

19.8 Der Server: Entfernte Objekte beim Namensdienst anmelden  downtop

An dieser Stelle haben wir schon fast alles zusammen. Der Namensdienst läuft und wartet auf den Server und den Client. Beginnen wir mit dem Server. Er ist ein normales Java-Programm ohne Einschränkungen. Er muss weder etwas mit Remote noch mit Serializable zu schaffen haben. Seine einzige Aufgabe ist es, ein entferntes Objekt anzulegen und beim Namensdienst einzutragen. Dazu wird die Methode rebind() oder bind() benutzt.

Listing 19.3   AdderServer.java
import java.net.*;
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;

public class AdderServer
{
public static void main( String args[] ) throws Exception
{
AdderImpl adder = new AdderImpl();
Naming.rebind( "Adder", adder );

System.out.println( "Adder bound" ); } }

An diesem Programm ist abzulesen, dass das Eintragen sehr einfach ist. Es ist wie eine assoziative Datenstruktur zu verstehen, die einen Objektnamen mit einem entfernten Objekt assoziiert. Die Notation für das Objekt ist wie bei einer URL:

Host/Objektname

Wenn ein alternativer Port für den Namensdienst gewählt wurde, stellen wie diesen mit Doppelpunkt wie üblich hinten an. Optional kann auch das Protokoll RMI vorangestellt werden. Da aber sowieso nichts anderes unterstützt wird, ist es echt optional.

Was das Binden damit zu tun hat

Zum Binden der Informationen bietet der Namensdienst zwei unterschiedliche Funktionen an. bind() trägt den Dienst im Namensdienst ein, aber wenn schon ein anderer Dienst unter dem gleichen Namen läuft, wird eine AlreadyBoundException ausgelöst. rebind() dagegen fügt abhängig vom Namensdienst einen neuen Eintrag mit dem gleichen Namen hinzu oder überschreibt den alten.

Und abmelden

Ist der Dienst nicht mehr gewünscht, so lässt er sich mit unbind() wieder abmelden, solange der Namensdienst läuft. Aus Sicherheitsgründen lässt der Namensdienst Objekte nur von dem Server entbinden, der auch das Objekt angemeldet hat. Einen zusätzlichen Namen müssen wir daher nicht angeben.

final class java.rmi.Naming

gp  static void bind( String name, Remote obj ) throws AlreadyBoundException,
MalformedURLException, RemoteException
Bindet das Objekt ref, welches in der Regel der Stub ist, an den Namen name und trägt es so in der Registrierung ein. Eine AlreadyBoundException zeigt an, dass der Name schon vergeben ist. Die MalformedURLException informiert, wenn der Name ungültig gebunden ist. Eine RemoteException wird ausgelöst, wenn der Namensdienst nicht erreicht werden konnte. Fehlende Rechte führen zu einer AccessException.
gp  static void rebind( String name, Remote obj )
Wie bind(), nur dass Objekte ersetzt werden, falls sie schon angemeldet sind.
gp  static void unbind( String name )
Entfernt das Objekt aus der Registierung. Ist das Objekt nicht gebunden, so folgt eine NotBoundException. Die anderen Fehler sind wie bei bind().

Galileo Computing

19.8.1 Automatisches Anmelden bei Bedarf  downtop

Bisher haben wir ein entferntes Objekt erzeugt und angemeldet, sodass später das Objekt schon da ist, wenn es angesprochen wird. Wir haben das durch UnicastRemoteObjekt realisiert, dessen Arbeitsweise darin besteht, das Objekt einmal anzumelden. Sollten auf einem Objekt-Server mehrere Dienste vor sich hindämmern, ist das natürlich nicht sonderlich effektiv und kostet unnötig Ressourcen. Daher unterstützt die Bibliothek neben UnicastRemoteObjekt eine weitere Klasse, die das automatische Hochstarten eines Dienstes erlaubt. Wir leiten unser Objekt dann von der Klasse Activatable ab, und dann werden die Objekte bis zu ihrer Aktivierung in einem Dämmerzustand gehalten. Kommt dann der erste Zustand, entfaltet das System dieses Objekt, sodass es Anfragen entgegennehmen kann. Wird das Objekt nach seiner Tat wiederum nicht verwendet, kann es wieder eingefroren werden. Die Daten bleiben dabei stabil. Activatable ist eine abstrakte Klasse, die von RemoteServer abgeleitet ist. Die unterschiedlichen Klassen zum Aktivieren bei Bedarf liegen alle im Paket java.rmi.activation.


Galileo Computing

19.9 Einen Client programmieren  downtop

Ebenso wie der Server ist der Client ein normales Java-Programm, welches weder etwas mit Remote noch mit Serializable zu tun hat. Um nun die entfernte Methode zu nutzen, muss ein entferntes Objekt gesucht und angesprochen werden. Dazu fragen wir den Namensdienst. Der Name für das Objekt setzt sich zusammen aus der URL und dem Namen des Dienstes. Bei Portangaben dürfen wir nicht vergessen, diesen wieder hinter einem Doppelpunkt anzugeben.

Listing 19.4   AdderClient.java
import java.rmi.*;
import java.rmi.registry.*;
import java.rmi.server.*;

public class AdderClient
{
public static void main( String args[] )
{
try
{
Adder a = (Adder)Naming.lookup("Adder"); int sum = a.add( 2, 2 ); System.out.println( sum ); } catch ( Exception e ) { System.out.println( e ); } } }

Damit ist das letzte Puzzelstück zusammen und das RMI-Beispiel komplett. Naming.lookup() liefert zu einer URL ein Stub-Objekt, welches die gewünschte Schnittstelle Adder implementiert. Der Rückgabetyp ist Remote. Das ist jetzt wirklich einfach und wir sehen, dass sich ein lokaler Funktionsaufruf nicht mehr von einem entfernten unterscheidet.

final class java.rmi.Naming

gp  static Remote lookup( String name )
throws NotBoundException, MalformedURLException, RemoteException
Liefert eine Referenz auf den Stub, der mit dem entfernten Objekt name verbunden ist. War kein Dienst unter dem Namen verfügbar, kommt es zu einer NotBoundException. Ist der Namensdienst nicht erreichbar, folgt eine RemoteException. MalformedURLException kann durch eine falsch gebildete URL folgen.
gp  static String[] list( String name )
Liefert ein Feld mit angemeldeten Diensten. Der angegebene Name gibt die URL des Namensdienstes an. Ist die URL falsch konstruiert, so folgt eine MalformedURLException; ist die Registry nicht erreichbar, folgt eine RemoteException.

Galileo Computing

19.9.1 Einfaches Logging  downtop

Um die Aktivität von RMI verfolgen zu können, haben die Entwickler einen einfachen Loggin-Mechanismus eingebaut. Er gibt Auskunft über die Objekte und entfernte Referenzen. Hier erfahren wir auch, ob alle gewünschten Objekte korrekt gefunden wurden. Das Logging lässt sich mit der Eigenschaft java.rmi.server.logClass einschalten, wenn der Wert auf true gesetzt ist. Dann erscheinen Ausgaben auf dem System.err-Fehlerkanal. Erweitern wir die Klasse RemoteServer, so erben wir zudem eine statische Funktion setLog(OutputStream), mit dem sich der Fehlerausgabestrom individuell setzen lässt. Mittels getLog(), der einen PrintStream liefert und keinen OutputStream, gelangen wir wieder an den Fehlerkanal.

abstract class java.rmi.server.RemoteServer 
extends RemoteObject

gp  static void setLog( OutputStream out )
Loggt RMI-Aufrufe, in dem sie in den Ausgabestrom out geschrieben werden. Ist out=null, wird das Logging beendet.
gp  static PrintStream getLog()
Liefert den Ausgabestrom für das RMI-Logging.

Galileo Computing

19.10 Aufräumen mit dem DGC  downtop

Im verteilten Fall reicht der normale GC nicht, und das Konzept muss um einen verteilten GC (engl. distributed GC, kurz DGC) erweitert werden. Im lokalen Fall weiß die lokale Maschine immer, ob ein Objekt referenziert wird, im verteilten Fall kann auf dem Server ein Objekt existieren, für das sich kein Mensch mehr interessiert. Damit im verteilten Fall auch der GC nicht mehr benutzte Objekte auf der Server-Seite freiräumen kann, verschickt die Maschine beim Nutzen und Lösen von Verbindungen referenced bzw. dereferenced Meldungen. Ist die Verbindung dann gelöst, bleibt die Klasse jedoch noch einige Zeit auf dem Server und wird nicht sofort gelöst. Aussagen über die Verweildauer gibt die Lease an, die sich über eine Eigenschaft verändern lässt.

Beispiel Setze die Verweildauer für Objekte auf eine halbe Stunde hoch
java -Djava.rmi.dgc.leaseValue=30

Die Standarddauer ist auf 10 Minuten gesetzt.



Galileo Computing

19.11 Entfernte Objekte übergeben und laden  downtop

In unserem bisherigen Beispiel haben wir zwei Ganzzahlwerte übergeben. Die Implementierung der Stellvertreter ist nun so, dass eine Socket-Verbindung die Daten überträgt. Da keine Objekte transportiert werden, muss keine Serialisierung die Daten in Reihe liefern. Wir wollen uns nun damit beschäftigen, was mit Objekten passiert, die übertragen werden. Wir können verschiedene Klassen unterscheiden:

gp  Klassen, die auf beiden Seiten vorliegen, weil es zum Beispiel Klassen aus dem Standard-API sind.
gp  Klassen, die nur auf der Server-Seite vorliegen und dem Client nicht bekannt sind.
gp  Klassen, die selbst wieder Remote implementieren.

Liegt die Klasse auf beiden Seiten als Klassenbeschreibung vor, da sie etwa eine Standard-Klasse ist oder in beiden Pfaden eingetragen ist, müssen wir mit keinen Problemen rechnen. Die übertragenen Daten müssen jedoch von Klassen stammen, die serialisierbar sind.

Wann eine Klassenbeschreibung nötig ist

Schwierig wird die Lage erst dann, wenn der Server Klassen benötigt, die beim Client liegen. Es könnte etwa eine entfernte Methode

int max( Vector v );

geben, die das Maximum der Elemente aus dem Vector bildet. Die Elemente sind jedoch Objekte, die der Server vorher nicht gesehen hat, etwa Unterklassen von Konto im Vector, und der Server kennt nur Konto und die Methoden davon, bindet aber dynamisch an die Methode der Unterklasse.


Galileo Computing

19.11.1 Klassen vom RMI-Klassenlader nachladen  downtop

Wir kommen also dazu, dass der Klassenlader Klassen nachladen muss, die für den verteilten Aufruf auf der Client- und Server-Seite nötig sind. Das erinnert an einen Applet-Klassenlader, der Gleiches machen muss. Für RMI-Aufrufe kommt der RMI-Klassenlader (java.rmi.RMIClassLoader) zum Zuge. Dieser Lader lädt jetzt die Stellvertreterobjekte sowie die benötigten Klassen in die lokale virtuelle Maschine. Woher die Klassen kommen, ist dem Lader egal. Sie können in CLASSPATH stehen, im aktuellen Verzeichnis oder auf einem Webserver. Im letzten Fall steuert die Eigenschaft java.rmi.server.codebase den Ort.

Beispiel Setzen der Codebase auf einen Webserver, damit die RMI-Programme die benötigten Klassen aus http://server/classimlp laden können.
java -Djava.rmi.codebase=http://server/classimlp

Sollten die Klassen nur vom Server geladen werden und aus anderen, vielleicht dunklen Stellen, so ist die Eigenschaft java.rmi.useCodebaseOnly auf true zu setzen.


Galileo Computing

19.11.2 Sicherheitsmanager  downtop

Damit die Klassen nicht auf dem Client oder Server liegen müssen, können sie nachträglich über den RMI-Klassenlader geladen werden. Doch das Laden von Klassen muss erst abgesegnet werden. Die Erlaubnis, ob Klassen übertragen werden dürfen, regelt ein spezieller Sicherheitsmanager. Die Klasse RMISecurityManager definiert eine Sicherheitsrichtlinie, dass serialisierbare Klassen von einem Rechner auf den anderen übertragen werden können. Wenn wir mit primitiven Werten arbeiten, wie in unserem ersten Beispiel, oder mit Standardklassen, ist dieser RMISecurityManager nicht nötig. Da wir aber serialisierbare Klassen übertragen müssen, ist der Sicherheitsmanager vorgeschrieben. Da Applets schon vom Applet-Sicherheitsmanager überwacht werden, können sie keinen zusätzlichen RMISecurityManager installieren.

Beispiel Ein RMISecurityManager ist nichts anderes als ein SecurityManager.
public class RMISecurityManager 
extends SecurityManager {
public RMISecurityManager() {
}

Wenn wir folgende Zeile in unserem Servercode aufnehmen, wird RMI vom Klassenlader die Klassen laden können:

System.setSecurityManager( new 
RMISecurityManager() );

Erst der Sicherheitsmanager gibt uns das Recht für die Übertragung. Tragen wir ihn nicht ein, so führt es zu einer Fehlermeldung der folgenden Art:

java.security.AccessControlException:
access denied
(java.net.SocketPermission 127.0.0.1:1099 connect,resolve)

Die Meldung zeigt an, dass die aktuellen Sicherheitsrichtlinien die Übertragung nicht zulassen. Häufig ist es so, dass die Java-Installationen Sicherheitsrichtlinien vorgegeben, die sehr eingeschränkt sind.

Um die Richtlinien zu lockern, müssen wir eine Policy-Datei anlegen, die uns die Rechte zum Laden von Klassen gibt.

Beispiel Die Datei rmi.policy gibt Rechte für alle Dateien.
grant
{
permission java.security.AllPermission;
};

Der Sicherheitsmanager bindet nun eine Reihe dieser Policy-Dateien ein. Wollen wir zusätzliche Policies einbinden, so geben wir sie auf der Kommandozeile für den Java-Interpreter an.

java -Djava.security.manager -Djava.security.policy=rmi.policy 
MyClass

Die Option -D setzt Systemeigenschaften. Die Anweisung -Djava.security.manager hat den gleichen Effekt wie

System.setSecurityManager( new 
SecurityManager() );

Dies installiert einen Sicherheitsmanager, der dann die nachfolgende Policy behandelt. Sie ist als URL angegeben und in unserem Fall eine Datei im aktuellen Verzeichnis.

Beispiel In einem Verzeichnis befindet sich die Policy-Datei rmi.policy und die Klassen für RMI. Über die Systemeigenschaften lässt sich codebase und policy setzen.
String codebase = "http://server/verzeichnis/RMI/";
System.setProperty( "java.rmi.server.codebase", codebase );
System.setProperty( "java.security.policy", codebase+"rmi.policy" );
System.setSecurityManager( new RMISecurityManager() );

Tipp Die Sicherheitsrichtlinie sollte wohl überlegt erfolgen. Lädt der Server jeden Stub bzw. Skeleton oder jede Klasse vom Client, hat er es natürlich einfach Serverfunktionalität auszuführen. Schmuggelt er miesen Programmcode ein, kann dies großen Schaden anrichten, da der Server häufig über mehr Rechte als der Client verfügt.


Galileo Computing

19.12 Registry wird vom Server gestartet  downtop

In der Regel wird der Namensdienst, also die Registry, immer von Hand gestartet. Mitunter ist es praktisch, diesen nicht manuell vor dem Serverdienst zu starten, sondern so, dass der Server diesen automatisch startet. Das hat den Vorteil, dass bei nicht gestartetem Namensdienst (der Benutzer hat es zum Beispiel vergessen) der Dienst angeboten werden kann. Möglich ist dies, da rmiregistry auch nur ein RMI-Java-Programm ist, und die Funktionalität für den Namensdienst über eine Klasse geht. Sie heißt LocateRegistry und bietet nur statische Methoden an. Darunter befindet sich die Methode createRegistry(), mit der ein Namensdienst gestartet wird.

import java.rmi.registry.*;
...
try {
LocateRegistry.createRegistry( Registry.REGISTRY_PORT );
}
catch (java.
rmi.RemoteException e) { ... }

Anschließend ist der Namensdienst gestartet und der Server kann seine Dienste dort anmelden. Problematisch wird dies allerdings dann, wenn das Programm mit dem Server beendet wird, in der Zwischenzeit jedoch noch ein anderer Server seinen Dienst eingetragen hat.


Galileo Computing

19.13 RMI über die Firewall  downtop

Bei der Kommunikation der beiden Partner gibt es eine direkte Verbindung über Socket-Objekte. Diesen Sockets wird ein Port zugewiesen, sodass RMI die serialisierten Daten dann über diese zugewiesenen Ports überträgt. Dies macht jedoch Probleme mit einer Firewall, die ein internes Firmennetz schützen möchte, und nur genau spezifizierte Ports, Protokolle und Richtungen offen lässt. Soll eine RMI-Lösung für ein abgeschottetes Netz entwickelt werden, stellt sich die Frage nach der einzusetzenden Technik.


Galileo Computing

19.13.12 RMI über HTTP getunnelt  downtop

HTTP dient normalerweise zum Übertragen der Daten von Webserver und Browser. RMI bietet zum Übertragen der Daten eine Lösung über HTTP an. Ist das Internet-Protokoll installiert, so lässt die Firewall die Anfragen und Antworten der Internet-Partner passieren – normalerweise auf Port 80. Die RMI-Lösung macht es sich zu nutze, dass die Daten in spezielle HTTP-Pakete eingepackt (getunnelt) werden. Dazu nutzt das Tunneling-Protokoll veränderte POST-Kommandos. Die Transportschicht des Clients generiert dann eine POST-Anfrage, wobei hier zwei unterschiedliche Verfahren zum Einsatz kommen.

gp  Der Sender schickt direkt die Anfrage an den RMI-Server, der an dem Port horcht. Dieser nimmt dann aus dem POST-Paket die Daten heraus und interpretiert sie. Das wäre eine Lösung, wenn es hinter dem Sender eine Firewall gibt, aber nicht vor dem Empfänger. In dem Fall, in dem Sender und Empfänger geschützt sind, hilft ein zweites Verfahren.
gp  Der Transportmechanismus arbeitet vollständig über HTTP-Anweisungen. Dann antwortet auf der Server-Seite der Webserver, der die Anfragen weiterleiten muss. Dazu dient ein CGI-Skript, welches die Daten wiederum zum RMI-Server hoch reicht.

Auf diese Weise werden die Objekte übertragen, allerdings mit einer Verzögerung durch das zusätzliche Verpacken und dem eventuellen Aufruf des CGI-Skripts.

Das schöne bei der Lösung ist, dass der Client dies gar nicht mitbekommt und nicht besonders konfiguriert werden muss. Soll das Verfahren ausdrücklich verboten werden, ist die Eigenschaft java.rmi.server.disableHttp auf true zu setzen.

Links:

gp  http://developer.java.sun.com/developer/onlineTraining/rmi/RMI.html

Galileo Computing

19.14 Java API für XML Messaging (JAXMdowntop

Die Java API für XML Messaging (JAXM) (http://java.sun.com/xml/jaxm/index.html) dient zum Austausch von Daten über das XML-basierte SOAP Protokoll über die Protokolle HTTP, SMTP und FTP. Während JAXM lediglich eine API ist, bietet Sun auch eine Implementierung an, die unter dem Codenamen »The M Project« bekannt ist. Die Implementierung nutzt zur Übertragung SOAP in der Version 1.1 und kann über Suns Java Developer Connection (http://developer.java.sun.com/developer/earlyAccess/xml/jaxm) heruntergeladen werden. Mithilfe von JAXM kann der Entwickler XML-Daten, etwa Buchdaten bei einer Buchbestellung und eine Vorschaugrafik, einfach übertragen, ohne sich über das Client- und Server-Protokoll Gedanken machen zu müssen. Intern arbeitet JAXM mit JDOM, ein gutes Signal, dass auch JDOM bald zur Standard-API gehören wird. Neben SOAP sind andere Methoden zur Übertragung geplant, wie ebXML (Electronic Business XML initiative), ein Framework von OASIS (the Organization for the Advancement of Structured Information Standards) und UN/CEFACT (United Nations Center for Trade Facilitation and Electronic Business).

Weitere lesenswerte Links sind:

gp  http://java.sun.com/features/2000/12/xml-java.p.html
gp  http://java.sun.com/xml/jaxm-0_9_2-prd-spec.pdf
gp  http://www.oasis-open.org/cover/mProject.html

Galileo Computing

19.15 Java Message Service (JMStoptop

Der Java Message Service (JMS) (http://java.sun.com/products/jms/index.html) ist Teil der J2EE und dient zum asynchronen Austausch von Daten. JMS unterstützt zwei unterschiedliche Ansätze zum Verschicken von Nachrichten, zum einen die Nachrichtenschlangen (Message Queues), und zum anderen ein Anmelde-Versendesystem (Publish-Subscribe). Bei gerichteten Punkt-zu-Punkt-Verbindungen (Point-to-Point (PTP) Systeme) sendet ein Client eine Nachricht an einen Empfänger, der die Nachricht in einer Queue einreiht. JMS definiert, wie der Client Nachrichten verschicken kann und wie die Nachrichten aus der Schlange genommen werden. Beim Anmeldesystem interessiert sich der Client für bestimmte Nachrichten, die dann automatisch verteilt werden. Diese Nachrichten heißen in JMS Topics.

Jede Nachricht, die JMS versendet, besteht aus einem Kopf (Header), einigen Eigenschaften (Properties) und dem Körper (Body). Der Header enthält Felder mit Angaben zu Identität und nötige Informationen, damit die Nachricht vermittelt werden kann (Routinginformationen). Die Properties sind zusätzliche Felder, die jede Applikation selbst bestimmen kann. Der Datenteil bildet der Körper, der aus unterschiedlichen Formaten bestehen kann: Einem Datenstrom, der sequenziell verarbeitet wird, Schlüssel-Werte-Paare, Text, ein Java-Objekt oder uninterpretierte Bytes.

Obwohl ursprünglich JMS ein reiner Java-Dienst war, gibt es die ersten Java Message Services für C++ etwa von der Firma Codemesh. Mit dem Produkt JMS Courier können C++-Applikationen die Java Messaging Services einsetzen.

Weitere Informationen finden sich unter:

gp  http://java.sun.com/products/jms
gp  http://java.sun.com/products/jms/docs.html
gp  http://www.javaworld.com/javaworld/jw-02-1999/jw-02-howto.html
gp  http://www.codemesh.com/en/JMSPressReleaseMay01.html
gp  http://www.swiftmq.com
gp  http://www.execpc.com/~gopalan/jms/jms.html
  

Java 2




Copyright © Galileo Press GmbH 2002
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, fon: 0228.42150.0, fax 0228.42150.77, info@galileo-press.de