Berichtstitel: Bessere Kommunikation im Netzwerk

JINI revolutioniert die Netzwerk-Ressourcennutzung

 

Einleitung:

Die Kommunikation und Zugriff auf verschiedene Rechner-Ressourcen innerhalb eines Netzwerkes ist eine Angelegenheit für sich. Aber die Internet-Anwendungen heute müssen flexibel innerhalb von Netzwerk-Umgebungen kommunizieren können. Nur so können nützliche Dienstleistungen mittels Web-Anwendungen dem Endanwender bereitgestellt werden. Mit proprietären Lösungswegen komt man nicht weiter. Es muß ein Standard zur transparenten Netzwerk-Kommunikation her. Genau dies hat die Firma SUN erkannt und bietet als Lösung JINI an. Die Aufgabe von JINI ist die Kommunikation zwischen Diensten, Programmen und Benutzern zu koordinieren.

 

Überschrift: Die Intension von JINI

Die Zukunft soll so sein, daß die verschiedensten Endgeräte ohne Probleme miteinander kommunizieren können. So sind diese dann in der Lage wirklich umfangreiche Aufgaben zu bewältigen. Diese Geräte funktionieren sofort ohne eine vorherige Konfiguration.

Die Anwendungen dieser Geräte sind sozusagen intelligent und reagieren vollkommen selbständig auf Probleme und handeln dementsprechend. Diese ermitteln Ergebnisse um flexibles Vorgehen in innerhalb einer vernetzen Umgebung zu erreichen. Es können etliche Ausnahmefälle vorkommen, die aber letztlich nie eine  Anwendung zum Stillstand bringen. Vielmehr sucht diese nach anderen Lösungswegen, um doch noch  ihren gestellten Anforderungen gerecht zu werden. Die Basis hierzu ist die verteilte Entwicklung, um solche Consumergeräte zu realisieren. Da solche verteilte Welten ziemlich komplex sind, ist eine ausgereifte Infrastruktur mit entsprechenden Mechanismen quasi eine Grundvoraussetzung. Nur so kann der Entwickler diese Komplexität der Verteiltheit noch beherrschen. Hingegen läuft es für einen Endanwender quasi alles automatisch ab und funktioniert einfach so, wie es eben soll.

 

Überschrift: Die JINI-Ebene

Hierzu stellt die Firma SUN die JINI-Technologie (Java Intelligent Network Infrastructure) bereit. Hierdurch wird ein Standard eingeführt, wodurch eine vereinheitlichte Kommunikation zwischen den verschiedensten Geräten miteinander ermöglicht wird.

JINI zielt auf den Wunsch ab, daß irgendwann mal alles miteinander vernetzt ist. Da alle Geräte miteinander verbunden und ständig miteinander in Kontakt sind, können so die Wünsche des Benutzers auch erfüllt werden. Alle diese Geräte sollen in Java programmiert und auch bendienbar sein. Der Beginn dieser Entwicklung soll die JINI-Technik einleiten. Hiermit braucht man endlich keine Treiber mehr für jedes einzelne Geräte zu installieren, wie dies in der Windowswelt der Fall ist.

 

Überschrift: Das Ziel von JINI

Das Entwicklungsziel von JINI war es Einfachheit, Verlässlichkeit, Skalierbarkeit und die Unabhängigkeit verschiedenster Geräte zu erreichen. JINI kann Verlässlichkeit erreichen, weil man dies schon auf der API-Ebene erzwingt und alle andere Operationen einfach nicht zuläßt. Natürlich kann es auch hier zu Problemfällen kommen. Jedoch wird eben dort aktiv darauf reagiert. Man kann dieses als spontanes Netzwerk ansehen. Sollte eine Ressource beispielsweise nicht verfügbar sein, wird versucht, eine Verbindung zu einem anderen Rechner herzustellen, welche ebenfalls die Ressource besitzt. Sollte eine Komponente sich zur Laufzeit aufhängen, wird dies erkannt und blockiert nicht unendlich die Netzwerkleistung. Man regelt diese Ausnahmefälle eben auf natürlichen Wege. Damit JINI auch einfach einsetzbar ist, baut es auf dem  Fundament der Sprache  Java auf. Die Entwickler nutzen die gleichen Konzepte die mit dem JDK 1.1 eingeführt wurden. Die Skalierbarkeit wird über das gesamte Netzwerk erreicht. Hierüber bilden die Geräte eine sogenannte Gemeinschaft, um besser ihre Arbeit zu bewältigen und lösen sich wieder auf, sobald die Arbeit erledigt ist. Jedoch ist man hierbei durch die Netzwerk-Topologien eingeschränkt. Die Basis ist weiterhin das einzelne Gerät und dessen Ergebnis. Das oberste Entwicklungsziel von JINI war es aber die Geräteunabhängigkeit zu erreichen. Es soll sich alles in der Java-Welt verbinden lassen. Sei es eine Waschmaschine oder einen Wäschetrockner. Damit dies funktioniert, muß jedes Gerät eine Kommunikationsbasis hierfür bereitstellen. Also muß jedes Geräte von Haus aus über eine JVM (Java 1.2 Version) , sowie einen TCP/IP Stack mit IP-Multicasting verfügen. Dies ermöglicht eine leistungsfähigeren Kommunikation untereinander. So kommt es eben nicht vor, daß ein Gerät still steht und keine Meldung mehr innerhalb eines Netzes erhält. Es wird nämlich immer automatisch eine Vorabmeldung rausgeschickt, damit der Dienst weiter ausgeführt werden kann. Dies ist nötig, um ein leistungsfähigeres Netzwerk zu erhalten.

 

Überschrift: Die Architektur von JINI

Die JINI-Umgebung besteht aus folgenden Bestandteilen:

Aus einer Reihe von Komponenten für die Infrastruktur, worüber die Zusammenführung von Diensten in der verteilten Systemumgebung erreicht wird. Ebenso gibt es ein Programmiermodell, worüber sich leistungsfähige und sichere Anwendung für die verteilte Welt realisieren lassen.

Dazu kommen noch die JINI-Dienste, die jeder nutzen kann. Die einzelnen Teil sind dabei eng miteinander verbunden. Beispielsweise verwendet das Programmiermodell die Infrastruktur-Komponenten. Diese stellen wiederum die Basis für die Dienste. Und natürlich bedient sich das Programmiermodell bei den Diensten dann. Die gesamte JINI-Architektur basiert größtenteils auf der Java-Plattform.

 

Überschrift: Die Konzeptionelle-Ebene  in JINI

Das zentrale Konzept unter JINI stellen die Dienste dar. Diese können von einer Person, einem Programm oder auch einem anderen Dienst genutzt werden. Die Hauptaufgaben von JINI ist einen Dienst bereitzustellen. Somit tritt dieser als Dienstleistungsanbieter im kompletten Netzwerkverbund auf. Beispielsweise kann über JINI ein Drucker netzwerkweit zur Verfügung gestellt werden. Jetzt kann jeder interessierte Client als Verbraucher diese Diensleistung auf Wunsch in Anspruch nehmen. Die komplette API von JINI behandelt die Verteilung der einzelnen Dienste auf verschiedene Clients. JINI stellt verschiedene Mechanismen zur Erstellung, zum Auffinden und zur Kommunikation dieser Dienste untereinander in einer verteilten Welt bereit.

Die Basis jeglicher JINI Kommunikation wird über sogenannte LookupService's abgewickelt. Der Benutzer nutzt diesen, wenn dieser einen Dienst netzweit bereitstellen will oder eben, wenn dieser einen Dienst nutzt. LookupService ist ein interner JINI-Dienst und ist von den gewöhnlichen Benutzer-Diensten zu unterscheiden. LookupService ist quasi eine Art Datenbank, in welcher die Dienstanbieter nachschlagbar sind. Mittels eines LookupService-Objekt  kann ein bestimmter Dienst in Anspruch genommen werden. Wenn ein Klient einen Dienst verwenden will, muß dieser eine Anfrage an den LookupService stellen, ob dieser Dienst gerade im Netzwerk zur Nutzung auch bereitsteht. Ist dies nicht der Fall, dann ist dieser Dienst beim LookupService zur Zeit als nicht erreichbar registriert. Die Frage ist hierbei nur, wie die Dienste oder die  Klients an diesen LookupService herankommen. Dies geschieht mit Hilfe des sogenannten Discovery-Protokoll. Dieses basiert auf IP-Multicasting und kann so eine konsequentere Netzwerk-Kommunikation führen. Hierzu wird die betreffende API genutzt, um an einen bestimmten LookupService zu gelangen. Dabei erfolgt die Kommunikation rein verbindungsorientiert ab.

Für den Aufruf eines Dienstes lädt der LookupService als RMI-Server den zugehörigen Proxy in den Klient. Dies passiert, indem der Service den Kode des Objektes für den Klient transparent überträgt. Dies ist nur beim erstenmal notwendig. Beim nächsten Aufruf ist die zum Objekt gehörige Klassendatei bereits auf dem Klient vorhanden und die vom Service gelieferte Referenz zeigt auf das dort gefundene Objekt. Der Proxy kann ein privates Protokoll für die Kommunikation mit dem Diensanbieter verwenden.

Ebenso läßt sich über die API ein Dienst registrieren, welcher ab sofort netzweit zur Verfügung stehen soll. Sinnigerweise heißt die Registierung eines Dienstes beim LookupService »Discovery«, da der Dienst quasi aus JINI-Sicht gerade entdeckt wird. Der LookupService verfügt dann über einen Verweis auf diesen Dienst. Mit Hilfe dessen kann dieser die zum Dienst zugehörige Ressourcen wie Treiber, Hilfesysteme und ähnliches hineinladen. Erst danach sind die Klients in der Lage auf diesen zuzugreifen. Damit jegliche Dienste und Clients auch über Ereignisse informiert werden, daß ein neuer Dienst bereitsteht, müssem diese sich bei einer entsprechenden DiscoveryEventListener Klasse zuerst registrieren.  Nun werden diese auch informiert , daß ein neuer Dienst im Gesamt-Netzwerk zur Verfügung steht. Somit wird eine vollkommen dynamische Umgebung realisiert. Die gerade eben registrierten Dienste werden mit einer niedrigen Priorität versehen, um so Dienstausfällen vorzubeugen. Über den Mechanismus Leasing wird erzielt, daß keine Ressourcenreste im JINI-Netzwerk hängen bleiben. Dazu muß jeder Klient ständig mitteilen, daß dieser noch Interesse an einen Dienst hat. Ansonsten wird der Klient gnadenlos entfernt und der entsprechende Eintrag im LookupService gemacht, So können neue Klients den Dienst dann nutzen.

Ebenso wird darauf geachtet, daß ein Klient nicht unendlich einen Dienst nutzt. Schließlich soll JINI die Verteilung der Dienste im Netzwerk erzielen und diese dabei optimal verwalten. Jeder Klient gibt dabei an, wie lange dieser den Dienst nutzen möchte. Jedoch entscheidet der Dienst, wie lange er diesen Dienst den Klienten zur Verfügung stellt. Der Klient hat hier keinen Einfluß darauf.  Die Funktionalität eines Dienstes exportiert JINI über eine Java-Schnittstelle. Der LookupService bildet diese Schnittstelle auf konkrete Objekte ab, welche die entsprechende Funktionalität implementieren. Zusätzlich kann ein Dienst noch beschreibende Einträge haben, die einem Benutzer bei der Auswahl eines passenden Dienstes helfen. Beispielsweise kann hierüber angegeben, über welche Funktionen ein Geräte konkret verfügt.

Die gesamte Kommunikation erfolgt bei JINI über ein sogenanntes ServiceProtocol, welches auf einer Reihe von Java-Schnittstellen aufbaut. Der Funktionsumfang sowie die Anzahl der ServiceProtokolle sind nicht beschränkt. JINI stellt eine Reihe von ServiceProtokolle bereit. Die Basis jeden ServiceProtokoll dient RMI (remote method invocation). Hierüber lassen sich Kodes in Form von Objekten über das Netzwerk austauschen. Hiermit ist grundlegend beschrieben, wie JINI grundsätzlich abläuft.

 

Überschrift: Die Programmierung in JINI

Jetzt ist es aber interessant auch die programmiertechnische Seite zu betrachten. Hierzu stellt JINI verschiedene Klassen und Interfaces zur Entwicklung bereit. Nehmen wir als Beispiel an, daß wir einen Drucker über JINI innerhalb eines Netzwerkes für eine Reihe von Clients ansprechbar sein soll. Alle interessierten Clients können den Dienst des Druckers in Anspruch nehmen. Dafür sind verschiedene Klassen und Schnittstelle für den JINI-Klient, den JINI-Server und den JINI-Dienst zu implementieren. Also sehen wir uns die Schritte an, die dafür notwendig sind.

 

Überschrift: Der JINI-Dienst

Grundsätzlich muß jeder JINI-Dienst ersteinmal über eine öffentliche Schnittstelle definiert werden. Diese Schnittstelle beschreibt konkret, wozu der Dienst grundsätzlich in der Lage sein soll. Dieser Dienst wird auf der Serverseite den zahlreichen interessierten Klients angeboten.

Dabei wird die komplette Diensleistung des Dienstes umschrieben. Hierzu legen wir eine gewöhnliche Java-Interface Schnittstelle an. Wir definieren nur eine einzige Methode innerhalb dieser Schnittstelle. Den Aufbau kann man sich so vorstellen:

 

#Kode:1#

// Schnittstelle für den Dienst

package jini.test;

public interface DruckerServiceInterface {

     public String drucken (); }

 

Die eigentliche Implementierung des JINI-Dienstes erfolgt dann mit Hilfe eines konkreten Proxy-Objektes. Wenn der Klient diesen Dienst anfordert, wird der Proxy von der Serverseite geladen und dem Klient zur Nutzung bereitgestellt. Die Klassendefinition der Proxy-Klasse muß die Schnittstelle »Serializable« implementieren. Nur so kann der Zustand des Objektes zur Laufzeit in einen Bytestrom gesichert werden, wenn der Klient vorzeitig den Dienst beendet. So kann der Klient später dort weitermachen, wo dieser aufgehört hat. Dieser muß also auf den Zustand des Objektes zugreifen können. Die einzelnen Klassenmethoden sind natürlich öffentlich zu deklarieren, damit der Klient diese verwenden kann. Die Klasse sieht im Detail so aus:

 

#Kode:2#

// Implementierung des Dienstes

package jini.test;

import net.jini.discovery.DiscoveryListener;

import net.jini.discovery.DiscoveryEvent;

import net.jini.discovery.LookupDiscovery;

import net.jini.core.lookup.ServiceItem;

import net.jini.core.lookup.ServiceRegistrar;

import net.jini.core.lookup.ServiceRegistration;

class DruckerServiceProxy() implements Serializable, DruckerServiceInterface {

public DruckerServiceProxy {}

public String drucken()

{ // nicht implementiert! } }

 

Überschrift: Der JINI-Server

Jetzt brauchen wir allerdings noch eine Klasse, welche alle bereitstehenden LookupService's findet und die dort verfügbaren Dienste den Klients zur Verfügung stellt. Die Aufgabe dieser Klasse ist die Verwaltung aller verfügbaren JINI-Dienste und die Bereitstellung dieser Dienstfunktionalitäten.  Hierzu führen wir die Klasse JINIServer ein. Dessen Hauptfunktion führt die Suche nach verfügbaren LookupService's im Netzwerk durch. Das interne JINI Discovery System informiert dann, ob neue Dienstobjekte im Netzwerk bereitstehen. Über den LookupService kann dann ebenfalls ein spezieller Dienst genutzt werden. Der Klassen-Konstruktor geht hierbei folgendermaßen vor: Zuerst wird ein Objekt der Klasse ServiceItem erzeugt.  Diese verwaltet alle Dienste, welcher der aktuelle Rechner und fremde Rechner zur Verfügung stellen und auch netzwerkweit bereitstehen. Dieses Objekt wird der Klasse ServiceRegistrar übergeben. Somit ist der Dienst auch registriert und kann von der Außenwelt angefragt werden. Der Konstruktor der Klasse ServiceItem verlangt beim Aufruf drei Argumente:

Hierbei ist als erstes die ServiceID für den Dienst anzugeben. Dieser dient zur globalen Identifizierung des Dienstes. Auch wenn der Dienst bei mehreren LookupService's registriert ist, ist der Dienst immer eindeutig identifizierbar. Dann ist der Dienst zu erzeugen, welcher zur Nutzung bereitstehen soll. Dies führt die Methode createProxy() dann durch. Über eine weiteres Argument können noch Zusatzinformationen für den Client angegeben werden. Um so eine sichere Server-Umgebung zu schaffen, wird noch ein SecurityManager erzeugt. Sobald ein LookupService im JINI-System bereitsteht, wird die Instanz als LookupService bereitgestellt.

Danach wird die Instanz der Klasse LookupDiscovery erzeugt. Dies ist eine Default-Klasse von JINI, dessen Aufgabe ist es, nach LookupServices zu suchen. Wenn man erfahren will, ob ein LookupService ein Mitglied der öffentlichen Gruppe ist, wird der Konstruktor einfach ein leerer String mit übergeben. Eine Gruppe repräsentiert dabei den Zusammenschluß mehrer JINI-Dienste. Alle die sich als DiscoveryListener beim LookupDiscovery angemeldet haben, werden über Ereignisse des Discovery-Subsystem unterrichtet. Die Discovery Schnittstelle verfügt über die Methoden discovered() und discarded(). Die erste Methode wird aufgerufen, wenn ein LookupService gefunden wurde, wenn dieser der gesuchten Gruppe entspricht. Die letzte Methode rufen wir auf, wenn wir den LookupService aus der Reihe der gefundenen LookupService's nicht mehr benötigen.

Innerhalb der discovered() Methode wird eine HashMap angelegt. Diese nimmt alle LookupService's auf, die gefunden wurden. Wenn durch das Discovery Protokoll ein neuer LookupService gefunden wird, erhält das Programm ein DiscoveryEvent zugestellt. Der Listener behandelt diesen dann entsprechend. Hierbei prüft dieser dann, ob in der HashMap dieser LookupService nicht schon existiert. Wenn dies nicht der Fall ist, wird dieser auch in der HashMap abgelegt und wird als LookupService registiert. Die Registierung ist eigentlich einfach. Hierzu besorgt man sich einfach eine ServiceRegistrar Instanz, welche zum aktuellen LookupService gehört und ruft die Methode register() auf. Dabei sind der Methode zwei Argumente mit zuübergeben. Der erste ist das ServiceItem, welchen wir zuvor angelegt haben. Dann ist noch die Lease-Zeit anzugeben. Dies ist die Zeitspanne, wie lange ein Proxy ohne Netzwerk-Kommunikation existieren darf. Die Klasse sieht im Detail dann so aus:

 

#Kode:3#

// Implementierung des Servers

public JINIServer implements Runnable {

protected final int LEASE_TIME = 10*60*1000;

protected HashMap registrations=new HashMap();

protected ServiceItem item;

protected LookupDiscovery disco;

 

class Listener implements DiscoveryListener {

  public void discovered(DiscoveryEvent ev) {

   System.out.println(»Ein LookupDienst gefunden!«);

   ServiceRegistrar[] newregs = ev.getRegistrars();

     for(int i=0; i<newregs.length; i++)

       if(!registrations.containsKey(newregs[i]==

       { registerWithLookup(newregs[i]);}

      }

   }

 }

   public void discarded(DiscoveryEvent ev) {

    ServiceRegistrar[] deadregs = ev.getRegistrars();

    for(int i=0; i<deadreags.length;i++)

    { registrations.remove(deadregs[i]=; }

 }}

 

  public JINIServer() throws IOException {

   item = new ServiceItem(null, createProxy(), null);

   if (System.getSecurityManager() == null)

   { System.setSecurityManager(new RMISecurityManager()); }

   disco = new LookupDiscovery(new String[] { "" });

   disco.addDiscoveryListener(new Listener()); }

   

   protected DruckerServiceInterface createProxy()

   { return new DruckerServiceProxy(); }

   

  synchronized void registerWithLookup(ServiceRegistrar registrar)  

  { ServiceRegistration registration = null;

    try {

        registration = registrar.register(item, LEASE_TIME);

        } catch (RemoteException ex) {

    System.out.println("Couldn't register: " + ex.getMessage());

    return; }

    if (item.serviceID == null)

    { item.serviceID = registration.getServiceID();

      System.out.println("Set serviceID to " + item.serviceID); }

      registrations.put(registrar, registration); }

  

   public void run() {

      while (true) {

         try {

           Thread.sleep(1000000);

           } catch (InterruptedException ex) {

    }}}

 

   public static void main(String args[]) {

        try {

            JINIServer hws =

                new JINIServer();

            new Thread(hws).start();

        } catch (IOException ex) {

            System.out.println("Konnte Dienst nicht anlegen" +

            ex.getMessage());

        }

    }

}

     

Überschrift: Der JINI-Klient

Nun muß nur noch der Klient implementiert werden. Der Client der an einen Dienst interessiert ist, fragt nun mit Hilfe eines Template bei einem LookupService nach einem geeigneten Service-Provider an. Dieser LookupService wird mit Hilfe des Discovery - Mechanismus gefunden und daraufhin wird als Ergebnis ein Proxy-Objekt des betreffenden LookupService zurückgeliefert. Danach wird über die lookup() Methode nach einem bestimmten Objekt gesucht. Die Methode liefert entweder null, eine Exception oder eben ein Objekt der nachgefragten Klasse zurück. Nach einem Typ-Konvertierung kann der Client alle Methoden des Interfaces ausführen. Dabei ist ein Dienst zuerst bekanntzugeben, bevor dieser genutzt werden kann.

 

#Kode:4#

// Die Implementierung des Klients

package jini.test;

import net.jini.discovery.DiscoveryListener;

import net.jini.discovery.DiscoveryEvent;

import net.jini.discovery.LookupDiscovery;

import net.jini.core.lookup.ServiceRegistrar;

import net.jini.core.lookup.ServiceTemplate;

 

public class JINIKlient implements Runnable {

   protected ServiceTemplate template;

   protected LookupDiscovery disco;

  

   public JINIKlient() throws IOException {

    Class[] types = { DruckerServiceInterface.class };

    template = new ServiceTemplate(null, types, null);

    if (System.getSecurityManager() == null)

    { System.setSecurityManager(new RMISecurityManager()); }

    disco = new LookupDiscovery(new String[] { "" });

    disco.addDiscoveryListener(new Listener()); }

 

   protected Object lookForService(ServiceRegistrar lusvc) {

    Object o = null;

    try { o = lusvc.lookup(template);

        } catch (RemoteException ex) {

    System.err.println("Error doing lookup: " + ex.getMessage());

    return null; }

    if (o == null)

    { System.err.println("No matching service.");

      return null }

      System.out.println("Habe den Dienst gefunden");

        System.out.println("Der Dienst ist: " +

        ((DruckerServiceInterface)).getMessage());

        return o; }

   

   public void run() {

    while (true) {

     try {

         Thread.sleep(1000000);

         } catch (InterruptedException ex) {

}}}

   

   public static void main(String args[]) {

   try {

        JINIKlient hwc = new JINIKlient();

        new Thread(hwc).start();

        } catch (IOException ex) {

        System.out.println("Der Klient konnte nicht erzeugt werden!");

}}}

 

Überschrift: Schluß:

Die JINI-Programmierung ist keinenfalls trivial. Erst muß man mal die Konzepte von JINI und dessen Architektur verstanden haben. Für die Programmierng stehen eine Reihe von standardisierte Klassen und Schnittstellen bereit. Auf jeden Fall bietet JINI eine interessanten Ansatz um zukünftig Ressourcen im Netzwerk besser zu verteilen und nutzen zu können. Die Zukunft wird zeigen, in wie fern die Industrie JINI als den Standard akzeptieren wird. Denn es gibt auch anderen Techniken die sich in diesem Bereich durchsetzen wollen. Dazu zählen die Firma HP mit JetSend und die Firma Lucent mit Inferno.