IT-Academy Logo
Sign Up Login Help
Home - Programmieren - Java - Java RMI



Java RMI

Eine kurze Einführung in Java RMI anhand eines kleinen Beispiels.


Autor: Patrick Bucher (paedubucher)
Datum: 27-01-2006, 11:37:09
Referenzen: siehe Ende des Dokuments
Schwierigkeit: Fortgeschrittene
Ansichten: 14982x
Rating: 8 (1x bewertet)

Hinweis:

Für den hier dargestellte Inhalt ist nicht der Betreiber der Plattform, sondern der jeweilige Autor verantwortlich.
Falls Sie Missbrauch vermuten, bitten wir Sie, uns unter missbrauch@it-academy.cc zu kontaktieren.

[Druckansicht] [Als E-Mail senden] [Kommentar verfassen]



Java RMI

© 2006 by Patrick Bucher

Dieser Artikel soll die Grundlagen von RMI anhand eines Beispiels erläutern.



Was ist RMI?

RMI steht für Remote Method Invocation, was auf deutsch ungefähr soviel bedeutet wie entfernter Methodenaufruf. Es geht darum, eine Methode eines Objekts aufzurufen, welches auf einem entfernten Rechner existiert. Dabei kommt es nich darauf an, ob dieses Objekt auf einem anderen (physischen) Rechner oder auf einer anderen Java Virtual Machine läuft. Es ist auch möglich, dass das Objekt auf dem gleichen Rechner innerhalb der gleichen Java Virtual Machine existiert.

RMI ist fester Bestandteil der Java 2 Standard Edition (J2SE). Dieser Artikel bezieht sich auf das JDK 1.5, womit auch die Beispielapplikation erstellt wurde.

Bei einer RMI-Anwendung braucht sich der Programmierer nicht konkret um die Netzwerkprogrammierung zu kümmern, darum kümmert sich RMI selber. Genauer kümmern sich sogenannte Stub-Klassen um die Netzwerkkommunikation, welche mit dem JDK-Werkzeug rmic erstellt werden können.



Ablauf

Ein entfernter Methodenaufruf mit RMI erfolgt in folgenden vier Schritten:

  1. Der Server registriert ein sogenanntes Remote Object in der RMI Registry. Das Objekt muss unter einem eindeutigen Namen registriert sein.

  2. Der Client besitzt ein bestimmtes Interface, welches durch das Remote Object implementiert wird. Nun schaut der Client in der RMI Registry nach, ob das entsprechende Objekt vorhanden ist und erstellt eine Objektreferenz darauf.

  3. Nun ist der Client in der Lage eine Methode des Remote Objects aufzurufen, welches sich auf dem Server befindet. Verlangt die Methode Parameter, so werden diese über das Netzwerk übertragen.

  4. Der Server kann dem Client nun den Rückgabewert der Methode zurückschicken oder eine Exception werfen.

Ist einmal eine Objektreferenz auf das Remote Object erstellt, so spielt es keine Rolle mehr wo sich das Objekt befindet - der Zugriff sieht für den Programmierer so aus, als ob sich die Klasse auf seinem Rechner befinden würde.







Ein einfaches Beispiel

Genug der Theorie, jetzt geht es an die Programmierung. Es soll eine Beispielanwendung erstellt werden, welche nach folgendem Muster ablaufen soll:

  1. Der Client sendet zwei Zahlen an den Server

  2. Der Server berechnet die Summe dieser beiden Zahlen

  3. Die Summe wird dem Client zurückgegeben

Die Applikation soll also demonstrieren, wie sich Rechenlast auf einen Server abwälzen lässt. In diesem Beispiel hat der Client zwar keinen Performance-Gewinn, bei grösseren Anwendungen wird der Client jedoch massiv entlastet.

Dabei soll der Rechner mit der IP-Adresse 192.168.1.33 als Server dienen, während dem der Rechner mit der IP-Adresse 192.168.1.34 die Rolle des Clients übernimmt. Diese IP-Adressen sind im Programmcode den jeweiligen lokalen Gegebenheiten anzupassen.



Aufbau

Bei einer RMI-Anwendung muss sich (wie oben beschrieben) auf dem Client ein Interface befinden, welches durch das Remote Object implementiert wird. Anders ausgedrückt muss der Client also nur wissen, wie das Remote Object aufgebaut ist, nicht aber welche Logik beim Methodenaufruf ausgeführt wird.

Die eigentliche Logik befindet sich also auf dem Server innerhalb des Remote Objects.

Die Beispielapplikation wird wie folgt aufgebaut:

  1. Package common
    Dieses Java-Paket soll gemeinsam verwendete Java-Elemente enthalten. Bei diesem Beispiel ist dies das Interface, welches durch das Remote Object implementiert wird.
    Der Name common kommt daher, da dieses Package dem Server wie auch dem Client ausgeliefert werden müssen.

  2. Package client
    Hier soll sich die Client-Klasse befinden, welche eine Verbindung mit dem Remote Object erstellt, eine Methode auf dem Server aufruft und schlussendlich das Ergebnis der Berechnung ausgibt.
    Dieses Package ist nur auf dem Client vorhanden.

  3. Package server
    In diesem Package soll sich die Implementierung unserer Serverklasse befinden.
    Dieses Package ist nur auf dem Server vorhanden.

Im folgenden werden nun die einzelnen Klassen bzw. Interfaces ausprogrammiert und erläutert:



Interface Summierer

package common;

import java.rmi.Remote;
import java.rmi.RemoteException;

// 1
public interface Summierer extends Remote
{
  // 2
  int summieren(int a, int b) throws RemoteException;
}

  1. Unser Interface heisst Summierer und erbt vom Interface Remote. Letzteres ist zwingend für die Verwendung von RMI.

  2. Das Interface enthält nur eine einzige Methode, die Methode summieren. Diese erwartet zwei Variablen des Datentyps int und gibt ebenfalls einen int zurück. Diese Methode muss zwigend eine RemoteException werfen können!



Klasse Server

package server;

import java.rmi.Naming;
import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

import common.Summierer;

// 1
public class Server extends UnicastRemoteObject implements Summierer
{
  public Server() throws RemoteException
  {
  }

  public static void main(String[] args)
  {
    // 2
    System.setSecurityManager(new RMISecurityManager());
    try
    {
      // 3
      Server server = new Server();

      
// 4
      Naming.bind("rmi://192.168.1.33:1010/server", server);

    }
    catch(Exception ex)
    {
      System.out.println(ex.getMessage());
    }
  }

  public int summieren(int a, int b) throws RemoteException
  {
    // 5
    return (a + b);
  }
}

  1. Diese Klasse muss von der Klasse UnicastRemoteObject erben. Ausserdem muss sie das Interface Summierer implementieren, welches dem Client auch zur Verfügung steht.

  2. In der main-Methode muss der RMISecurityManager gesetzt werden, welcher hier als neue Instanz übergeben wird.

  3. Im try-Block wird zunächst eine neue Instanz des Remote Objects erstellt. Dabei handelt es sich um das Objekt, das später dem Client zugänglich sein wird. Das Exception-Handling ist hier zwingend!

  4. Nun wird das Remote-Object in die RMI-Registry eingetragen. Die statische Klasse Naming steht dabei für die RMI-Registry. Mit der Methode bind() kann nun die Klasse registriert werden. Als erster Parameter wird die URL zum Objekt angegeben. Das Protokoll lautet rmi, die IP-Adresse ist diejenige unseres Servers. Als Port verwenden RMI-Anwendungen 1010, obwohl andere Ports auch möglich sind. Am Schluss der URL wird ein eindeutiger Objektname angegeben, unter welchem unser Remote Object nun abgerufen werden kann.

  5. Hier steht die Logik unserer Klasse. Es werden einfach die beiden Zahlen addiert und zurückgegeben.



Klasse Client

package client;

import java.rmi.Naming;
import java.rmi.RMISecurityManager;

import common.Summierer;

public class Client
{
  public static void main(String[] args)
  {
    // 1
    System.setSecurityManager(new RMISecurityManager());
    try
    {
      // 2
      Summierer summierer = (Summierer)Naming.lookup("rmi://192.168.1.33:1010/server");

      
// 3
      int result = summierer.summieren(16, 35);

      
// 4
      System.out.println(result);

    }
    catch(Exception ex)
    {
      System.out.println(ex.getMessage());
    }
  }
}

  1. Wie beim Server muss auch beim Client der Security-Manager gesetzt werden.

  2. Nun suchen wir unser Remote-Object auf dem Server mit Naming.lookup(). Als Parameter geben wir die URL zu unserem Remote Object an, dabei handelt es sich um den gleichen String wie wir ihn beim Server unter dem Punkt 4 angegeben haben! Da diese Methode ein Objekt des Typs Remote zurückgibt, ist hier ein Casting notwendig.

  3. Hier steht der eigentliche Kernpunkt unserer Applikation, wird rufen die Methode summieren auf. Als Parameter werden dem Server die bieden Zahlen 16 und 35 übergeben.

  4. Zum Schluss geben wir das Ergebnis unserer Berechnung noch aus.



Kompilierung

Nun wollen wir unsere Applikation in Bytecode überführen und die oben genannte Stub-Klasse erstellen, welche den Netzwerkverkehr übernimmt.

Die Variable $JDK_HOME steht für den Java-Installationspfad. Diese Variable kann auf der Konsole einfach durch den Installationspfad ersetzt werden oder man gibt eine Umgebungsvariable an, die auf das entsprechende Verzeichnis zeigt.

Als erstes kompilieren wir einfach all unsere java-Dateien mit javac, dabei müssen wir uns zwingend im Projekt-Wurzelverzeichnis befinden!

$JDK_HOME/bin/javac client/Client.java common/Summierer.java server/Server.java

Die Stub-Klasse wird dann anhand der Server-Klasse mit dem Programm rmic erstellt, welches Bestandteil des JDKs ist:

$JDK_HOME/bin/rmic server.Server

Im Gegensatz zu javac geben wir hier keine eigentliche Datei, sondern den Pfad auf eine Klasse an. Darum wird der Slash durch einen Punkt ersetzt und die Dateiendung weggelassen.



Auslieferung

Unsere beiden Applikationen, Server und Client, wollen wir nicht einfach als lose Klassen ausliefern. Wir erstellen deshalb für jede Applikation ein jar-File.

Damit java weiss, in welcher Klasse sich die main-Methode der jeweiligen Applikation befindet, müssen wir dies in einem sogenannten manifest angeben. Wir erstellen also eine einfache Textdatei namens manifest in unserem Projekt-Wurzelverzeichnis. Der Inhalt für den Client sieht dabei folgendermassen aus:

Manifest-Version: 1.0
Main-Class: server.Server

Die erste Zeile gibt die Version der Datei an, in der zweiten geben wir die eigentliche Hauptklasse an, welche wir mit dem Packagenamen qualifizieren müssen (server.Server).

Der Server wird folgendermassen in ein jar-File gepackt:

$JDK_HOME/bin/jar cfm Server.jar manifest server/Server.class server/Server_Stub.class common/Summierer.class

Das neu erstellte jar-File kann nun in das Dateisystem unseres Servers verschoben werden.

Für die Erstellung eines Client-jar-Files müssen wir die Manifest-Datei ändern:

Manifest-Version: 1.0
Main-Class: client.Client

Die main-Methode des Clients befindet sich ja schliesslich in der Klasse Client, welche sich widerum im Package client befindet.

Wichtig: Wir müssen unserem Client unbedingt die Server_Stub-Klasse mitliefern!

$JDK_HOME/bin/jar cfm Client.jar manifest client/Client.class server/Server_Stub.class common/Summierer.class

Die Datei Client.jar muss nun noch auf den Client verschoben werden.



Berechtigungen

Das Ausführen einer RMI-Anwendung benötigt einige zusätzliche Berechtigungen - auf dem Client wie auf dem Server. Die java-Sicherheitseinstellungen befinden sich in der Datei $JDK_HOME/jre/lib/security/java.policy, welche um folgende Zeile ergänzt werden muss:

permission java.security.AllPermission;

Diese Zeile ist direkt vor der schliessenden geschweiften Klammer anzugeben.

Achtung: Damit erlauben wir java alles auf unserem Rechner zu tun! Diese Zeile sollte wieder entfernt werden, sobald man mit dem RMI-Experiment am Ende ist. Für einen dauerhaften Einsatz von RMI sollte die Sicherheit feiner abgestimmt werden, dies würde jedoch den Rahmen dieses Dokuments sprengen.

Als zweiten Schritt müssen wir auf dem Server den Port 1010 für die RMI-Registry freigeben:

$JDK_HOME/bin/rmiregistry 1010

Die Konsole darf nun nicht mehr geschlossen werden, da sonst der Port nicht mehr länger freigegeben ist!



Starten der Beispielapplikation

Ist nun alles eingerichtet, folgt der grosse Moment - die Anwendungen werden gestartet! Als erstes muss der Server gestartet werden:

$JDK_HOME/jre/bin/java -Djava.rmi.server.codebase=file://$SERVER_DIRECTORY/Server.jar -jar $SERVER_DIRECTORY/Server.jar

Der Parameter -Djava.rmi.server.codebase sagt java, wo sich unsere Server-Klassen befinden. Die Variable $SERVER_DIRECTORY ist mit der Pfadangabe zu ersetzen, unter welcher sich unser Server.jar befindet. Ist alles gut gegangen, so sollte der Server nun mit einem blinkenden Cursor in der Warteposition verharren. Das Remote Object ist nun registriert!

Nun können wir den Client starten. Dieser erfordert keine weiteren Parameter:

$JDK_HOME/jre/bin/java -jar $CLIENT_DIRECTORY/Client.jar

Auch hier ist die Variable $CLIENT_DIRCTORY durch das Verzeichnis zu ersetzen, in welchem sich unser Client befindet. Auf unserer Client-Konsole sollte nun der Wert 51 ausgegeben werden!

Es ist selbstverständlich auch möglich, Server und Client auf dem gleichen Rechner auszuführen. Dabei geht jedoch der eigentliche Witz an der Sache verloren. Für Testzwecke reicht dies aber allemal.



Quellen



[back to top]



Userdaten
User nicht eingeloggt

Gesamtranking
Werbung
Datenbankstand
Autoren:04503
Artikel:00815
Glossar:04116
News:13565
Userbeiträge:16551
Queueeinträge:06238
News Umfrage
Ihre Anforderungen an ein Online-Zeiterfassungs-Produkt?
Mobile Nutzung möglich (Ipone, Android)
Externe API Schnittstelle/Plugins dritter
Zeiterfassung meiner Mitarbeiter
Exportieren in CSV/XLS
Siehe Kommentar



[Results] | [Archiv] Votes: 1137
Comments: 0