VWX G LH Q D UE H LWD LIS Universität Stuttgart Jan-Martin Bofinger Realisierung von verteilten GIS-Funktionalitäten unter Verwendung der Java Remote Method Invocation ifp Betreuer: Dipl.-Geogr. Steffen Volz Prüfer: Prof. Dr.-Ing. habil Dieter Fritsch Inhaltsverzeichnis Einleitung 1 EINLEITUNG ....................................................................................................... 2 2 VERTEILTE GIS-FUNKTIONALITÄTEN ............................................................ 3 3 2.1 GIS im Internet...................................................................................................... 3 2.2 Interoperabilität .................................................................................................... 5 2.3 Das Projekt NEXUS .............................................................................................. 6 REMOTE METHOD INVOCATION (RMI) UNTER JAVA.................................... 7 3.1 Einführung ............................................................................................................ 8 3.2 Architektur ............................................................................................................ 9 3.2.1 Transportschicht ............................................................................................. 9 3.2.2 3.2.3 Remote Reference Layer.............................................................................. 10 Stubs und Skeletons..................................................................................... 10 3.3 Vorgehensweise bei der Implementierung einer RMI-Anwendung ................. 11 3.3.1 Definition der RMI-Schnittstelle..................................................................... 11 3.3.2 Erstellen des Remote-Objektes .................................................................... 12 3.3.3 Implementierung des Clients......................................................................... 13 3.3.4 Starten der Registry...................................................................................... 13 3.4 4 Dynamisches Herunterladen des Stubs ........................................................... 14 ALGORITHMEN ZUM „KÜRZESTE WEGE“-PROBLEM ................................ 15 4.1 Single Source Shortest Path.............................................................................. 16 4.1.1 Der Dijkstra-Algorithmus ............................................................................... 17 4.1.2 Implementierung des Dijkstra-Algorithmus in Java........................................ 18 4.2 All-Pairs Shortest Path....................................................................................... 19 4.2.1 Der Floyd-Algorithmus .................................................................................. 19 4.2.2 Implementierung des Floyd-Algorithmus in Java........................................... 20 5 LÖSUNGSANSATZ UND IMPLEMENTIERUNG DER RMI-ANWENDUNG .... 21 5.1 Schnittstellen...................................................................................................... 22 5.2 Server.................................................................................................................. 23 5.3 Client ................................................................................................................... 25 5.3.1 Bedienung der graphischen Oberfläche........................................................ 25 5.3.2 Implementierung des Clients......................................................................... 27 6 TEST DER CLIENT-SERVER-ANWENDUNG .................................................. 29 6.1 Initialisierung der Server.................................................................................... 29 6.2 Ausführung von GIS-Funktionalitäten .............................................................. 30 7 ZUSAMMENFASSUNG/AUSBLICK ................................................................. 33 8 LITERATURVERZEICHNIS .............................................................................. 34 ANHANG, ANLAGE Seite 1 Einleitung 1 Einleitung In den letzten Jahren konnten verschiedene Anbieter Dienste auf dem GIS-Markt etablieren, die Geodaten, wie z.B. Karten, über das Internet zur Verfügung stellen. Für die Auswahl der relevanten Geodaten kann der Nutzer umfangreiche GIS-Funktionalitäten einsetzen, dennoch steht bei diesen Diensten, die auch als Geodaten-Dienste bezeichnet werden, die Bereitstellung der Daten im Vordergrund. In aktuellen Ansätzen der GIS-Forschung wird versucht, eine Trennung von Geodaten und GIS-Funktionalitäten herbeizuführen und die jeweiligen GIS-Funktionalitäten unabhängig von spezifischen Geodaten anzubieten [1]. Diese Dienste werden als Geoverarbeitungs-Dienste bezeichnet. Die Bereitstellung der GIS-Funktionalitäten kann über eine Client-Server-Architektur realisiert werden. In diesem Fall werden die Funktionalitäten als Objekte implementiert, welche auf verschiedenen Servern abgelegt sind. Damit der Client Zugriff auf diese Objekte hat, muss eine Schnittstelle bereitgestellt werden. Abhängig von der Leistung des Client-Rechners und dem jeweiligen Datenaufkommen ist der Client dann in der Lage, die GIS-Funktionalitäten entweder direkt auf dem Rechner des Servers zu berechnen oder die benötigten Objekte herunterzuladen und lokal auszuführen. Im Rahmen dieser Studienarbeit soll nun untersucht werden, ob der Kommunikationsmechanismus der Programmiersprache Java, Remote Method Invocation (RMI) genannt, für die Umsetzung der Client-Server-Architektur in eine Anwendung geeignet ist. RMI erlaubt es, Objekte auf entfernten Rechnern in genau derselben Weise anzusprechen wie Objekte, die auf demselben Rechner laufen. Diese Eigenschaft erleichtert das Erstellen von verteilten Anwendungen erheblich, da der Entwickler keine Funktionen für den Austausch von Objekten zwischen den verschiedenen Rechnern implementieren muss. Nach der Definition geeigneter Schnittstellen können Client und Server implementiert werden. In Kapitel 2 werden verschiedene Aspekte zum Einsatz von GIS im Internet angesprochen und es wird die mögliche Einbindung von RMI in das NEXUS-Projekt [7] aufgezeigt. Das darauf folgende Kapitel gibt eine Einführung in die Architektur und die Vorgehensweise bei der Implementierung einer Client-Server-Anwendung. Für die Demonstration der implementierten Client-Server-Applikation sollen Aufgaben aus der Netzwerkanalyse herangezogen werden. Hier bietet sich z.B. die Lösung von Problemen der Kürzesten Wege an. Deshalb wird Kapitel 4 auf diese Problematik eingehen und zwei Lösungsalgorithmen, den Dijkstra- und den Floyd-Algorithmus, vorstellen. Kapitel 5 zeigt einen Lösungsansatz für die Client-Server-Architektur und dessen Umsetzung in Java. In den danach folgenden Kapiteln wird die Anwendung einem Test unterzogen und es werden mögliche Erweiterungen für eine zukünftige Weiterentwicklung angedacht. Seite 2 Verteilte GIS-Funktionalitäten 2 Verteilte GIS-Funktionalitäten Die Bereitstellung von Geodaten über das Internet wird bereits seit längerer Zeit erfolgreich betrieben. So gibt es einige Anbieter, die zentralisierte Geodaten-Dienste auf der Basis von Client-Server-Architekturen zur Verfügung stellen. Der Benutzer dieser Dienste hat die Möglichkeit, über eine graphische Schnittstelle raumbezogene Anfragen an den Server zu schicken. Diese Anfragen werden auf dem Server mittels der erforderlichen GISFunktionalitäten bearbeitet und die Ergebnisse an den Client übertragen. Es gibt jedoch Fälle, in denen die Geodaten auf der Client-Seite gespeichert und verwaltet werden. Dann benötigen die Clients lediglich die GIS-Funktionalitäten, um ihre Daten zu verarbeiten. Diese Funktionalitäten sind zum Beispiel Werkzeuge für die Konvertierung von Daten, für die Kartenprojektion oder für die Bearbeitung von Daten. Diese Arbeit wird versuchen, einen möglichen Lösungsansatz für die Bereitstellung von benötigten GISFunktionalitäten über das Internet aufzuzeigen. In den folgenden Unterkapiteln wird näher auf GIS im Internet, den Begriff der Interoperabilität und das Projekt NEXUS eingegangen. 2.1 GIS im Internet Grundsätzlich gibt es zwei verschiedene Ansätze, GIS-Funktionalitäten über das Internet verfügbar zu machen. Zum einen existiert der serverbasierte und zum anderen der clientbasierte Ansatz. Im serverbasierten Ansatz werden sämtliche Berechnungen, die für das Ausführen der GISFunktionalitäten benötigt werden, auf dem Server ausgeführt. Dies bedeutet, dass sich neben den Geodaten auch die erforderliche Software auf dem Server befindet. Der Client, der zumeist als Applet in einem Internet-Browser implementiert ist, wird lediglich dazu benötigt, die Anfragen an den Server zu stellen und die übermittelten Resultate graphisch oder in Textform darzustellen. Berechnungen, welche die Geodaten betreffen, werden auf der Clientseite nicht durchgeführt. Die Software, welche die GIS-Funktionalitäten auf dem Server zur Verfügung stellt, kann eine extra für diesen Zweck programmierte Anwendung in C++ oder Visual Basic sein. Es kann aber auch eine Anwendung sein, die von kommerziell erhältlichen GIS-Paketen wie ArcInfo implementiert wurde. Wie bereits weiter oben erwähnt, basieren die serverbasierten Anwendungen auf Applets, die in einem Internet-Browser geladen werden können. Auf die Anfrage des Clients hin wird eine Karte auf dem Server generiert, als Rasterbild an das Applet gesendet und im Browser dargestellt. Der Nachteil dieses Verfahrens ist die eingeschränkte Interaktivität zwischen Client und Server. Einfache Operationen wie das Zoomen in Bildern mittels Zeichnen eines Rechteckes können nicht in das Applet integriert werden. Folglich müssen auch diese Aufgaben durch den Server erledigt werden, was zu einer hohen Belastung des Servers Seite 3 Verteilte GIS-Funktionalitäten führen kann, der für jedes Zoomen oder Verschieben der Karte kontaktiert werden muss. Gerade in Zeiten, in denen der Server mit rechenintensiven Aufgaben beschäftigt ist, kann es dann auch bei diesen vermeintlich einfachen Funktionen zu größeren Wartezeiten kommen. Die serverbasierten Anwendungen haben jedoch auch einige Vorteile. Da die Berechnungen auf dem Server ausgeführt werden, ist die Rechenzeit für die einzelnen Anfragen nicht von der Rechenleistung des Client-Rechners abhängig und auch bei sehr großen und komplexen Datensätzen sind die Rechenzeiten zumeist akzeptabel. Außerdem entfällt die Installation und Wartung der Programme auf dem Client-Rechner, weil die Applets über den InternetBrowser geladen werden. Dies bewirkt, dass der Client auf allen Computersystemen, die einen Internetanschluss haben, verfügbar ist. Im clientbasierten Ansatz werden einige der Berechnungen vom Rechner des Clients übernommen. Wenn die Datenmenge nicht zu groß ist, können bestimmte Geodaten an den Client übertragen werden. Darum bietet es sich an, die Ergebniskarte nicht im Rasterformat, sondern als Vektordaten an den Client zu übermitteln. Der für die Darstellung der Daten verantwortliche Client ist dann in der Lage, so einfache Darstellungsfunktionen wie das Zoomen oder Verschieben des Sichtfensters eigenständig durchzuführen. Dies hat zur Folge, dass die Zahl der Anfragen an den Server zurückgeht. Der Server wird dadurch entlastet und kann sich auf die Bearbeitung von komplexeren Anfragen konzentrieren. Der Nachteil kann allerdings die Übertragung von nicht unerheblichen Datenmengen an den Client sein, die je nach Größe der Datensätze zu längeren Wartezeiten führen kann. Ist der Client auf einem leistungsschwachen Rechner installiert, so bereitet die Darstellung der Geodaten ebenfalls Probleme. Wenn die clientbasierte Anwendung nur auf die Geodaten des Servers zugreifen soll, kann der Client wie im serverbasierten Ansatz als Applet implementiert werden. Sollen jedoch die Ergebnisse von Analysen auf dem Rechner des Clients abgespeichert oder Daten genutzt werden, die auf dem Client-Rechner abgelegt sind, muss eine Anwendung programmiert werden, die unabhängig vom Internet-Browser läuft. Dies liegt daran, dass Applets die folgenden Einschränkungen haben: • Applets haben keinen Lese- oder Schreibzugriff auf das Dateisystem des ausführenden Computers. Es sind lediglich Zugriffe auf eine URL-Adresse des Servers möglich, der das Applet bereitstellt. • Ein Applet kann keine Netzwerkverbindung zu anderen Rechnern aufbauen, mit Ausnahme des Rechners, von dem es geladen worden ist. Bevor also ein GIS für das Internet aufgebaut wird, müssen die jeweiligen Anforderungen an das System ermittelt werden. Daraufhin kann unter Berücksichtigung der verfügbaren Hardwareressourcen der geeignete Ansatz gewählt werden. Seite 4 Verteilte GIS-Funktionalitäten 2.2 Interoperabilität In einem Artikel von P. Ladstätter [4] befindet sich eine dem heutigen Stand der Entwicklung entsprechende Definition für den Begriff Interoperabilität: Interoperabilität ist die Möglichkeit von Softwareprodukten oder -komponenten, innerhalb einer verteilten Anwendung (Client-Server- oder Multi-Tier-Architektur) Informationen oder Dienste über standardisierte und eindeutig spezifizierte Interfaces oder Protokolle zu nutzen. Die Interoperabilität zielt – im Gegensatz zum Datenaustausch – darauf ab, dass Programme direkt miteinander kommunizieren statt Informationen über Dateien auszutauschen. Ein klassisches Beispiel sind Datenbank-Anwendungen in Client-Server-Architektur. In diesem Fall kommunizieren zwei unabhängige Programme – der Client und der Server – miteinander über eine gemeinsame Schnittstelle. Typischerweise laufen beide Programme auf unterschiedlichen Rechnerplattformen. Für die Kommunikation zwischen Client und Server wird dann eine sogenannte Middleware verwendet, die in diesem speziellen Fall einer Datenbank-Anwendung entweder vom Datenbankanbieter selbst stammt oder, wie z.B. ODBC, weitgehend datenbankunabhängig ist. Bekannt geworden ist der Begriff Interoperabilität im Zusammenhang mit der Common Object Request Broker Architecture (CORBA). CORBA ist ebenfalls eine Middleware, die von einem Industriekonsortium, der Object Management Group (OMG) [10], speziell für verteilte Anwendungen entwickelt wurde. Die wesentlichen Voraussetzungen für interoperable Software sind Software-Programme oder Komponenten, die als Client oder Server fungieren, definierte Schnittstellen und eine gemeinsame Middleware. Die definierten Schnittstellen stellen die Verbindung zwischen Client und Server dar. Durch sie ist der Client in der Lage, Funktionen aufzurufen, die dann auf dem Server ausgeführt werden. Die Schnittstellen dürfen nach dem Starten der Client-Server-Applikation auf keinen Fall verändert werden, da sie die Kommunikation zwischen Client und Server gewährleisten. Änderungen im Server- oder im Clientprogramm können auch nach dem Starten noch vorgenommen werden, natürlich unter der Voraussetzung, dass sie die Schnittstellen weiterhin implementieren. In dieser Studienarbeit soll die Interoperabilität zwischen einem Client und mehreren Servern ermöglicht werden, d.h. der Client kann die jeweiligen Funktionen der verschiedenen Server nutzen. Als Middleware zwischen Client und Server wird Remote Method Invocation (RMI) eingesetzt. Im Gegensatz zu CORBA kann RMI nur unter Java eingesetzt werden, so dass die Implementierung von Client und Server in Java erfolgen muss. Die Vorgehensweise bei der Definition von Schnittstellen und bei der Implementierung von Client und Server wird in Kapitel 3 ausführlich beschrieben. Seite 5 Verteilte GIS-Funktionalitäten 2.3 Das Projekt NEXUS Dieser Abschnitt soll einen kurzen Einblick in das Projekt NEXUS geben, an dem das Institut für Photogrammetrie beteiligt ist [7]. Außerdem soll die mögliche Einbindung von RMI in dieses Projekt aufgezeigt werden. NEXUS beschäftigt sich mit dem Problem, dass es aufgrund der wachsenden Datenmengen in der heutigen Zeit immer schwieriger erscheint, ohne größeren Aufwand genau die Informationen ausfindig zu machen, die man gerade benötigt. Deshalb wird eine geeignete Strukturierung der Daten immer bedeutsamer. Ein Ansatz besteht nun darin, den räumlichen Bezug von Informationen bzw. deren Verknüpfung mit einem bestimmten Ort auszunützen. Anwendungen, welche diesen Ansatz berücksichtigen, versuchen dem Benutzer den Zugriff auf jene Informationen zu erleichtern, die an seinem Aufenthaltsort von Bedeutung sind. Die Idee von NEXUS ist es nun, eine generische Plattform für diese Art von Anwendungen bereitzustellen, wobei die Mobilität der Benutzer durch die Verwendbarkeit von Kleinrechnern wie Subnotebooks, PDAs oder Wearable Computers unterstützt werden soll. Über diese als NEXUS-Stations bezeichneten Kleinrechner, in denen Positionierungssensoren und Kommunikationseinrichtungen integriert sind, können die Benutzer bzw. die ortsbewussten Anwendungen auf die Plattform zugreifen. Zur Unterstützung der Benutzer auf dem Weg durch die NEXUS-Welt, die sich aus einem Modell der realen Welt und zusätzlichen virtuellen Objekten zusammensetzt, müssen GISFunktionalitäten in die Plattform integriert werden (siehe Abbildung 1). Als Beispiel hierfür sind „Kürzeste Wege“-Algorithmen oder räumliche Selektionen zu nennen. NEXUS Plattform Clients NEXUSNEXUS(NEXUSStations Stations Station) BenutzerSchnittstelle Lokations-Server GIS-Datenserver Server für GIS-Funktionalitäten Abbildung 1: Vereinfachtes Modell der NEXUS-Plattform Mit RMI können Schnittstellen für die Benutzung der GIS-Funktionalitäten zur Verfügung gestellt werden, die unabhängig von den verschiedenen Arten der NEXUS-Stations sind. Abhängig von der Rechenleistung der NEXUS-Station können die Clients dann entscheiden, ob die GIS-Funktionalitäten direkt auf den jeweiligen Servern ausgeführt und die Ergebnisse an den Client gesendet werden, oder die GIS-Funktionalitäten an den Client übertragen und auf der NEXUS-Station berechnet werden. Seite 6 Remote Method Invocation (RMI) unter Java 3 Remote Method Invocation (RMI) unter Java RMI erlaubt es Entwicklern, Programme zu schreiben, die Objekte auf entfernten Rechnern in genau derselben Weise ansprechen wie Objekte, die auf demselben Rechner laufen. Diese Eigenschaft erleichtert das Erstellen von verteilten Anwendungen wesentlich, da der Entwickler keine Funktionen für den Austausch von Objekten zwischen den verschiedenen Rechnern implementieren muss. Entwicklungsgeschichtlich geht RMI aus dem Remote Procedure Calls (RPC) genannten Konzept von SUN Microsystems hervor. Dies hatte anfangs der 80er Jahre den Prozeduraufruf auf einem entfernten Computer methodisch einem Aufruf auf dem gleichen Rechner angeglichen. Die Technik des RMI erweitert nun dieses Konzept hinsichtlich der objektorientierten Programmierung in Java. So wird der Polymorphismus vollständig unterstützt. Referenzen zu Objekten auf anderen Virtuellen Maschinen (JVM) werden aufgebaut und konsistent verwaltet. RMI beschränkt sich nicht nur auf ein einfaches Aufrufen von entfernten Methoden, sondern lässt die Möglichkeit, direkte Implementierungen von Schnittstellen und Unterklassen zu versenden, offen. Es werden konkrete Objekte als Argumente übergeben. Erhält eine Methode ein zulässiges Objekt als Argument, dessen Klasse es nicht kennt, kann unter bestimmten Voraussetzungen die Klasse dynamisch vom fernen Host geladen werden. RMI ist ein sprachgebundenes Werkzeug, dass nur unter Java läuft. Es grenzt sich dadurch von der offenen CORBA-Technologie ab. Allerdings besitzt RMI neben dem Nachteil der Sprachgebundenheit auch einige Vorteile gegenüber CORBA. So kann zum Beispiel auf den Einsatz einer von der Implementierung unabhängigen Meta-Sprache (IDL) für die Schnittstellendefinition verzichtet werden. Die Benutzung von CORBA-spezifischen Typen ist ebenfalls nicht notwendig, da die Speicherplatzbelegung der einzelnen Typen in den JVMs unabhängig von der Architektur des Rechners ist. Bei anderen Programmiersprachen kann es z.B. vorkommen, dass ein Integer-Wert, der auf dem einen System 32 Bit auf dem anderen aber 16 Bit belegt, bei der Übertragung seinen Wert verändert. RMI kapselt den Datentransfer weitgehend vom Systementwurf ab und stellt verschiedene Mechanismen bereit, die nach den aktuellen Möglichkeiten vom System genutzt werden. Der Programmierer kümmert sich nicht darum, ob die Verbindung zwischen den Computern über eine Socket-Socket Leitung oder aus Sicherheitsgründen über das HTTP-Protokoll (Firewalls) zustande kommt. Die Zahl der potenziellen Fehlermöglichkeiten in Netzwerk-Programmen steigt gegenüber einfachen Java-Anwendungen enorm an. Dies muss im Softwareentwurf sehr frühzeitig beachtet werden. RMI bietet aber eine leicht zu handhabende Fehlerkontrolle mittels des Exception-Mechanismus, die leider durch ein schlechteres Laufzeitverhalten erkauft werden muss. Seite 7 Remote Method Invocation (RMI) unter Java 3.1 Einführung Eine einfache RMI-Anwendung besteht aus zwei Programmen, die auf unterschiedlichen Rechnern laufen. Dabei hat das eine Programm die Funktion eines Servers, das andere übernimmt die Aufgaben des Clients. Im folgenden soll die Funktionsweise dieser RMI Anwendung kurz beschrieben werden. Eine Serveranwendung erzeugt ein sogenanntes Remote-Objekt, macht eine Referenz auf dieses Objekt zugänglich und wartet darauf, dass ein Client Methoden dieses Objektes aufruft. Die Clientanwendung bekommt auf Anfrage die entfernte Referenz zu einem oder mehreren Remote-Objekten des Servers von der Registry zugewiesen und ruft dann Methoden dieser Objekte auf. RMI stellt den Mechanismus zur Verfügung, mit dem Server und Client miteinander kommunizieren und Informationen austauschen können. Die Abbildung 2 soll einen ersten Einblick in die Abläufe einer RMI Anwendung geben. Client ruft Methoden des Remote-Objekts auf holt sich Referenz auf Remote-Objekt Server bindet Referenz auf Remote-Objekt ein Registry Abbildung 2: Einfaches Ablaufschema einer RMI Anwendung Der Server benutzt die sogenannte Registry von RMI, um die Referenzen der RemoteObjekte für den Client zugänglich zu machen. Jede Instanz eines Remote-Objekts wird vom Server mittels eines spezifischen Namens in die Registry eingebunden. Dieser Name muss dem Client bekannt sein und ist vom Typ String. Der Client kann nun mit Hilfe des Namens das gewünschte Remote-Objekt in der Registry auffinden und die Methoden des Objektes ausführen, die für einen fernen Aufruf zugänglich sind. Die Methoden des Remote-Objektes, die vom Client aufgerufen werden können, sind in einer Schnittstelle definiert, welche vom Server implementiert werden muss. Da der Client aus Sicherheitsgründen nur auf diese Methoden zugreifen soll, werden sie in einer Stub-Klasse zusammengefasst, deren Code vom Client dynamisch geladen werden kann, wenn dieser auf der JVM des Clients nicht existiert. Die Klasse des Stubs beinhaltet neben den entfernt aufrufbaren Methoden noch Funktionen für die Kommunikation zwischen Client und Server. Die folgenden Kapitel werden die Architektur von RMI und die Vorgehensweise bei der Implementierung einer RMI Anwendung beschreiben. Seite 8 Remote Method Invocation (RMI) unter Java 3.2 Architektur Der Aufruf einer Methode von der Clientseite aus wird auf der lokalen JVM zuerst durch mehrere Schichten geleitet, bevor er zur JVM des Servers gelangt. Abbildung 3 zeigt den Weg eines Methodenaufrufs durch die verschiedenen Schichten des RMI Systems. Der Entwickler ist lediglich für das Erzeugen des Stubs und des Skeletons mit dem RMIWerkzeug rmic verantwortlich. Mit den beiden unteren Schichten wie dem Remote Reference Layer (RRL) oder dem Transport muss er sich bei der Implementierung seiner Client-Server-Anwendung überhaupt nicht befassen. RMI Client RMI Server Stub Skeleton RRL RMI System RRL Transport Transport JVM 1 JVM 2 Abbildung 3: Die Architektur von RMI Die einzelnen Schichten des RMI Systems sollen mit der untersten Schicht beginnend in den folgenden Unterkapiteln näher erläutert werden. 3.2.1 Transportschicht Die Transportschicht ist sowohl für das Verbindungssetup und die Verbindungsverwaltung als auch für die Verwaltung der Remote-Objekte verantwortlich. Dies alles geschieht im Adressraum der Transportschicht. Es folgt eine Aufzählung der Aufgaben, welche die Transportschicht übernehmen muss. • Empfang der Anfragen der clientseitigen RRL. • Lokalisierung des RMI-Servers für die entfernte Objektanfrage. • Etablierung einer Socketverbindung mit dem Server. • Weiterleiten der Verbindung zur clientseitigen RRL. • Hinzufügen des Remote-Objektes zu einer Tabelle von Remote-Objekten, mit denen die Transportschicht kommunizieren kann. • Überwachung der Verbindung, ob sie noch steht. Seite 9 Remote Method Invocation (RMI) unter Java 3.2.2 Remote Reference Layer Diese Schicht verwaltet die Kommunikation zwischen Stubs und Skeletons und der Transportschicht, indem sie das Protokoll bestimmt, nach dem die entfernten Referenzen zugeordnet werden. Als Standard wird das Remote Reference Protocol (RRP) verwendet, welches unabhängig von den Stubs und Skeletons ist. Der RRL hat zwei Komponenten, eine clientseitige und eine serverseitige. Die clientseitige Komponente enthält spezifische Informationen des entfernten Servers und kommuniziert über die Transportschicht mit der serverseitigen Komponente. Die serverseitige Komponente ihrerseits implementiert die spezifische Referenz-Semantik (Remote Reference Semantic), bevor eine Anfrage an das Skeleton weitergeleitet wird. In der Referenz-Semantik des Servers abstrahiert der RRL die verschiedenen Arten, mit denen ein Objekt implementiert werden kann. So kann ein Objekt auf Servern implementiert sein, die immer auf einer Maschine laufen, oder auf Servern, die nur dann aktiv werden, wenn eine Anfrage auftritt. Dies wird dann als Aktivierung (Activation) bezeichnet. 3.2.3 Stubs und Skeletons Wenn von einem Objekt im Client eine Methode eines Serverobjekts aufgerufen werden soll, bzw. dem Serverobjekt eine Nachricht gesendet werden soll, wird ein lokales Stellvertreterobjekt verwendet, welches als Stub bezeichnet wird. Dieses existiert im Adressraum der JVM, welche das Clientobjekt ausführt und stellt somit für die restliche Applikation ein reguläres Java-Objekt dar. Die Stub-Methode fasst die Parameter des Methodenaufrufs zusammen, was auch als "parameter marshalling" bezeichnet wird, und zusammen mit der eindeutigen Referenz des Remote-Objekts und der Nummer der aufgerufenen Methode wird ein Datenblock gebildet, der über den Remote Reference Layer zum Server gesendet wird. Client Server Aufruf des Methodenaufruf Applikation Methodenaufruf, vom Stub weitergeleitet Stub Methodenergebnis eigentlichen Objektes Skeleton Methodenergebnis Objekt Methodenergebnis Abbildung 4: Stub und Skeleton Auf der Serverseite empfängt das entfernte Stellvertreterobjekt des eigentlichen RemoteObjektes, bezeichnet als Skeleton, über den dortigen Remote Reference Layer den Datenblock und ruft die gewünschte Methode des Serverobjekts auf. Rückgabewerte, wozu auch ausgelöste Ausnahme- bzw. Fehlerobjekte (Exceptions) gehören, werden auf dem analogen Weg zum Clientobjekt zurückgesendet. Seite 10 Remote Method Invocation (RMI) unter Java 3.3 Vorgehensweise bei der Implementierung einer RMI-Anwendung Der Entwicklungsprozess einer verteilten Anwendung mit RMI gliedert sich in mehrere Schritte, die in der folgenden Reihenfolge ausgeführt werden sollten. • Definieren einer Schnittstelle, in der alle öffentlichen Methoden des entfernt aufrufbaren Objektes deklariert werden. • Erstellen des Remote-Objektes, welches die Schnittstelle implementiert und zusätzlich intern benötigte Methoden definiert. • Kompilieren der Schnittstelle und des Remote-Objektes mit javac. • Stubs und Skeletons mit dem Tool rmic erzeugen. • Einen Client (Applet/Applikation) schreiben und kompilieren, der die entfernten Methodenaufrufe durchführt. • Die RMI Registry als Hintergrundprozess auf dem Rechner des Servers starten. • Das Remote-Objekt, d.h. den Server, ebenfalls starten. • Den Client starten. Die aufwendigeren Schritte werden in folgenden Unterkapiteln näher erläutert. 3.3.1 Definition der RMI-Schnittstelle In der RMI-Schnittstelle, auch Remote-Interface genannt, werden die Methoden deklariert, die von anderen Objekten (auf anderen JVMs) aufgerufen werden können. Hier werden die benötigten Methodennamen und deren Parameter- und Rückgabetypen festgelegt. Der folgende Quellcode zeigt ein einfaches Beispiel für ein Remote-Interface. public interface MyService extends java.rmi.Remote { void doSomething(Object param) throws java.rmi.RemoteException; } Indem ein Remote-Interface von der Schnittstelle java.rmi.Remote erbt, kennzeichnet es sich als eine Schnittstelle, deren Methoden von anderen JVMs aufgerufen werden können. Jedes Objekt, welches das Remote-Interface implementiert, wird zu einem Remote-Objekt. Die Methoden, die in der RMI-Schnittstelle definiert sind, müssen alle eine throws - Klausel für die java.rmi.RemoteException enthalten, da diese ausgelöst wird, wenn ein Fehler in der Kommunikation oder im Protokoll auftritt. Die Parameter- oder Rückgabewerte der Methoden können nahezu von jedem beliebigen Typ sein, lokale Objekte, Remote-Objekte und einfache Typen eingeschlossen. Die einzige Voraussetzung ist die Serialisierbarkeit der Objekte. In einfachen Datentypen und in RemoteObjekten ist diese Eigenschaft bereits implizit enthalten. Die anderen Objekte müssen die Schnittstelle java.io.Serializable implementiert haben, damit sie als Parameter- oder Rückgabewerte eingesetzt werden können. Seite 11 Remote Method Invocation (RMI) unter Java RMI nutzt den Mechanismus der Objektserialisierung, um die Objekte „by value“ zwischen Virtuellen Maschinen zu transportieren, d.h. es wird eine Kopie des Objektes erstellt. Die Implementierung von Serializable ermöglicht es einem Objekt, in eine Folge von Bytes umgewandelt zu werden. Nach dem Transport der Bytefolge an eine andere JVM kann eine exakte Kopie des serialisierten Objektes erstellt werden. Im Gegensatz zu allen anderen Objekten werden Remote-Objekte per Referenz übergeben. 3.3.2 Erstellen des Remote-Objektes Der Server erzeugt das Remote-Objekt und bindet es in die Registry ein. Diese Prozedur kann entweder in der main-Funktion der Implementierung des Remote-Objektes oder vollständig in einer anderen Klasse enthalten sein. Im folgenden Beispiel ist der Server in die Implementierung des Remote-Objektes integriert. public class MyServiceImpl extends java.rmi.server.UnicastRemoteObject implements MyService { public MyServiceImpl() throws java.rmi.RemoteException { super(); } void doSomething(Object param) throws java.rmi.RemoteException { ... } ... public static void main(String[] args) { System.setSecurityManager(new java.rmi.RMISecurityManager()); String name = "//host:2001/ServiceName"; try { MyServiceImpl obj = new MyServiceImpl(); Java.rmi.Naming.rebind(name, obj); catch (RemoteException e) { ... } } } Wie aus der Klassendeklaration hervorgeht, implementiert die Klasse des Remote-Objektes die Schnittstelle MyService aus dem vorherigen Kapitel und erbt zusätzlich von der Klasse UniCastRemoteObject. Diese Klasse ist von der abstrakten Klasse RemoteServer abgeleitet, welche eine Umgebung für das Funktionieren des RMI-Mechanismus bereitstellt. Die Klasse des Remote-Objektes muss einen Konstruktor enthalten, der ebenfalls eine java.rmi.RemoteException weiterleitet und den Konstruktor der Basisklasse mit dem super()-Befehl aufruft. Neben den Methoden der RMI-Schnittstelle können in der Klasse noch zusätzliche lokale Methoden für Berechnungen auf Server-Seite enthalten sein. Die Initialisierung des Remote-Objektes wird in der main-Funktion durchgeführt. Zuerst muss jedoch ein sogenannter Security-Manager installiert werden [11]. Dieser schützt die Systemressourcen vor Manipulation durch heruntergeladenen Code, der nicht Seite 12 Remote Method Invocation (RMI) unter Java vertrauenswürdig ist. Der Security-Manager legt z.B. fest, ob der heruntergeladene Code Zugriff auf das lokale Dateisystem hat. Mit RMISecurityManager wird ein Manager zur Verfügung gestellt, der einem Security-Manager für Applets gleicht. Um trotzdem den Zugriff auf das lokale Dateisystem zu ermöglichen, kann eine Datei java.policy für die Gewährung bestimmter Rechte eingesetzt werden [3]. Nach der Installation des Security-Managers wird eine Instanz des Remote-Objektes obj erzeugt und mit der Methode rebind() in die Registry eingebunden. Der Parameter name enthält den Namen des Hosts, auf dem die Registry läuft, und den Port, an den sie geknüpft ist. Wird die Portnummer weggelassen, so wird der Default-Port 1099 verwendet. Am Ende des Strings steht der Name, mit dem die Instanz in die Registry eingebunden werden soll. Aus Sicherheitsgründen darf eine Server-Anwendung nur die Registry benutzen, die auf dem gleichen Host läuft. 3.3.3 Implementierung des Clients Der folgende Quellcode zeigt die Schritte, welche der Client für den Aufruf einer entfernten Methode ausführen muss. public class MyClient extends Applet { ... System.setSecurityManager(new java.rmi.RMISecurityManager()); String name = "//host:2001/ServiceName"; try { MyService RMIServer = (MyService) java.rmi.Naming.lookup(name); RMIServer.doSomething(param); } catch (RemoteException e) { ... } ... } Bevor der Client die Verbindung zu einem Remote-Objekt herstellen darf, muss analog zum Server ein Security-Manager installiert werden. Danach sucht die Methode lookup() in der Registry des Server-Hosts nach dem Remote-Objekt mit dem Namen ServiceName. Ist die Suche erfolgreich, so wird die Referenz auf den Stub des Remote-Objektes zurückgeliefert und in der Variable RMIServer abgespeichert, die als eine RMI-Schnittstelle definiert ist. Mit Hilfe der Referenz kann die entfernte Methode doSomething() auf der Seite des Servers ausgeführt werden. 3.3.4 Starten der Registry Um mehrere Remote-Objekte in der Registry binden zu können, muss das Tool rmiregistry eingesetzt werden. Beim Starten von rmiregistry in der Kommandozeile ist es wichtig, dass die Umgebungsvariable CLASSPATH nicht auf Klassen des RemoteObjektes zeigt, da die Registry sonst nicht in der Lage ist, den Stub aus der JVM des Servers zu laden. Seite 13 Remote Method Invocation (RMI) unter Java 3.4 Dynamisches Herunterladen des Stubs Wie bereits in vorherigen Kapiteln erwähnt, benötigt der Client einen Stub des RemoteObjektes. Für das erfolgreiche Implementieren einer RMI-Anwendung kann es von Vorteil sein, die einzelnen Schritte beim Herunterladen des Stubs zu kennen. Der Client muss sich den Stub mit Hilfe eines Uniform Resource Locators (URL) besorgen. Dieser URL kann sich auf das Dateisystem des Rechners, einen FTP- oder einen Web-Server beziehen. Die einzelnen Schritte beim dynamischen Herunterladen des Stubs sollen anhand der Abbildung 5 erläutert werden. Client 2 Registry 3 1 5 Server 4 URL-Quelle myHost (file, ftp oder http) Abbildung 5: Dynamisches Herunterladen eines Stubs 1 Bevor der Server das Remote-Objekt in die Registry einbinden kann, muss er in die JVM-Eigenschaft java.rmi.server.codebase den URL für die Stub-Klassendatei eintragen. Daraufhin registriert der Server das Remote-Objekt in der Registry, indem er es an einen Namen bindet. Zusätzlich wird die Codebase, welche in der JVM des Servers eingetragen ist, an die Referenz des Remote-Objektes angehängt. 2 Mit Hilfe des Namens fordert der Client eine Referenz des Remote-Objektes an. Diese Referenz kann der Client benutzen, um Methodenaufrufe auf dem entfernten Objekt zu realisieren. 3 Die Registry liefert eine Referenz auf die angeforderte Klasse zurück. Wenn die Klassendefinition für die Instanz des Stubs lokal im Klassenpfad des Clients gefunden wird, lädt der Client die Klasse lokal und die Schritte 4 und 5 entfallen. Findet er sie jedoch nicht in seinem Klassenpfad, so versucht er die Klassendefinition von der Codebase des Remote-Objektes abzurufen. 4 Der Client fordert die Klassendefinition von der Codebase an, welche zu dem Zeitpunkt, an dem die Stub-Klasse in die Registry geladen wurde, an die Instanz des Stubs gehängt wurde. 5 Die Klassendefinition für den Stub wird an den Client übertragen. Seite 14 Algorithmen zum „Kürzeste Wege“-Problem 4 Algorithmen zum „Kürzeste Wege“-Problem Die GIS-Funktionalitäten, welche in dieser Arbeit für die Demonstration der Client-ServerAnwendung eingesetzt werden sollen, stammen aus dem Bereich der Netzwerkanalyse. Die folgenden Ausführungen zu dieser Thematik halten sich an das Skript zur Vorlesung GeoInformationssysteme III [2]. Ein Teilgebiet der Netzwerkanalyse beschäftigt sich mit der Lösung des „Kürzeste Wege“Problems, welches unterschiedliche Ausprägungen und Anwendungsgebiete hat. So kann man sich vorstellen, den kürzesten Weg zwischen einer Stadt A und einer Stadt B auffinden zu wollen. Besteht eine direkte Verbindung zwischen den beiden Städten, dann ist das Problem äußerst leicht zu lösen. Je mehr Zwischenpunkte oder Städte jedoch zwischen A und B liegen, um so komplizierter wird es, die kürzeste Verbindung zu finden. Die Definition des Begriffs kürzest spielt bei diesem Problem eine wichtige Rolle. Er kann sich auf die Zeitdauer, aber auch auf die Weglänge beziehen. Außerdem können noch zusätzliche Faktoren wie die Beschaffenheit der Straßen berücksichtigt werden. Dies zeigt, dass der Begriff kürzest immer auch auf die Anforderungen des jeweiligen Benutzers bezogen werden muss. Deshalb wird der Begriff Kosten eingeführt, der die einzelnen Faktoren in einem Wert zusammenfasst, wobei diese unterschiedlich gewichtet werden können. Die Graphentheorie stellt nun wichtige Werkzeuge für die Lösung des Problems zur Verfügung, d.h. das Problem kann auf einen Graphen abgebildet werden. Ein Graph besteht aus Knoten und Kanten und kann ungerichtet oder gerichtet sein. Außerdem können die Kanten mit einer Bewertung versehen werden. Dadurch können die unterschiedlichen Anforderungen der Benutzer in das Problem miteinbezogen werden, d.h. jede Kante erhält ein Maß für die Kosten des Weges. Die folgenden Probleme werden im Zusammenhang mit den Kürzesten Wegen behandelt: • Single Source Shortest Path: Kürzeste Wege von einer Quelle zu allen anderen Knoten • All-Pairs Shortest Path: Kürzeste Wege zwischen allen Knoten • Zentrumsproblem: z.B. optimaler Standort für ein Kaufhaus • Minimales Gerüst: Suche nach minimaler Länge der Verbindungen zwischen gegebenen Punkten • Rundreiseproblem: Besuch aller Städte in kürzester Zeit Für diese Arbeit wurden die ersten beiden Probleme genauer untersucht und die Lösungen in Java implementiert. Auf die Algorithmen zur Lösung des Single Source Shortest Path und des All-Pairs Shortest Path Problems wird in den folgenden Unterkapiteln eingegangen werden. Zuvor sollen allerdings die benötigten Werkzeuge der Graphentheorie erläutert werden. Seite 15 Algorithmen zum „Kürzeste Wege“-Problem Beide genannte Aufgaben werten einen gerichteten Graphen aus. Es gibt verschiedene Datenstrukturen für einen gerichteten Graphen. Die Wahl der Datenstruktur orientiert sich an der jeweiligen Aufgabenstellung. Für die beiden zu untersuchenden Probleme bietet sich die Speicherung des Graphen in einer Nachbarschaftsmatrix (Adjazenzmatrix) an. Abbildung 6 zeigt in (a) einen gerichteten Graphen und in (b) dessen Abbildung auf eine Adjazenzmatrix. An den Stellen, an denen es eine Verbindung zwischen den Knoten gibt, wird eine 1 eingetragen. Die restlichen Elemente sind 0. Ein Vorteil der Adjazenzmatrix ist der schnelle Zugriff auf die einzelnen Elemente der Matrix, der unabhängig von der Anzahl der Kanten oder Knoten ist. Als nachteilig muss jedoch der hohe Speicherbedarf vom Grad n2 angesehen werden (n ist die Anzahl der Knoten). Da bei den meisten Graphen die Anzahl der Kanten viel kleiner als n2 ist, werden viele Nullen mitabgespeichert. 0 1 2 3 − 0 0 0 1 1 − 0 0 − 1 0 (b) (a) Abbildung 6: 1 0 1 − (a) Gerichteter Graph (b) Adjazenzmatrix ∞ 30 50 20 ∞ ∞ ∞ ∞ ∞ ∞ ∞ 10 ∞ 40 ∞ ∞ (c) (c) Kostenmatrix Für die Auswertung des gerichteten Graphen in den zu untersuchenden Problemen werden neben den gerichteten Verbindungen zwischen den Knoten auch die Kosten der einzelnen Kanten benötigt. Eine Adjazenzmatrix, welche diese Kosten berücksichtigt, bezeichnet man als Kostenmatrix. Wie man in Abbildung 6 (c) erkennen kann, wird an den Stellen, an denen es keine Verbindung gibt, das Symbol für unendlich eingetragen. Damit wird ausgedrückt, dass der Weg auf keinen Fall über diese Verbindung führen kann, da die Kosten ansonsten ins Unendliche steigen würden. In der Studienarbeit wird an diesen Stellen für die Darstellung im Rechner eine -1 eingesetzt, die dann aber für die Berechnungen durch eine sehr große Zahl ersetzt wird. 4.1 Single Source Shortest Path Bei diesem Problem ist ein attributierter Graph G = (V,E) gegeben, d.h. es sind die Kosten der einzelnen Verbindungen an den Kanten (E) angebracht. Außerdem wird der Startknoten als die Quelle bezeichnet. Die Aufgabe besteht nun darin, „ die Kosten des kürzesten Weges von der Quelle zu jedem anderen Knoten der Knotenmenge V zu bestimmen. Die Länge des Weges ist dabei die Summe der Kosten der besuchten Kanten des Pfades.“ [2]. Für die Lösung des Problems kann der Algorithmus von Dijkstra verwendet werden. Dieser wird im folgenden Unterkapitel eingehend erläutert werden. Seite 16 Algorithmen zum „Kürzeste Wege“-Problem 4.1.1 Der Dijkstra-Algorithmus Der Dijkstra-Algorithmus kann mit der folgenden Definition kurz umschrieben werden: In einer Menge S werden alle besuchten Knoten gesammelt. Zunächst ist nur die Quelle in S enthalten. In jedem Schritt wird der Knoten aus der Menge V-S zu S hinzugefügt, der die geringste Distanz zur Quelle hat. Diese minimalen Distanzen werden in einem Vektor abgespeichert. Die Kosten für die Verbindung zweier Knoten werden einer Kostenmatrix entnommen. Beispiel In Abbildung 7 ist ein attributierter Graph in Form einer Kostenmatrix gegeben. 0 80 0 10 1 4 40 40 20 10 2 3 0 1 2 3 4 1 2 3 4 ∞ 80 ∞ 40 10 ∞ ∞ 10 ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ 40 30 ∞ ∞ ∞ ∞ ∞ 20 ∞ 30 Abbildung 7: Beispielgraph in Form einer Kostenmatrix Die folgende Tabelle zeigt die Iterationsschritte des Dijkstra-Algorithmus für den Graphen in Abbildung 7. In S werden die bereits besuchten Knoten abgespeichert. Der Vektor d nimmt die minimalen Distanzen zur Quelle 0 auf, er wird zu Beginn des Algorithmus mit der ersten Zeile der Kostenmatrix initialisiert. Im Vektor p wird der jeweilige Vorgängerknoten notiert. i 1 2 3 4 S [0] [0,4] [0,4,3] [0,4,3,2] d(0), p(0) - d(1), p(1) 80, 0 80, 0 70, 3 70, 3 d(2), p(2) ∞, 0 ∞, 0 60, 3 - d(3), p(3) 40, 0 30, 4 - d(4), p(4) 10, 0 - In der ersten Iteration wird der Knoten 4 ausgewählt, der die minimale Distanz von 10 zum Quellknoten hat. Daraufhin ändert sich die Distanz zum Knoten 3, da der Weg über den Knoten 4 mit 10 + 20 = 30 kürzer ist als der direkte Weg von der Quelle. Zusätzlich wird der Knoten 4 als Vorgängerknoten in p(3) festgehalten. Die nächsten Iterationsschritte laufen analog zum ersten Schritt ab. Das Ergebnis des Algorithmus sind die Vektoren d und p. Der Vektor d enthält die minimalen Distanzen der einzelnen Knoten zur Quelle. Im Vektor p sind die Pfade zu den verschiedenen Knoten implizit abgespeichert, d.h. sie können mit Hilfe von p rekonstruiert werden. Seite 17 Algorithmen zum „Kürzeste Wege“-Problem Die Rekonstruktion des Pfades soll am Beispiel des Knoten 2 erläutert werden. Dort ergibt sich die Abfolge: p(2) = 3, p(3) = 4, p(4) = 0. Somit verläuft der kürzeste Weg von der Quelle 0 zum Knoten 2 über 4 und 3. 4.1.2 Implementierung des Dijkstra-Algorithmus in Java In diesem Abschnitt sollen die wichtigsten Teile aus der Java-Implementierung erläutert werden. Der gesamte Quellcode des Dijkstra-Algorithmus befindet sich im Anhang dieser Studienarbeit. Zu Beginn wird die übergebene Kostenmatrix kopiert, deren Größe size bestimmt und es werden die Elemente, in denen -1 steht, auf eine sehr große Zahl INFINITE gesetzt. Danach beginnt der eigentliche Schritt der Initialisierung, d.h. es werden Arrays definiert und mit Startwerten belegt. int[] p = new int[size]; int[] d = new int[size]; for(int i=0;i<size;i++) d[i] = costMatrix[0][i]; int[] S = new int[1]; S[0] = 0; int[] T = new int[size-1]; for (int i=1;i<size;i++) T[i-1] = i; Wie aus dem Java-Code ersichtlich ist, werden alle Elemente des Pfadarrays p[] mit dem Knoten 0 vorbelegt, der die Quelle des Algorithmus ist (Java initialisiert die Werte in p[] automatisch mit 0). In das Distanzarray d[] wird die erste Zeile der Kostenmatrix geschrieben. Daraufhin werden die Listen S und T als Arrays angelegt. In S[] wird der Knoten 0, in T[] werden die restlichen Knoten abgespeichert. Nach diesem Initialisierungsschritt beginnt die Hauptschleife des Algorithmus, die insgesamt (size-1)mal durchlaufen wird. In jedem Schleifendurchlauf wird zuerst das kleinste Element und dessen Index in d bestimmt. min_d = d[T[0]]; min_element = T[0]; // min_d = minimale Kosten zum Quellknoten // min_element = Knoten mit den geringsten Kosten for (int j=1;j<T.length;j++){ if (d[T[j]]<min_d){ min_d = d[T[j]]; min_element = T[j]; } } Der Knoten mit den geringsten Kosten wird aus T[] gelöscht und in S[] eingetragen. Da bei der Implementierung des Algorithmus mit Absicht auf komplexere Speicherstrukturen verzichtet wurde, um den Quellcode so einfach wie möglich zu halten, werden für diesen Schritt zwei temporäre Arrays angelegt. Seite 18 Algorithmen zum „Kürzeste Wege“-Problem Danach erfolgt für jeden Knoten in T[] eine Überprüfung, ob der Weg über den soeben gelöschten Knoten min_element kürzer als der direkte Weg vom Quellknoten ist. Wenn diese Bedingung erfüllt ist, wird min_element in den Pfad und die Kosten des kürzeren Weges in d[] eingetragen. for(int j=0;j<T.length;j++){ if((d[min_element] + costMatrix[min_element][T[j]]) < d[T[j]]) p[T[j]] = min_element; d[T[j]] = d[min_element] + costMatrix[min_element][T[j]]; } Nachdem die Hauptschleife durchlaufen ist, stehen in d[] die minimalen Kosten für den Weg vom Quellknoten zu den anderen Knoten. Der Pfad kann über das Array p[] rekonstruiert werden, da für jeden Knoten der Vorgängerknoten abgespeichert wurde. 4.2 All-Pairs Shortest Path Der Ausgangspunkt für diesen Algorithmus ist wiederum ein attributierter Graph G = (V,E). Die Aufgabe besteht nun darin, „die Kosten des kürzesten Weges zwischen jedem Knotenpaar der Knotenmenge V zu bestimmen. Die Länge des Weges ist dabei die Summe der Kosten der besuchten Kanten des Pfades.“ [2]. Die Lösung des Problems kann theoretisch durch die Anwendung des Dijkstra-Algorithmus auf alle n Knoten als Quelle erfolgen. Eine elegante Alternative ist der Algorithmus von Floyd. Beide Möglichkeiten liegen vom Rechenaufwand her im gleichen Bereich, der FloydAlgorithmus ist allerdings einfacher zu implementieren. 4.2.1 Der Floyd-Algorithmus Die Eingangsgröße beim Floyd-Algorithmus ist eine Kostenmatrix. Alle Berechnungen im Algorithmus können auf den Elementen dieser Matrix durchgeführt werden. Der Algorithmus wird in Abbildung 8 veranschaulicht. k (a) Ak −1 (i , j ) Ak (i , j ) = min Ak −1 (i , k ) + Ak −1 (k , j ) Abbildung 8: (a) Formel für Floyd-Algorithmus (b) i j (b) Geometrischer Grundgedanke Der Algorithmus hat n Iterationsschritte, wobei n die Größe der Kostenmatrix ist. Der Parameter k, der vor dem ersten Schritt auf 0 gesetzt wird, läuft von 0 bis n-1. In jedem Schritt wird die gesamte Matrix so aufdatiert, dass im Element Ak(i,j) jeweils der kürzeste Weg von Knoten i zum Knoten j verzeichnet ist. Die zwei möglichen Wege sind in Abbildung 8 (b) dargestellt. Nach der Aufdatierung wird k um 1 erhöht. Folglich werden in jedem Schritt nur die Knoten berücksichtigt, deren Verbindungen in den vorhergehenden Schritten bereits minimiert wurden. Seite 19 Algorithmen zum „Kürzeste Wege“-Problem 4.2.2 Implementierung des Floyd-Algorithmus in Java Am Anfang der Implementierung wird die übergebene Kostenmatrix in eine Matrix d[][] kopiert, auf welcher die Berechnungen durchgeführt werden. Dies gewährleistet, dass die Kostenmatrix nicht überschrieben wird. Außerdem werden die Elemente, in denen eine -1 steht auf eine sehr große Zahl INFINITE gesetzt. Nach diesem Initialisierungsschritt wird der Floyd-Algorithmus auf die Matrix d[][] angewendet. for(int k=0;k<size;k++){ for(int i=0;i<size;i++){ for(int j=0;j<size;j++){ d[i][j] = Math.min(d[i][j],(d[i][k] + d[k][j])); } } } Danach werden Elemente, die auf INFINITE stehen, wieder auf -1 zurückgesetzt. Das Ergebnis des Algorithmus steht nun in der Matrix d[][]. Seite 20 Lösungsansatz und Implementierung der RMI-Anwendung 5 Lösungsansatz und Implementierung der RMI-Anwendung Ziel der Studienarbeit ist es, GIS-Funktionalitäten mit Hilfe von RMI zur Verfügung zu stellen. Diese Funktionalitäten sollen von verschiedenen Servern bereitgestellt und von einem Client genutzt werden (siehe Abbildung 9). Server 1 GIS-Funktionalität 1 GIS-Funktionalität n Client Schnittstelle Server n GIS-Funktionalität 1 GIS-Funktionalität n Abbildung 9: Verteilte GIS-Funktionalitäten Für den Zugriff auf die einzelnen Funktionalitäten benötigt der Client eine Schnittstelle, welche die Kommunikation zwischen Client und Server ermöglicht. Die Server sollen in der Lage sein, die GIS-Funktionalitäten direkt auf ihrer Seite auszuführen und das Ergebnis an den Client zu senden. Da aber auch das Herunterladen der Funktionalitäten auf den ClientRechner möglich sein soll, müssen diese in einzelnen Objekten zusammengefasst werden. Mittels RMI können die Objekte an den Client-Rechner übermittelt und deren Methoden ausgeführt werden. Damit der Client die benötigten Funktionalitäten auf den Servern finden kann, muss jeder Server eine Liste der verfügbaren GIS-Funktionalitäten bereitstellen. Nach der erfolgreichen Verbindung zum Server lädt der Client die Liste herunter, um sie einer Suchfunktion für GISFunktionalitäten zur Verfügung zu stellen. Die Implementierung der RMI-Anwendung erfolgt nach der Vorgehensweise in Kapitel 3.3. Bevor die einzelnen Server und der Client erstellt werden können, müssen sinnvolle Schnittstellen definiert werden, welche die Umsetzung des vorgestellten Ansatz in eine Client-Server-Anwendung ermöglichen. Die folgenden Unterkapitel werden die wichtigsten Teile der Implementierung ausführlich beschreiben. Seite 21 Lösungsansatz und Implementierung der RMI-Anwendung 5.1 Schnittstellen Bevor die entfernt aufrufbaren Methoden des Servers definiert werden können, muss eine Schnittstelle für das Ausführen einer GIS-Funktionalität festgelegt werden. Die Schnittstelle GISTask erweitert Serializable, damit sie in den entfernt aufrufbaren Methoden des Servers als Parameter eingesetzt werden kann. Jede GIS-Funktion, die in der Anwendung eingesetzt werden soll, muss in eine Klasse umgeschrieben werden, welche die Schnittstelle GISTask implementiert. public interface GISTask extends Serializable { String getName(); void init(Object param); Object execute(); } Um die Identität der GIS-Funktion kontrollieren zu können, liefert die Methode getName() den Namen der Funktionalität. In der Methode init(Object param) werden dann die Eingabeparameter an die GIS-Funktion übergeben. Da param vom Typ Object ist, können alle Typen, die von Object abgeleitet sind, verwendet werden. Dies bedeutet, dass mit Hilfe von Container-Klassen, wie z.B. Vector, beliebig viele Parameter unterschiedlichen Typs an die GIS-Funktion übermittelt werden können. Nachdem die Funktion nun initialisiert ist, wird sie mittels execute() ausgeführt. Das Ergebnis der Funktion wird in den Rückgabewert der execute()-Methode geschrieben und ist ebenfalls vom Typ Object. Nach der Definition von GISTask kann die Definition des Remote-Interfaces für die ClientServer-Anwendung erfolgen. Die Schnittstelle FunctionServer muss von jedem RemoteObjekt implementiert werden. In unserem Fall wird das Remote-Objekt die Funktion eines Servers für GIS-Funktionalitäten übernehmen. public interface FunctionServer extends java.rmi.Remote { GISTask getFunction(String name) throws java.rmi.RemoteException; Object executeFunction(String name, Object param) throws java.rmi... String[] getFunctionList() throws java.rmi.RemoteException; } Bevor ein Client auf die GIS-Funktionen zugreift, holt er sich mit getFunctionList() die Liste der verfügbaren GIS-Funktionalitäten. Der Client ist nun in der Lage, mit Hilfe von getFunction(String name) die Funktion herunterzuladen, welche die Bezeichnung name hat. Diese Bezeichnung muss eindeutig und sowohl Server als auch Client bekannt sein. Die Methode executeFunction(String name, Object param) ermöglicht das Ausführen der Funktion auf dem Rechner des Servers. Die benötigten Eingabeparameter werden in param übergeben. Nachdem die erforderlichen Schnittstellen definiert sind, kann mit der Implementierung von Server und Client begonnen werden. Seite 22 Lösungsansatz und Implementierung der RMI-Anwendung 5.2 Server Die eingesetzte Client-Server-Architektur ermöglicht den Einsatz mehrerer Server. Diese Server müssen in Abhängigkeit der vorhandenen GIS-Funktionalitäten implementiert werden, d.h. die GIS-Funktionalitäten werden in den Quellcode der Server aufgenommen. Server und Remote-Objekt sind identisch, da die main-Funktion des Remote-Objektes die Funktion des Servers übernimmt. Analog zu Kapitel 3.3.3 erfolgt die Deklaration der Klasse. public class FunctionServerOne extends UnicastRemoteObject implements FunctionServer Die Bezeichnung der Klasse ist beliebig, sollte jedoch die Funktion der Klasse wiedergeben. FunctionServerOne übernimmt die Schnittstelle FunctionServer aus 5.1. Um die Serverfunktion der Klasse zuzulassen, müssen die entfernt aufrufbaren Methoden in FunctionServerOne implementiert werden. Für die Einbindung der RMI-Umgebung wird die Klasse UnicastRemoteObject geerbt. Die Implementierungen der entfernt aufrufbaren Methoden werden in den folgenden Absätzen beschrieben. Die Methode getFunctionList() liefert die Liste der vorhandenen GIS-Funktionalitäten. public String[] getFunctionList(){ String[] list = {"SingleSourceShortestPath"}; return list; } Das String-Array list wird mit der Liste initialisiert, die bereits vor der Implementierung des Servers bekannt sein muss, und an den aufrufenden Client übergeben. Mit Hilfe der Methode getFunction(String name) kann der Client das Objekt einer GISFunktion herunterladen. public GISTask getFunction(String name){ if (name.equals(new String("SingleSourceShortestPath"))==true){ System.out.println("SingleSourceShortestPath-Objekt exportieren"); return new SingleSourceShortestPath(); } return new EmptyFunction(); } Zu Beginn der Methode wird für jede vorhandene GIS-Funktionalität eine if-Abfrage durchgeführt, die überprüft, ob der Bezeichner der angeforderten Funktionalität mit dem der jeweiligen GIS-Funktionalität übereinstimmt. Ist dies der Fall, so wird das relevante Objekt erzeugt und an den Client übermittelt. Wenn die angeforderte GIS-Funktionalität nicht auf dem Server vorhanden ist, wird die Instanz eines GISTask-Objektes erzeugt, welche in der Funktion getName() den String "EmptyFunction" zurückgibt. Dies ermöglicht dem Client, eine Kontrolle durchzuführen. Seite 23 Lösungsansatz und Implementierung der RMI-Anwendung Mit der Methode executeFunction(String name, Object param) kann der Client eine GIS-Funktionalität direkt auf dem Server ausführen. Allerdings müssen dazu die Eingabeparameter in der Variablen param übergeben werden. public Object executeFunction(String name,Object param){ if (name.equals(newString("SingleSourceShortestPath"))==true){ System.out.println("SingleSourceShortestPath-Objekt wird direkt ausgefuehrt..."); SingleSourceShortestPath ssp = new SingleSourceShortestPath(); ssp.init(param); return ssp.execute(); } Vector result = new Vector(); result.add(new Boolean(false)); return result; } Auch in dieser Methode wird zuerst ein Vergleich der angeforderten Funktionalität mit den vorhandenen GIS-Funktionalitäten gemacht. Wenn es zu einer Übereinstimmung kommt, wird das relevante Objekt erzeugt, mit den Eingabeparametern initialisiert und ausgeführt. Lediglich das Ergebnis der Funktion wird an den Client übergeben. Das Ergebnis sollte immer ein Vektor sein, in dem das erste Element ein Boolean-Objekt ist, welches Auskunft über den Erfolg der Ausführung gibt. Dadurch kann am Ende der Methode auch eine nicht vorhandene Funktionalität abgefangen werden. In der Methode main(String args[]) sind die Serverfunktionalitäten implementiert. Im Quellcode stehen lediglich die relevanten Schritte für das Starten des Servers. public static void main(String args[]) { ... try { FunctionServerOne obj = new FunctionServerOne(); Naming.rebind("//"+args[0]+"/"+args[1], obj); } catch (Exception e) { ... } } } Zunächst wird das Remote-Objekt obj erzeugt. Danach wird versucht, dass Objekt in die Registry einzubinden, die zuvor gestartet worden ist. Mit Hilfe der Argumente args[] der Befehlszeile kann der erste Parameter von rebind() den jeweiligen Eigenschaften der Registry und des Servers angepasst werden. In args[0] wird der Host und die Portnummer der Registry angegeben. Das Argument hat das Format host:port, wobei die Portnummer auch weggelassen werden kann. Dann wird die Portnummer auf den Default-Wert 1099 eingestellt. Das Argument args[1] gibt den Bezeichner des Servers an, mit dem der Client den Server auf dem entfernten Rechner finden kann. Seite 24 Lösungsansatz und Implementierung der RMI-Anwendung 5.3 Client Das Client wurde als Java-Applikation unter Verwendung der Swing-Klassen implementiert, damit der Benutzer auf das Dateisystem des Client-Rechners zugreifen kann. Während das nächste Unterkapitel eine Art Bedienungsanleitung des Programms ist, erläutert das darauf folgende Unterkapitel die Umsetzung der wichtigsten Bestandteile in Java. 5.3.1 Bedienung der graphischen Oberfläche Nach dem Starten des Clients erscheint die graphische Oberfläche, die in Abbildung 10 dargestellt ist. Abbildung 10: Swing-Oberfläche des Clients Bevor der Benutzer jedoch mit dem Programm interagieren kann, lädt der Client eine Initialisierungsdatei, welche als Argument in der Befehlszeile angegeben wurde. Dabei ist wichtig, dass der vollständige Pfad der Datei angegeben wird. Die Initialisierungsdatei hat das folgende Format. Server-Einstellungen d:\studienarbeit-gis\RMIApp\Client\server.txt Arbeitsverzeichnis d:\studienarbeit-gis\RMIApp\Client In der ersten Zeile steht eine Textdatei für die Server-Einstellungen, in der zweiten das Arbeitsverzeichnis des Clients. Die Textdatei server.txt ist folgendermaßen gegliedert. ifppcz 1099 ServerOne true ifppcx 1099 ServerTwo true In jeder neuen Zeile stehen die Einstellungen für einen Server. Die erste Spalte enthält den Namen des Hosts, auf dem der Server läuft. In der zweiten Spalte steht die Portnummer der Registry und in der dritten der Bezeichner des Servers. Die vierte Spalte gibt an, ob eine Verbindung zu diesem Server hergestellt werden soll und kann die Werte true oder false enthalten. Über den Menüpunkt Einstellungen/Server-Einstellungen können die Werte vor dem Verbindungsaufbau noch geändert werden (siehe Abbildung 11). Auch das Laden einer neuen Datei mit Server-Einstellungen ist möglich. Nachdem die Server-Einstellungen korrekt eingestellt sind, kann über Datei/Verbinden versucht werden, die Verbindungen zu den Servern herzustellen. Seite 25 Lösungsansatz und Implementierung der RMI-Anwendung Scheitert der Verbindungsaufbau zu einem Server, dann gibt der Client eine Fehlermeldung mit den Einstellungen des Servers aus. Die Anzahl der verbundenen Server wird in der Titelleiste des Clients angezeigt (siehe Abbildung 12). Damit das Menü Funktionen, in dem die GIS-Funktionalitäten aufgerufen werden, aktiviert werden kann, muss mindestens ein Server angeschlossen sein. Abbildung 11: Server-Einstellungen Für die Demonstration des RMI werden zwei Algorithmen zur Lösung von „Kürzeste Wege“Problemen eingesetzt. Diese benötigen als Eingabeparameter eine Kostenmatrix. Die Matrix ist in der Textdatei gespeichert und wird über den Menüpunkt Datei/Graph einlesen geladen. Damit das Einlesen fehlerfrei abläuft, muss die Datei folgendes Format haben: 5 -1 -1 -1 -1 -1 10 -1 -1 -1 -1 -1 50 -1 20 -1 30 100 -1 -1 -1 10 -1 60 -1 -1 In der ersten Zeile steht die Dimension n der Matrix. Die darauffolgenden Zeilen enthalten die jeweiligen Elemente der Kostenmatrix. Alle fehlenden Verbindungen werden mit -1 eingegeben. Nach dem erfolgreichen Laden der Kostenmatrix wird das Menü Funktionen aktiviert, da nun die Algorithmen auf die Matrix angewendet werden können. Abbildung 12: Verfügbare GIS-Funktionalitäten Seite 26 Lösungsansatz und Implementierung der RMI-Anwendung In Abbildung 12 sind die beiden Untermenüs von Funktionen dargestellt. Wählt der Benutzer das Untermenü client-basiert aus, so werden die GIS-Funktionalitäten auf den Rechner des Clients heruntergeladen und dort ausgeführt. Im Untermenü server-basiert können die gleichen Funktionalitäten aufgerufen werden, jedoch werden diese auf dem Rechner des Servers ausgeführt. Lediglich das Ergebnis wird an den Client übermittelt. Für den Fall, dass sich die Liste der verfügbaren Server während der Ausführung des Programms ändert, kann die Verbindung zu den Servern über Datei/Trennen gelöst werden. Dadurch wird es möglich, die Server-Einstellungen der neuen Situation anzupassen. 5.3.2 Implementierung des Clients Die Gestaltung der graphischen Oberfläche des Clients wurde mit der integrierten Entwicklungsumgebung Forte for Java, Community Edition 1.0 von SUN Microsystems durchgeführt. Einige Teile des Quellcodes, welche die graphische Oberfläche betreffen, sind von der Entwicklungsumgebung automatisch eingefügt worden und sollten daher nicht verändert werden. In diesem Abschnitt werden die Bestandteile der Java-Implementierung näher erläutert, welche die Interaktion des Clients mit dem Server ermöglichen. Die Methode VerbindenActionPerformed() stellt die Verbindung vom Client zu den ausgewählten Servern her. Die Server-Einstellungen sind in dem zweidimensionalen Array serverdata abgelegt, das vom Typ Object ist. Der folgende Quellcode zeigt den wichtigsten Teil der Methode. int serverindex = 0; // Zähler für ausgewählte Server for(int i=0;i<serverdata.length;i++){ if (((Boolean)serverdata[i][3]).booleanValue()==true){ try { host = (String)serverdata[i][0]; port = ((Integer)serverdata[i][1]).toString(); servername = (String)serverdata[i][2]; functionObjectList[serverindex] = (FunctionServer)Naming.lookup("//"+host+":"+port+"/"+servername); functionList[serverindex] = functionObjectList[serverindex].getFunctionList(); ... } catch (Exception e) { ... } serverindex++; } } In jedem Durchlauf der for-Schleife wird überprüft, ob der Server ausgewählt ist. Wenn dies der Fall ist, wird die RMI-Methode lookup() ausgeführt, um eine Referenz auf den Stub des Remote-Objektes zu erhalten. Ist die Methode erfolgreich, so wird die Referenz in die Liste functionObjectList aufgenommen, die vom Typ FunctionServer ist. Außerdem wird die Liste der verfügbaren GIS-Funktionalitäten mit getFunctionList() vom Server heruntergeladen. Seite 27 Lösungsansatz und Implementierung der RMI-Anwendung Die Methode searchFunction(String func) durchsucht die Funktionalitäten-Listen der verbundenen Server nach dem Bezeichner func und liefert den Index des ermittelten Servers in der functionObjectList zurück. Mit der Methode ssp_cbActionPerformed() wird das Single Source Shortest Path Problem auf der Clientseite gelöst. Der folgende Quellcode zeigt, welche Schritte dafür notwendig sind. int index = searchFunction("SingleSourceShortestPath"); ... try { GISTask t = functionObjectList[index].getFunction("SingleSourceShortestPath"); t.init(costMatrix); result = (Vector)t.execute(); } catch (Exception e) { ... } Zu Beginn wird die GIS-Funktionalität SingleSourceShortestPath in den Listen der Server gesucht. Wenn die Funktionalität auf keinem der Server vorhanden ist, gibt die Methode searchFunction() eine -1 zurück und der Client gibt eine Fehlermeldung aus. Ansonsten holt sich der Client die Funktionalität mittels getFunction() und führt sie auf seiner Seite aus. In der Methode sspActionPerformed() wird die GIS-Funktionalität auf der Seite des Servers ausgeführt. int index = searchFunction("SingleSourceShortestPath"); try { result = (Vector)functionObjectList[index].executeFunction( "SingleSourceShortestPath",costMatrix); } catch (Exception e) { ... } Nachdem der richtige Server gefunden worden ist, wird das „Kürzeste Wege“-Problem mittels executeFunction() direkt auf dem Server gelöst und das Ergebnis in den Lösungsvektor result geschrieben. Als zweiter Parameter wird die Kostenmatrix in Form eines zweidimensionalen Arrays vom Typ Integer übergeben. Das Trennen des Clients von den Servern ist denkbar einfach realisiert. In der Methode TrennenActionPerformed() werden sämtliche Listen, welche die Server betreffen, auf null gesetzt. functionObjectList = null; connectedToFunctionObject = null; functionList = null; Dies hängt damit zusammen, dass RMI einen speziellen Mechanismus zur Beseitigung von nicht referenzierten Remote-Objekten hat (Garbage Collection). Seite 28 Test der Client-Server-Anwendung 6 Test der Client-Server-Anwendung Dieser Test soll Aufschluss über die Funktionsfähigkeit der Client-Server-Anwendung in einem Netzwerk geben. Deshalb wird die Anwendung auf drei Rechner verteilt, die im Studentenraum des ifp stehen. Wie aus Abbildung 13 hervorgeht, werden zwei Server für die Bereitstellung von GIS-Funktionalitäten verwendet. ifppcx Client ifppcz Class-Server ifppcv Registry Registry Class-Server Single Source Shortest Path All-Pairs Shortest Path FunctionServerOne FunctionServerTwo Abbildung 13: Test-Konfiguration Der FunctionServerOne stellt den Dijkstra-Algorithmus zum Lösen des Single Source Shortest Path Problems zur Verfügung, der FunctionserverTwo den Floyd-Algorithmus für das All-Pairs Shortest Path Problem. Neben der Registry wird ein Class-Server benötigt, der dem Client die erforderlichen class-Dateien liefert. Diese Aufgabe kann auch durch einen Web-Server erfüllt werden. 6.1 Initialisierung der Server Zu Beginn des Tests müssen die Server ordnungsgemäß eingerichtet werden. Die nötigen Schritte sollen anhand des FunctionServerOne gezeigt werden, der auf dem Rechner mit der Bezeichnung ifppz laufen wird. 1) Starten der Registry mit dem Kommando rmiregistry, d.h. der Default-Port 1099 wird verwendet. 2) Starten des Class-Servers mit dem Kommando java classServer.ClassFileServer 2001 ../Server1/classes. Die Datei ClassFileServer.class liegt in dem Unterverzeichnis classServer. Das erste Argument ist die Portnummer, auf welcher der Class-Server arbeiten soll und das zweite Argument gibt das Basisverzeichnis für die class-Dateien an. Seite 29 Test der Client-Server-Anwendung 3) Starten des Servers mit dem Kommando java -Djava.security.policy=java.policy -Djava.rmi.server.codebase=http://ifppcz:2001/ -Djava.rmi.server.hostname=ifppcz server.FunctionServerOne ifppcz ServerOne Die Datei java.policy legt die Einstellungen im Security-Manager [11] fest und muss im aktuellen Verzeichnis liegen. Der Einfachheit halber wurden in java.policy alle Rechte gewährt (permission java.security.AllPermission;). Die Codebase wird auf die URL-Adresse des Class-Servers gesetzt und als Hostname der Rechnername angegeben. Am Ende des Kommandos werden die Parameter für das Binden des Servers an die Registry übergeben (siehe Kapitel 5.2). Wie aus dem Protokoll des Class-Servers hervorgeht, lädt die Registry in diesem Schritt die Dateien FunctionServerOne_Stub.class, FunctionServer.class und GISTask.class herunter. 6.2 Ausführung von GIS-Funktionalitäten Nach der erfolgreichen Initialisierung der Server werden die Server-Einstellungen des Clients an die Situation angepasst. Danach wird der Client gestartet und mit Datei/Verbinden werden die Verbindungen zu den Servern hergestellt. Dabei fällt auf, dass die Stubs der Server von den jeweiligen Class-Servern geladen werden. Für den Test der GIS-Funktionalitäten wird der Graph aus Kapitel 4.1.1, Abbildung 7 verwendet. Die Kostenmatrix des Graphen wurde mittels Datei/Graph einlesen in das ClientProgramm geladen (siehe Abbildung 14). Abbildung 14: Server-basiertes Ausführen des Single Source Shortest Path Seite 30 Test der Client-Server-Anwendung In dem Test sollen beide GIS-Funktionen sowohl server- als auch clientbasiert ausgeführt werden. Die dabei auftretenden Aktionen auf Client- und Serverseite werden in den nächsten Absätzen mittels Tabellen aufgezeigt, welche die Ausgaben in den Konsolen der Server, Class-Server und dem Client darstellen. Bei jedem Ausführen einer Funktionalität wird auch die Bearbeitungszeit aufgezeichnet. • Single Source Shortest Path (server-basiert) Die Lösung des Single Source Shortest Path Problems ist in Abbildung 14 dargestellt. Wie aus der Abbildung zu erkennen ist, beträgt die Bearbeitungszeit beim ersten Ausführen der GIS-Funktionalität 661 ms. Bei einem erneuten Ausführen sinkt diese allerdings auf 80 ms. Server SingleSourceShortestPath-Objekt wird direkt ausgefuehrt... Berechnung von SingleSourceShortestPath laeuft... Class-Server Client Die Tabelle zeigt die gewünschten Effekte, d.h. die Funktion wird direkt auf dem Server gestartet und dort berechnet. • Single Source Shortest Path (client-basiert) Die Bearbeitungszeit bei der ersten Berechnung beträgt 260 ms, danach durchschnittlich 30 ms. Server Class-Server Client SingleSourceShortestPath-Objekt exportieren... reading: server.SingleSourceShortestPath Berechnung von SingleSourceShortestPath laeuft... Wie man aus der Tabelle erkennen kann, exportiert der Server das Objekt für den Single Source Shortest Path Algorithmus. Der Client lädt sich die Class-Datei des Objektes vom Class-Server und führt die Berechnung lokal aus. • All-Pairs Shortest Path (server-basiert) Auch hier beträgt die Bearbeitungszeit zuerst 161 ms, sinkt dann aber auf 20 ms ab. Server AllPairsShortestPath-Objekt wird direkt ausgefuehrt... Berechnung von AllPairsShortestPath laeuft... Class-Server Client Die Bearbeitung läuft analog zum Single Source Shortest Path Problem ab. Seite 31 Test der Client-Server-Anwendung • All-Pairs Shortest Path (client-basiert) Das Ergebnis der client-basierten Berechnung des All-Pairs Shortest Path Algorithmus ist in Abbildung 15 dargestellt. Die Bearbeitungszeit beträgt zuerst 180 ms, dann nur noch 10 ms. AllPairsShortestPath-Objekt exportieren... Server Class-Server Client reading: server.AllPairsShortestPath Berechnung von AllPairsShortestPath laeuft... Auch in diesem Fall entsprechen die Ausgaben auf den Konsolen den Erwartungen. Abbildung 15: Client-basiertes Ausführen des All-Pairs Shortest Path Der Test hat gezeigt, dass die GIS-Funktionalitäten korrekt ausgeführt werden. Allerdings fällt die Interpretation der Bearbeitungszeiten etwas schwer, da die internen Vorgänge des RMI dem Entwickler verborgen bleiben. Eine Möglichkeit ist jedoch, dass die Objekte beim ersten Ausführen über das Netz geladen und im Speicher der JVM des Clients abgelegt werden. Bei den darauf folgenden Aufrufen können die GIS-Funktionalitäten ohne lange Ladezeiten ausgeführt werden. Seite 32 Zusammenfassung/Ausblick 7 Zusammenfassung/Ausblick Ziel der vorliegenden Arbeit war es, die Bereitstellung von GIS-Funktionalitäten über eine Client-Server-Architektur zu ermöglichen. Einem aktuellen Ansatz in der GIS-Forschung folgend, sollten die GIS-Funktionalitäten getrennt von spezifischen Geodaten zur Verfügung gestellt werden. Außerdem sollte der Client in der Lage sein, die Funktionalitäten entweder direkt auf den Servern auszuführen oder zu sich auf den Rechner herunterzuladen und dort zu berechnen. Für die Umsetzung der Client-Server-Architektur in eine Anwendung sollte die JavaTechnologie RMI eingesetzt werden. Bevor jedoch mit der Implementierung der Anwendung begonnen werden konnte, mussten zunächst die Möglichkeiten dieser Technologie untersucht werden. Dabei erwies sich RMI als sehr geeignet für die Programmierung einer Client-Server-Architektur, da der Entwickler keine Funktionen für den Austausch von Objekten implementieren muss. Für den Test der Anwendung wurden zwei GIS-Funktionalitäten aus dem Bereich der Kürzesten Wege in Objekte umgesetzt, welche sowohl auf dem Server ausgeführt als auch auf den Client heruntergeladen werden können. Der Test verlief erfolgreich und bestätigte die Erwartungen an die Applikation. Die vorliegende Arbeit lässt für zukünftige Entwicklungen jedoch noch einigen Spielraum. Im folgenden sollen daher einige Vorschläge für die Weiterentwicklung gemacht werden. Bisher hat der Client zwei Möglichkeiten, eine GIS-Funktionalität auszuführen. Bei beiden Möglichkeiten stammen die Funktionalitäten vom Server. Nicht vorgesehen ist jedoch das Ausführen von GIS-Funktionalitäten, die nur der Client kennt. RMI könnte es nun Clients mit leistungsschwachen Rechnern ermöglichen, Objekte mit eigenen, rechenintensiven GISFunktionalitäten an einen Server zu übertragen und dort Funktionen dieses Objektes auszuführen. D.h. der Server stellt seine Rechenleistung dem Client zur Verfügung. Dieser Ansatz wird besonders bei verteilten Anwendungen verfolgt. Für die Anwendung des vorgestellten Verfahrens in einem größeren Rahmen, müsste ein Katalog erstellt werden, der für jede GIS-Funktionalität einen individuellen Schlüssel im String-Format bereithält. Außerdem sollten die Server in der Lage sein, neue GISFunktionalitäten ohne eine Änderung im Quellcode aufzunehmen. Bei steigender Zahl der verfügbaren Server ist es wenig sinnvoll, dass der Client zu allen Servern eine Verbindung herstellt, die Listen mit den verfügbaren GIS-Funktionalitäten herunterlädt und die gewünschte Funktionalität in diesen Listen sucht. Diese Suchfunktion könnte auch von einer Art Name-Server übernommen werden. Die einzelnen Server schicken eine Liste mit ihren Funktionalitäten an diesen Name-Server, der die Liste mit der Adresse des zugehörigen Servers abspeichert. Benötigt der Client eine GIS-Funktionalität, so stellt er eine Anfrage an den Name-Server. Dieser liefert die Adresse des Servers zurück, der die Funktionalität anbietet und am wenigsten ausgelastet ist. Seite 33 Literaturverzeichnis 8 Literaturverzeichnis [1] Tao, V., Yuan, S.: Development of network-based GIServices in Support of Online Geocomputing, IAPRS, Vol..XXXIII, Part B4, Amsterdam, 2000. [2] Sester, M.: Skript zur Vorlesung Geo-Informationssysteme III, Universität Stuttgart, August 2000. [3] SUN Microsystems: Java-Tutorial, http://java.sun.com/docs/books/tutorial. [4] Ladstätter, P.: Interoperabilität und OpenGIS, GIS, Ausgabe 1/00. [5] Prastacos, P.: Putting GIS on the Web, GIS, Ausgabe 1/00. [6] Averdung, C.: Integration raumbezogener Daten über Schnittstellen, GIS, Ausgabe 1/00. [7] Volz, S., Sester, M., Fritsch, D., Leonhardi, A.: Multi-Scale Data Sets in Distributed Environments, APRS, Vol..XXXIII, Technical IV/I, Amsterdam, 2000. [8] Graf, T.: RMI (Seminar Java-Technologien), FH München, FB 07 Informatik, http://www.informatik.fh-muenchen.de/~schieder/seminar-java-ss98/rmi, 1998. [9] Schulz, K.: Java professionell programmieren: eine Einführung in die erweiterten APIs der Java 2 Plattform, Heidelberg, 2000. [10] Object Managment Group (OMG): http://www.omg.org/. [11] Gong, L.: Java™Security Architecture (JDK1.2), SUN Microsystems, Oktober 1998. Seite 34 Anhang Literaturverzeichnis Anhang • Java-Quellcode zum Dijkstra-Algorithmus • Java-Quellcode zum Floyd-Algorithmus Seite 35 Anhang Literaturverzeichnis Java-Quellcode zum Dijkstra-Algorithmus In der Matrix inputMatrix sind die Kosten gespeichert. Unendlich wird mit -1 dargestellt. // Größe der Kosten-Matrix int size = inputMatrix.length; // Kopieren und Umwandeln von Werten mit -1 in Unendlich int[][] costMatrix = new int[size][size]; for(int i=0;i<size;i++){ for(int j=0;j<size;j++){ if(inputMatrix[i][j] == -1){ costMatrix[i][j] = INFINITE; } else{ costMatrix[i][j] = inputMatrix[i][j]; } } } // Array für Rekonstruktion des Pfades (alle Elemente auf 0 setzen) int[] p = new int[size]; // Initialisieren des Distanzenarrays mit erster Zeile von Kosten-Matrix int[] d = new int[size]; for(int i=0;i<size;i++) d[i] = costMatrix[0][i]; // Liste für besuchte Knoten int[] S = new int[1]; S[0] = 0; // Liste für noch zu besuchende Knoten int[] T = new int[size-1]; for (int i=1;i<size;i++) T[i-1] = i; // Hilfsvariablen für die Berechnung int min_d; int min_element; int index; int[] tmp1; int[] tmp2; // Hauptschleife (Iterationen) for(int i=1;i<size;i++){ // kleinstes Element und dessen Index in d bestimmen min_d = d[T[0]]; // min_d = minimale Kosten zum Quellknoten min_element = T[0]; // min_element = Knoten mit den geringsten Kosten for (int j=1;j<T.length;j++){ if (d[T[j]]<min_d){ min_d = d[T[j]]; min_element = T[j]; } } // Knoten mit geringsten Kosten aus T löschen und in S hinzufügen tmp1 = new int[T.length-1]; index = 0; for (int j=0;j<T.length;j++){ if (T[j]!=min_element){ tmp1[index] = T[j]; index++; Seite 36 Anhang Literaturverzeichnis } } tmp2 = new int[S.length+1]; for (int j=0;j<S.length;j++) tmp2[j] = S[j]; tmp2[S.length] = min_element; T = tmp1; S = tmp2; // Überprüfen, ob der Weg von der Quelle zu einem Knoten über den // ausgewählten Knoten kürzer ist als der direkte Weg // -> aktuellen Knoten in Pfad aufnehmen und Strecke in // Distanzenarray durch kleineren Wert ersetzen for(int j=0;j<T.length;j++){ if((d[min_element] + costMatrix[min_element][T[j]]) < d[T[j]]){ p[T[j]] = min_element; d[T[j]] = d[min_element] + costMatrix[min_element][T[j]]; } } } Java-Quellcode zum Floyd-Algorithmus In der Matrix inputMatrix sind die Kosten gespeichert. Unendlich wird mit -1 dargestellt. // Größe der Kosten-Matrix int size = inputMatrix.length; // Kopieren der Eingabematrix in neue Matrix und Umwandeln in Unendlich int [][] d = new int[size][size]; for(int i=0;i<size;i++){ for(int j=0;j<size;j++){ if(inputMatrix[i][j] == -1){ d[i][j] = INFINITE; } else{ d[i][j] = inputMatrix[i][j]; } } } // Floyd-Algorithmus for(int k=0;k<size;k++){ for(int i=0;i<size;i++){ for(int j=0;j<size;j++){ d[i][j] = Math.min(d[i][j],(d[i][k]+d[k][j])); } } } // Zurückwandeln von Unendlich auf -1 for(int i=0;i<size;i++){ for(int j=0;j<size;j++){ if(d[i][j] == INFINITE) d[i][j] = -1; } } Seite 37 Anlage Literaturverzeichnis Anlage • CD-ROM mit Quellcode Seite 38