Advanced   Java   Services RMI  mit UnicastRemoteObject Back Next Up Home


RMI mit UnicastRemoteObject

Wir übertragen nun unser Beispiel auf eine echte Client-Server Situation und werden sehen, daß wir fast die gleichen Schritte durchführen. Wir zeigen die Kommunikaion zweier JavaApplikationen auf zwei verschiedenen Rechnern. Ein Client nimmt Kontakt zu einem Server auf und erhält ein RemoteObjekt, das auf der anderen Maschine existiert. Zu diesem Objekt kann er Methoden aufrufen, so als ob das Objekt auf seinem Rechner existiert. Realisiert wird dies im Hintergrund durch eine Socketverbindung zwischen Client und Server. Bei Benützung der durch die API zur Verfügung gestellten Klassen und Interfaces muß man diese Socketverbindung nicht selbst einrichten. Es genügt, bestimmte Interfaces zu implementieren. Den Rest erledigen die Klasse Naming und das Interface Registry (bzw. die von Sun gelieferte Implementierung dieses Interfaces). Wir haben also eine Situation, die wir an dem einleitenden Beispiel schon kennengelernt haben. Wir implementieren wieder einen einfachen Datumsserver.


Ausgangssituation

Zu einem RemoteObjekt soll clientseitig eine Methode getDate() aufgerufen werden, die das aktuelle Datum auf dem Server liefert. Für unser Beispiel brauchen wir eine IP-Adresse für den Server, wir nehmen die folgende.

Server IP-Adresse:  192.168.11.22

RemoteInterface (KommunikationsInterface für Server und Client)

Das Kommunikationsinterface heißt nun RemoteInterface und es muß die folgenden Bedingungen erfüllen.

Das folgende einfache Interface erfüüllt die Bedingungen.

import java.util.Date;
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface DateRemoteInterface extends Remote
{
   Date getDate() throws RemoteException ;
}

Implementierung des RemoteInterfaces (nur serverseitig)

Für die Implementierung des RemoteInterfaces gelten folgende Bedingungen:

Die letzte Bedingung ist natürlich selbstverständlich. Die Klasse kann darüber hinaus zusätzliche Methoden haben. Auf diese kann jedoch später clientseitig nicht zugegriffen werden, da sie ja dem RemoteInterface nicht angehören, der Client kann sie also nicht kennen.

Für unser Interface sieht die Implementierung so aus.

import java.util.*;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject ;



public class DateRemoteInterfaceImpl extends UnicastRemoteObject
   implements DateRemoteInterface
{
   // Defaultkonstruktor muß RemoteException werfen
   public MyRemoteImpl() throws RemoteException
   {
   }

   // vom Interface geforderte Methode
   public Date getDate() throws RemoteException
   {
      return new Date();
   }
}

Die Serverklasse

Wir brauchen nun einen kleinen Webserver, der ein Objekt vom Typ des RemoteInterfaces zur Verfügung hat, der für einen bestimmten Port konfiguriert ist und der darauf wartet, daß ein Client Verbindung aufnimmt. Dahinter verbirgt sich natürlich ein ServerSocketobjekt, daß diese Aufgabe übernimmt. Mit Hilfe der Klassen LocateRegistry und Naming reduziert sich der Aufwand auf einen analogen Vorgang wie in unserem Eingangsbeispiel.

Damit sieht unsere Serverklasse wie folgt aus.

import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;

public class RMIDateServer
{
   public static void main(String args[])
   {
      try
      {
         System.out.println("create RemoteObject");
         DateRemoteInterfaceImpl remObj = new DateRemoteInterfaceImpl();
         // wirft java.rmi.RemoteException

         Registry registry = LocateRegistry.createRegistry(1099);
         // creates a server on the local host that accepts requests on the specified port.
         // wirft java.rmi.RemoteException

         // Remoteobjekt registrieren und Server starten
         System.out.println("try : naming RemoteObject");
         Naming.bind("DateServer", remObj);
         
         // wirft java.rmi.AlreadyBoundException
         // wirft java.net.MalformedURLException
         // wirft java.rmi.UnmarshalException
         // = Unterklasse von RemoteException, RuntimeException
         System.out.println("-- server ready, waiting for clients --");

         System.out.println("stop server ?");
         Scanner sc = new Scanner(System.in);
         String s = sc.next();
         if( s.toLowerCase().equals("j"))
         {
            reg.unbind("DateServer");
            System.exit(0);
         }

      }
      catch(Exception ex)
      {
         ex.printStackTrace();
      }
   }
}

Binden über die Registry

An Stelle der Klasse Naming kann man auch den Returnwert von LocateRegistry.createRegistry() verwenden um eine Namenszuordnung zu erreichen. Der Returntyp java.rmi.registry.Registry ist ein Interface, das ebenfalls eine Methode bind() zur Verfügung stellt.

registry.bind("DateServer", remoteObject);

Der Bindungsvorgang ist eine Namensregistrierung. Für das Objekt remoteObject wird der Name DateServer eingetragen. Gibt der Client beim Verbindungsaufbau diesen Namen an, so kann der Server an Hand des Namens das zugehörige Objekt finden.


Clientklasse

Der Vorgang für den Client ist sehr einfach. Mit Hilfe der IP-Addresse, der Portnummer und des vom Server vergebenen Namens "DateServer" erhält er ein Objekt vom Typ DateRemoteInterface und kann damit die im Interface vereinbarten Methoden rufen.

Der Client im Code

import java.rmi.*;

public class RMIDateClient
{
   public static void main(String args[])
   {
      try
      {  
         DateRemoteInterface remRef = (DateRemoteInterface)Naming.lookup("//192.168.11.22:1099/DateServer");
         // wirft NotBoundException und MalformedURLException
         System.out.println("Date = " + remRef.getDate() ) ;
         // wirft RemoteException
      }
      catch(Exception ex)
      {
         System.out.println("Exception = " + ex ) ;
      }
   }
}

Alternative Clientklasse

Die Klasse Naming ist lediglich eine Hilfsklasse. Mit ihr erhält man in einer Zeile ein Objekt vom Typ DateRemoteInterface. Verwendet man die Klasse Naming nicht, so wird das Vorgehen auf der Clientseite symmetrisch zum Code der alternativen Serverklasse:


Seit Java 1.5 : Konsoltool rmic nicht mehr notwendig

Vor der Version 1.2 war es notwendig mit dem Konsoltool zwei weitere Klassen zu erzeugen, Die Stub- und die Skeletonklassen. Ab der Version 1.2 entfiel das Erzeugen der Skeletonklassen und ab der Version 1.5 sind auch die Stubklassen nicht mehr notwendig. Falls man mit älteren Java-Versionen arbeiten muß hier noch die Aufrufe des Konsoltools rmic


Das Konsoltool rmic erzeugt die Stubklasse (und die Skeletonklasse)

In unserem Fall heißt die fehlende Klasse DateRemoteInterfaceImpl_Stub.class. Sie stellt eine Art Aufbereitung der Klasse DateRemoteInterfaceImpl.class dar, um mit Hilfe von Reflection und Serialisierung den Datenaustausch zwischen Client und Server zu ermöglichen. Diese Klasse brauchen wir nicht selber zu erstellen, es gibt ein Hilfstool mit dem Namen rmic (rmi-compiler), das uns diese Arbeit abnimmt. Um mit früheren JDK-Versionen abwärtskompatibel zu bleiben, erzeugt rmic auch eine inzwischen überflüssig gewordene sog. Skeletonklasse DateRemoteInterfaceImpl_Skel.class. Seit JSDK 1.2 ist nämlich das Interface Skeleton deprecated und ersatzlos gestrichen worden. Die Datei DateRemoteInterfaceImpl_Skel.class wurde früher serverseitig gebraucht. rmic wird aufgerufen von der Konsole in dem Verzeichnis, in dem sich die Klasse DateRemoteInterfaceImpl befindet, rmic erzeugt damit die beiden Klassen DateRemoteInterfaceImpl_Stub.class und DateRemoteInterfaceImpl_Skel.class im selben Verzeichnis.


Aufruf von rmic

Hat man den Pfad zum bin-Verzeichnis des JDK der Betriebssystemvariablen Path ergänzt, so kann man rmic von jedem Verzeichnis aufrufen. Hat man keine Packages angelegt, so ruft man rmic in dem Verzeichnis auf, in dem die Datei DateRemoteInterfaceImpl.class liegt wie folgt auf.

rmic DateRemoteInterfaceImpl

Der Name der Stubklasse (und der Skeletonklasse)

rmic vergibt die Namen für diese Klassen automatisch. Der Name der Stubklasse entsteht aus dem Namen der implementiereden Klasse durch Anhängen von "_Stub".

Name der implementierenden KlasseName der Stubklasse
XxxImplXxxImpl_Stub

Dieser Name darf nicht geändert werden, sonst kann zu diese Klasse kein Objekt mehr angelegt werden (vgl. das weiter unten folgende Sequenzdiagramm).

Wenn wir nun DateRemoteInterfaceImpl_Stub sowohl beim Client deponieren, dann sollte das Beispiel laufen.


Aufruf von rmic, falls Packages angelegt worden sind

In der Praxis legt man den Server (und auch den Client) in ein Package. In diesem Fall muß man rmic im Verzeichnis über dem Packageverzeichnis aufrufen, also

rmic hms.DateRemoteInterfaceImpl

Was liefert Naming.lookup() bzw. registry.lookup()

Der Aufruf von Naming.lookup() liefert (natürlich) ein Objekt der Klasse, die das Interface DateRemoteInterface implementiert hat. Es ist dies ein Objekt der Klasse DateRemoteInterfaceImpl_Stub, also genau derjenigen Klasse, die durch rmic erzeugt wird.


Client-Server Modell

Die folgende Graphik ist eine schamtische Darstellung der vorherigen Erläuterungen.

rmi-cs-modell.jpg


Die Remote Hierarchie

Die folgende Graphik zeigt die Elternklassen bzw. ElternInterfaces von DateRemoteInterfaceImpl und DateRemoteInterfaceImpl_Stub an.

RMI-Hierarchie.jpg

Man könnte hier vermuten, daß "extends Remote" im Interface DateRemoteInterface überflüssig ist, da die Klassen DateRemoteInterfaceImpl und DateRemoteInterfaceImpl_Stub dieses (tagging-) Interface sowieso über die entsprechenden Vererbungslinien implementieren. rmic verweigert aber seinen Dienst, wenn die Klasse DateRemoteInterfaceImpl dieses Interface nicht implementiert und gibt die folgende Fehlermeldung aus:

error: Stubs are only needed for classes that directly implement an interface that extends
java.rmi.Remote;

class DateRemoteInterfaceImpl does not directly implement a remote interface.

Die Instanziierung des Stub-Objektes (Sequenzdiagranmm)

Das folgende leicht vereinfachte Sequenzdiagramm zeigt die Vorgänge beim Instanziieren eines Objektes vom Typ der Implementierung DateRemoteInterfaceImpl. Die nichtbeschrifteten Pfeile stellen Konstruktoraufrufe dar und zeigen auf das Objekt, das sie instanziieren. Die beschrifteten Pfeile sind Methodenaufrufe und zeigen auf ihren eigenen Methodenrumpf.

RMI-Sequenz.jpg

Man sieht hier, daß die Instanziierung des Objektes vom Typ DateRemoteInterfaceImpl die Instanziierung des Stub-Objektes voraussetzt.

Valid XHTML 1.0 Strict top Back Next Up Home