9 Verteilte Anwendungen Jörg Deutschmann 9.1 Einführung Die Bedeutung verteilter, nach dem Client/Server-Prinzip gestalteter Rechneranwendungen wächst ständig. Die Kommunikation zwischen Rechnern in einem Netzwerk ist eine Voraussetzung für das Funktionieren der verteilten Anwendungen. Für die Entwicklung verteilter Anwendungen existieren zahlreiche Progammierwerkzeuge und -umgebungen, von denen einige ausgewählte Vertreter in diesem Kapitel vorgestellt werden. Dabei steht die Vermittlung der technologischen Grundlagen für die Programmierung der verteilten Anwendungen im Vordergrund. 9.1.1 Verteilte Anwendungen und verteilte Systeme Der Begriff der Verteilung im Umfeld von Computernetzwerken bezieht sich sowohl auf die Hardware als auch auf die Software. Definition 9.1 Ein verteiltes System ist ein Hard- und Software-System, dessen Daten und Funktionseinheiten auf mehrere zu einem Netz zusammengeschlossene Rechner verteilt sind. Für den Benutzer sollte die Verteilung möglichst nicht sichtbar sein. Die physikalische Vernetzung und die Einbindung in ein Kommunikationssystem bilden die Voraussetzungen für die Funktionsfähigkeit eines verteilten Systems. Moderne Betriebssysteme unterstützen die Integration in eine Netzwerkumgebung, wie zum Beispiel die Integration in das Internet, und bieten Zugang zu programmierbaren Netzwerkschnittstellen auf unterschiedlichem Niveau. Auf der Basis verteilter Systeme können Probleme in einzelne, unabhängig ausführbare Teilprobleme zerlegt und die einzelnen Teilaufgaben auf mehreren Rechnern, die geeignete Ressourcen zur Verfügung stellen, gelöst werden. Praktisch existieren in einem verteilten System mehrere auf einzelne Rechner verteilte Prozesse, die über das Netzwerk interagieren und parallel abgearbeitet werden. verteiltes System Definition 9.2 Eine verteilte Anwendung besteht aus mehreren nebenläufigen, untereinander kommunizierenden Prozessen, welche gemeinsam die vom Anwender geforderte Leistung erbringen. Die wesentlichen Ziele verteilter Anwendungen bestehen in der dezentralen Nutzung von Informationen und anderen Ressourcen. Der Programmierer verteilter Anwendungen steht damit vor der Aufgabe, sein Programmsystem für den Anwender möglichst trans- verteilte Anwendung 214 9 Verteilte Anwendungen parent zu dezentralisieren. Typische Beispiele verteilter Anwendungen sind Bank- und Buchungssysteme sowie Konferenzsysteme zur Anwenderkommunikation. 9.1.2 Client/ServerModell Client/Server-Modell Die grundlegende Architektur für die Programmierung verteilter Anwendungen wird durch das Client/Server-Modell beschrieben. Der Server bietet seinen Dienst, wie im Bild 9.1 dargestellt, über eine definierte Schnittstelle an. Ein Client zeichnet sich dadurch aus, dass er Anforderungen an den Server schickt und im Allgemeinen auf Antworten wartet. Client 1 Server Client 2 … Bild 9.1 Client/ServerModell: Strategie Anforderung (Request) Client n Antwort (Response) Client und Server kommunizieren asymmetrisch auf der Basis eines festgelegten Protokolls nach dem Request-Response-Prinzip. Der Client stellt asynchron, das heißt zu einem beliebigen Zeitpunkt, Anforderungen an den Server. Der Server antwortet synchron, also innerhalb eines bestimmten Zeitintervalls. Die Unterscheidung zwischen Client und Server bedeutet eine Verteilung von Rollen. Ein Server kann selbst wieder die Dienste eines anderen in Anspruch nehmen und tritt diesem gegenüber in der Rolle des Client auf. Entwicklungs- und Laufzeitunterstützungsumgebungen Der Anwendungsentwickler wird bei der Client/Server-Programmierung im Netzwerk auf unterschiedlichem Niveau unterstützt. Es gibt Application Program Interfaces (APIs), aufsetzend auf die Transportschicht. Weite Verbreitung hat das Socket API gefunden. Diese Schnittstelle zur Programmierung von Netzwerkanwendungen wurde ursprünglich an der kalifornischen Universität in Berkeley für das Betriebssystem UNIX entwickelt. Unter der Bezeichnung WinSock findet man auch eine Implementierung des Socket API für Microsoft Windows. Ein höheres Abstraktionsniveau erlaubt der Remote Procedure Call (RPC), bei dem sich der Programmierer um weniger Details des Rechnernetzes kümmern muss. Das Prinzip des RPC stand Pate bei der Realisierung von Entwicklungs- und Laufzeitunterstützungsumgebungen wie dem Distributed Computing Environment (DCE) und der Common Object Request Broker Architecture (CORBA). Auch Microsoft verweist im Zusammenhang mit dem Component Object Model (COM) auf die Spezifikation des DCE RPC. Der Entwickler verteilter Applikationen steht unter anderem vor der Aufgabe, sich in Abhängigkeit von den Anforderungen und Randbedingungen einer Anwendung für die technologische Vorgehensweise bei der Programmierung zu entscheiden. Dies setzt Kenntnisse über die APIs, über das Prinzip des RPC und über die Entwicklungs- und Laufzeitunterstützungsumgebungen voraus. 9.1.3 Programmierbeispiel Um einen Vergleich zu ermöglichen, bezieht sich das verwendete Programmierbeispiel einheitlich für alle vorgestellten Technologien auf eine einfache arithmetische Anwendung, bei der zwei Vektoren, bestehend aus fünf ganzen Zahlen, addiert werden. Der Client stellt die zwei Vektoren zur Verfügung und sendet diese über das Netzwerk an 9.2 Socket API 215 den Server. Der Diensterbringer hat die Aufgabe, die Addition durchzuführen und den Ergebnisvektor an den Client zurück zu geben. Der Dienstnutzer gibt das Ergebnis schließlich aus. 9.1.4 Übungen Aufgaben 9.1 9.2 9.2 Geben Sie je eine Definition für die Begriffe Client und Server an! Wodurch zeichnet sich ein Client aus und was sind charakteristische Merkmale eines Server? Aus der Sicht eines Applikationsprogrammierers lassen sich folgende allgemeine Anforderungen formulieren: Netzwerkunterstützung, Leistungsfähigkeit und Geschwindigkeit, hohe Verfügbarkeit und Parallelität, Robustheit. a) Welche der Anforderungen sind besonders wichtig für den Client, für den Server oder für Client und Server? b) Welche Unterstützung erwarten Sie daraufhin von dem Betriebssystem, dem API beziehungsweise von der Entwicklungs- und Laufzeitunterstützungsumgebung? Socket API Das Socket Application Program Interface (Socket API) stellt eine Sammlung von Funktionen für die Programmierung von Netzwerkanwendungen, aufsetzend auf den Dienst der Transportschicht, bereit. Sie wurden für die Programmiersprache C entwickelt und orientieren sich stark an der UNIX-Dateiarbeit. Obwohl in den folgenden Ausführungen vor allem die Entwicklung von Programmen für das Internet betrachtet wird, ist die Verwendung des Socket API nicht an die TCP/IP-Protokollfamilie gebunden. Socket Application Program Interface Definition 9.3 Ein Socket ist ein Kommunikationsendpunkt innerhalb eines Kommunikationsbereiches. Der Datenaustausch erfolgt zwischen den Sockets desselben Kommunikationsbereiches. Der Kommunikationsbereich spezifiziert die beteiligten Adressstrukturen und das verwendete Transportschichtprotokoll. Im Internet enthält die Adressstruktur die InternetAdresse des Rechners und die Portnummer des Prozesses sowie die Angabe, ob das Transmission Control Protocol (TCP) oder das User Datagram Protocol (UDP) verwendet wird. Das Konzept der abstrakten Zielpunkte in Form von Portnummern wird unter 8.3 erläutert. Allgemein lässt sich eine Kommunikationsbeziehung durch das folgende Fünfertupel eindeutig angeben: (Protokoll, lokale Adresse, lokaler Port, entfernte Adresse, entfernter Port) Die einzelnen Elemente dieses Fünfertupels finden sich in den Adressstrukturen und den Funktionen des Socket API wieder. Die durch den Übergang von der aktuellen Version des Internet-Protokolls (IPv4) zur neuen IP-Version 6 notwendigen Änderungen werden, soweit dies möglich ist, im Quelltext kursiv und fett hervorgehoben. Socket 216 9 Verteilte Anwendungen 9.2.1 Adressstruktur Adressstruktur für die Internet-Familie Der Anwendungsprogrammierer im Internet arbeitet mit folgender Adressstruktur, die in der Header-Datei <netinet/in.h> definiert ist: struct sockaddr_in { uint8_t sin_len; sa_family_t sin_family; in_port_t sin_port; struct in_addr sin_addr; char sin_zero[8]; }; struct sockaddr_in6 { /* IP-Version 6 */ uint8_t sin6_len; sa_family_t sin6_family; in_port_t sin6_port; uint32_t sin6_flowinfo; struct in6_addr sin6_addr; }; /* IP-Version 4 */ Nicht alle Umgebungen unterstützen das Strukturelement sin6_len, das die Größe der Struktur in Bytes enthält. Das Strukturelement sin6_family gibt die Adressfamilie an, wobei die symbolische Konstante AF_INET6 für die Adressfamilie Internet verfügbar ist. sin6_port enthält die Portnummer des Prozesses und die Struktur sin6_addr die Internet-Adresse des Rechners. Das IPv4-spezifische Element sin_zero ist ein nicht benötigter Strukturanteil, der auf null gesetzt wird. Die Angaben im IPv6-spezifischen Strukturelement sin6_flowinfo finden ihre Verwendung im Zusammenhang mit der Prioritäts- und Flusssteuerung (siehe 8.2.4). Das folgende Fragment eines C-Quelltextes demonstriert die Initialisierung einer Adressstruktur. Beispiel 9.1 struct sockaddr_in serv_addr; /* IP-Version 4 */ bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(8555); serv_addr.sin_addr.s_addr = inet_addr(“192.168.0.1”); struct sockaddr_in6 serv_addr; /* IP-Version 6 */ bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sin6_family = AF_INET6; serv_addr.sin6_port = htons(8555); inet_pton(AF_INET6, “0:0:0:0:0:0:192.168.0.1”, &serv_addr.sin6_addr); Zunächst wird die Struktur mit der Funktion bzero() in allen Strukturelementen auf null gesetzt (siehe auch Aufgabe 9.6) und sin6_family mit der symbolischen Konstanten AF_INET6 initialisiert. Die Funktion inet_addr() wandelt eine Zeichenkette im punktierten Dezimalformat in eine 32-Bit-Internet-Adresse um. Unter der neuen Version des Internet-Protokolls (IPv6) und auch in einigen Umgebungen der IP-Version 4 9.2 Socket API 217 steht für diese Aufgabe die Funktion inet_pton() (presentation to numeric) zur Verfügung. Die Byte-Ordnungsroutine htons() behandelt die Ordnungsunterschiede von Bytes zwischen unterschiedlichen Rechnerarchitekturen (siehe auch Aufgabe 9.5). 9.2.2 Grundlegende Socket-API-Funktionen Im Folgenden werden die für eine Programmierung von Netzwerkanwendungen wichtigsten Funktionen des Socket API kurz vorgestellt. Die Vorstellung erfolgt unter Angabe des Funktionsprototypen, einer Erläuterung und eines C-Quelltext-Fragments für die Demonstration der Verwendung. Alle API-Funktionen erfordern die Header-Datei socket.h. #include <sys/socket.h> socket()-Funktion int socket(int family, int type, int protocol); socket()Funktion Die socket()-Funktion erzeugt einen Kommunikationsendpunkt und gibt einen Socket-Deskriptor zurück, der in den weiteren Funktionen wie ein Datei-Deskriptor verwendet wird. Die Parameter von socket() bestimmen das zu verwendende Kommunikationsprotokoll. Einige erlaubte Kombinationen sind in der Tabelle 9.1 angegeben. Die socket()-Funktion spezifiziert lediglich das Element Protokoll im Fünfertupel der Kommunikationsbeziehung. family type protocol AF_INET6 SOCK_STREAM IPPROTO_TCP AF_INET6 SOCK_DGRAM IPPROTO_UDP AF_INET6 SOCK_RAW IPPROTO_ICMP Tabelle 9.1 Wichtige Protokolltypen für die Internet-Familie Der protocol-Parameter wird meistens auf null gesetzt, da type das Kommunikationsprotokoll außer bei SOCK_RAW eindeutig bestimmt. So können verbindungsorientierter Client und Server folgendermaßen einen Kommunikationsendpunkt kreieren: Beispiel 9.2 int sockfd; sockfd = socket(AF_INET6, SOCK_STREAM, 0) bind()-Funktion int bind( int sockfd, struct sockaddr *myaddr, int addrlen); Die bind()-Funktion meldet den durch sockfd spezifizierten, noch unbekannten Socket am lokalen System unter eventueller Angabe der Portnummer des lokalen Prozesses an. myaddr ist ein Zeiger auf eine protokollspezifische Adresse. Bei der Internet-Familie handelt es sich um die bereits vorgestellte Struktur sockaddr_in6, deren sin6_addr-Anteil durch die symbolische Konstante INADDR_ANY beziehungsweise durch in6addr_any auf null gesetzt wird. Die Zuweisung einer Portnummer erfolgt dynamisch durch das unterliegende System, wenn der sin6_port-Anteil ebenfalls auf null gesetzt ist. Andernfalls versucht das System, den Prozess mit der angegebenen bind()Funktion 218 9 Verteilte Anwendungen Portnummer zu verbinden. Bei Rückkehr der Funktion sind die mit null angegebenen Adressanteile der myaddr-Struktur aktualisiert. Die bind()-Funktion spezifiziert die Elemente lokale Adresse und lokaler Port im Fünfertupel der Kommunikationsbeziehung. Für die unter 9.2.1 bereits initialisierte Adressstruktur sieht die Anmeldung des Sockets am lokalen System folgendermaßen aus: Beispiel 9.3 bind( sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)); Bei struct sockaddr handelt es sich um eine generische Adressstruktur. Immer wenn eine Socket-Funktion einen Zeiger auf eine Socket-Adressstruktur erwartet, muss eine explizite Umwandlung (type cast) des Zeigers in den Typ der generischen Adressstruktur erfolgen. listen()Funktion listen()-Funktion int listen(int sockfd, int backlog); Die listen()-Funktion signalisiert die Bereitschaft eines verbindungsorientierten Server, Verbindungsaufbauwünsche auf dem mit sockfd spezifizierten Socket anzunehmen. listen() kommt für gewöhnlich nach socket() und bind() zum Einsatz. Das backlog-Argument gibt die Anzahl der möglichen Verbindungsanforderungen an, die in einer Warteschlange verwaltet werden, während das unterliegende System auf die Ausführung der accept()-Funktion wartet. Das folgende Beispiel lässt fünf Verbindungsaufbauwünsche innerhalb der Warteschlange zu. Beispiel 9.4 listen(sockfd, 5); accept()-Funktion accept()Funktion int accept( int sockfd, struct sockaddr *peer, int *addrlen); Die accept()-Funktion nimmt für den verbindungsorientierten Server die erste Client-Anforderung aus der Warteschlange und generiert einen neuen Socket der zunächst die gleichen Eigenschaften wie sockfd besitzt. peer und addrlen beziehen sich auf die Adresselemente des Partnerprozesses. Nach der Rückkehr der accept()Funktion enthält die peer-Struktur die Internet-Adresse und die Portnummer des Client-Prozesses. accept() spezifiziert damit auf Seiten des Server alle fünf Elemente der Kommunikationsbeziehung für den neuen Socket. Für den alten, durch sockfd gekennzeichneten Socket, bleiben entfernte Adresse und entfernter Port unspezifiziert, um den Deskriptor für weitere Verbindungsaufbauwünsche verwenden zu können. Diese Eigenschaft wird bei der Programmierung von Diensterbringern benutzt, die mehrere Verbindungen gleichzeitig bearbeiten. Wenn sich keine Anforderung in der Warteschlange befindet, blockiert die Funktion, bis ein Client eine Verbindung zum Server aufbauen will. Dies kann prinzipiell verhindert werden, indem der Programmierer den Socket als nicht blockierend kennzeichnet. Mit dem folgenden Quelltextfragment akzeptiert ein verbindungsorientierter Server eine Verbindungsanforderung auf dem Socket-Deskriptor, 9.2 Socket API 219 auf dem im Beispiel 9.4 bereits mit listen() die Bereitschaft für den Verbindungsaufbau signalisiert wurde. Beispiel 9.5 struct sockaddr_in6 int int clilen newsockfd cli_addr; newsockfd; clilen; = sizeof(cli_addr); = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); connect()-Funktion int connect( int sockfd, struct sockaddr *serverAddr, int addrlen) connect()Funktion Die connect()-Funktion verbindet den Socket eines Client mit dem Server. Sie richtet für den verbindungsorientierten Client eine Verbindung vom lokalen zum entfernten System ein. Der verbindungsorientierte Client muss nicht bind() vor connect() aufrufen, da connect() auch die lokale Adresse des Client automatisch zuweist und somit in diesem Fall die Elemente lokale Adresse, lokaler Port, entfernte Adresse, entfernter Port im Fünfertupel der Kommunikationsbeziehung spezifiziert. Der verbindungslose Client nutzt connect() zur lokalen Spezifikation des entfernten Server. Obwohl in diesem Fall keine Verbindung zum Server aufgebaut wird, liegt seine Adresse für den Datenaustausch ständig vor und braucht nicht explizit angegeben zu werden. Als Parameter benötigt die connect()-Funktion neben dem Socket-Deskriptor die Adressstruktur, in der Internet-Adresse und Portnummer vom Server festgelegt sind, sowie die Länge der Adressstruktur, so dass ein Client eine Verbindung mit Hilfe der im Beispiel 9.1 bereits initialisierten Adressstruktur folgendermaßen aufbauen könnte: Beispiel 9.6 connect( sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)); send()-, sendto()-, recv()-, recvfrom()-Funktion int send( int sockfd, char *buff, int nbytes, int flags); int sendto( int sockfd, char *buff, int nbytes, int flags, struct sockaddr *to, int addrlen); int recv (int sockfd, char *buff, int nbytes, int flags); int recvfrom( int sockfd, char *buff, int nbytes, int flags, struct sockaddr *from, int *addrlen); Die Funktionen send(), sendto(), recv() und recvfrom() dienen dem Datenaustausch. Sie sind den Funktionen read() und write() sehr ähnlich, benötigen aber zusätzliche Argumente. buff spezifiziert den Ein- beziehungsweise Ausgabepuffer. send()sendto()recv()recvfrom()Funktion 220 9 Verteilte Anwendungen nbytes gibt die Größe der auszugebenden beziehungsweise der einzubindenden Daten in Bytes an. flags ist für den normalen Datentransfer auf null gesetzt. send() und recv() stehen für die verbindungsorientierte Kommunikation zur Verfügung. Sie können aber auch bei verbindungsloser Kommunikation Verwendung finden, wenn der Partnerprozess mit connect() spezifiziert wurde. sendto() benötigt immer die explizite Angabe des Partners im to-Argument und die Größe der damit verbundenen Adressstruktur in addrlen. recvfrom() übergibt dem Kommunikationsprozess die Adresse des Partners im from-Argument sowie ebenfalls die Größe der damit verbundenen Adressstruktur in addrlen. Alle vier Funktionen geben die Länge der gesendeten oder empfangenen Daten zurück. Das folgende Quelltextfragment kann vom Arithmetik-Client verwendet werden, um zunächst zwei Vektoren an den Arithmetik-Server zu schicken und schließlich den Ergebnisvektor zu empfangen. Der Client bezieht sich dabei auf den im Beispiel 9.6 mit connect() bereits verbundenen Socket. Beispiel 9.7 #define ARRAY_SIZE 5 typedef int myArray[ARRAY_SIZE]; struct myIn { myArray inArray1; myArray inArray2; } struct myIn inArrays = {{0, 1, 2, 3, 4}, {9, 8, 7, 6, 5}}; myArray outArray; send(sockfd, (char *) &inArrays, sizeof(inArrays), 0); recv(sockfd, (char *) &outArray, sizeof(outArray), 0); close()Funktion close()-Funktion int close(int sockfd); Die close()-Funktion schließt den Socket und veranlasst die Freigabe der vom System zugewiesenen Ressourcen. Wenn nicht durch den Programmierer über eine Socket-Option explizit verhindert, so sollte das unterliegende System trotz aufgerufenen close() sichern, dass noch nicht gesendete Daten und Bestätigungen, die gepuffert sind, ausgegeben werden. Nachdem zum Beispiel der Arithmetik-Server das Ergebnis an den Client geschickt hat, schließt er den im Beispiel 9.5 durch accept() gelieferten Socket mit: Beispiel 9.8 close(newsockfd); 9.2.3 Client/Server-Entwicklung mit dem Socket-API In Abhängigkeit von der Rolle als Diensterbringer oder Dienstnutzer stellt das SocketAPI unterschiedliche Funktionen zur Verfügung. Das Bild 9.2 zeigt die Reihenfolge 9.2 Socket API 221 der Socket-Funktionen bei der Verwendung des verbindungsorientierten Transportschichtprotokolls TCP. Es wird auch die typische Struktur von Client und Server deutlich. Server Client socket() socket() bind() bind() listen() accept() connect() recv() send() send() recv() close() close() Während der Client seinen mit socket() kreierten Kommunikationsendpunkt schließt und anschließend im Allgemeinen terminiert, verweilt der Server in einer Schleife, die als Server-Loop bezeichnet wird. Innerhalb dieser Schleife nimmt er ständig neue Anforderungen entgegen. Im verbindungsorientierten Fall akzeptiert der Server mit accept() eine neue Verbindung, deren Socket nach erbrachter Dienstleistung wieder freigegeben wird. Die vollständige Implementierung des Diensterbringers der Arithmetik-Anwendung könnte dann folgendermaßen aussehen: Beispiel 9.9 #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #define SERV_PORT 8555 #define ARRAY_SIZE 5 typedef int myArray[ARRAY_SIZE]; struct myIn { myArray inArray1; myArray inArray2; }; main(int argc, char **argv) { int sockfd, newsockfd, clilen; struct sockaddr_in6 cli_addr, serv_addr; struct myIn myArray int inArrays; outArray; i; sockfd = socket(AF_INET6, SOCK_STREAM, 0); Bild 9.2 Struktur der Socket-Funktionen bei verbindungsorientiertem Protokoll Server-Loop 222 9 Verteilte Anwendungen bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sin6_family = AF_INET6; serv_addr.sin6_port = htons(SERV_PORT); serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); /*IPv4*/ serv_addr.sin6_addr = in6addr_any; /*IPv6*/ bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)); listen(sockfd, 5); for ( ; ; ) { clilen = sizeof(cli_addr); newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); recv(newsockfd, (char *) &inArrays, sizeof(inArrays), 0); for (i = 0; i < ARRAY_SIZE; i++) outArray[i] = inArrays.inArray1[i] + inArrays.inArray2[i]; send(newsockfd, (char *) &outArray, sizeof(outArray), 0); close(newsockfd); } } Die include-Anweisungen, die Festlegung der Konstanten und die Typdefinitionen sollten in einer separaten, benutzerdefinierten Header-Datei arithmetic.h erfolgen. Fügt man diese Header-Datei sowohl in das Client- als auch in das Server-Programm ein, so stellt man sicher, dass Client und Server mit den gleichen Voraussetzungen, wie zum Beispiel den gleichen benutzerdefinierten Datentypen entwickelt werden. Windows Socket Bei der Programmierung mit den Windows Sockets muss der Entwickler als Erstes die Funktion WSAStartup() aufrufen (IP-Version 4): Beispiel 9.10 WORD wVersionRequested=0x0101; WSADATA wsaData; WSAStartup(wVersionRequested, &wsaData); WSAStartup() spezifiziert die Version des Socket API (wVersionRequested) und eine Struktur für Details der Socket-Implementierung vom Typ WSADATA. Die Funktion closesocket() schließt einen Socket und WSACleanup() beendet die Verwendung 9.3 RPC 223 der Windows Sockets Dynamic Link Library (DLL). Zur Übersetzung einer 32-BitAnwendung mit Socket-API-Aufrufen ist das Bibliotheksmodul wsock32.lib für den Linker mit anzugeben. 9.2.4 Übungen Aufgaben 9.3 Implementieren Sie den Arithmetik-Client zu dem vorgestellten Server-Programm! 9.4 Die Funktionen socket(), bind(), connect(), listen(), und accept() geben im Fehlerfall einen Wert kleiner null zurück. Ergänzen Sie die ArithmetikApplikation um eine Fehlerbehandlung! 9.5 Wie kommen die unterschiedlichen Byte-Ordnungen Little Endian und Big Endian zustande, und wie wirken sie sich bei der Datenübertragung aus? Welche Möglichkeiten hat der Programmierer, darauf zu reagieren? 9.6 In einigen Umgebungen sind die Byte-Operationen bcopy(), bzero() und bcmp() nicht verfügbar. Schreiben Sie deshalb drei einfache Macros, welche die Byte-Operationen auf die ANSI C-Funktionen memcpy(), memset() und memcmp() abbilden! 9.7 Welchen Zweck erfüllen die Funktionen inet_addr() und inet_ntoa() bei der Adressumwandlung? 9.8 Halten Sie die Reihenfolge der Socket-Funktionen bei der Verwendung des verbindungslosen Transportschichtprotokolls User Datagram Protocol (UDP) in einem Struktogramm oder in einem Programmablaufplan fest! 9.9 Entwickeln Sie die Arithmetik-Anwendung auf der Basis von UDP! 9.10 Welche Vorteile bringt die Verwendung der connect()-Funktion für den verbindungslosen Client? 9.11 In welchen Anwendungsfällen würden Sie sich für die Verwendung eines verbindungsorientierten Transportschichtprotokolls entscheiden? Wann bietet sich die Verwendung eines verbindungslosen Transportschichtprotokolls an? 9.12 Informieren Sie sich über das Prozess- oder Thread-Konzept Ihres Betriebssystems! Implementieren Sie den Arithmetik-Server so, dass er parallel mehrere Anforderungen bearbeiten kann! 9.3 RPC Das Wesen des Remote Procedure Call (RPC) besteht darin, dass der Client eine Prozedur aufruft, die der Server auf einem entfernten System bereitstellt. Dabei sollte der entfernte Prozeduraufruf für den Client möglichst wie ein lokaler Aufruf erscheinen. Das Hauptziel des RPC ist deshalb die transparente Kommunikation zwischen Client und Server. Die Realisierung verteilter Anwendungen wird dadurch vereinfacht, dass sich der Programmierer nicht mehr um die Details der unterliegenden Netzwerkprogrammierung, wie zum Beispiel bei dem Socket API, kümmern muss. Für den RPC existieren verschiedene Umsetzungen, wobei sich neben den proprietären Implementierungen, wie beispielsweise dem Sun RPC, vor allem der RPC des Distributed Computing Environment (DCE RPC) /9.2/ als plattformübergreifende Referenz durchgesetzt hat. Remote Procedure Call 224 9 Verteilte Anwendungen 9.3.1 Allgemeines Konzept des RPC Die Beschreibungen des RPC setzen sich im Wesentlichen aus den zwei Teilen RPCModell und Datenrepräsentation zusammen. Das RPC-Modell gibt Auskunft über die Architektur und das implementierte Protokoll des RPC. Das Problem der Datenrepräsentation bezieht sich auf die Unterschiede bei der Interpretation von Daten in verschiedenen Hardware- und Sofware-Umgebungen. Obwohl sich die RPC-Umsetzungen in einzelnen Punkten unterscheiden, so ist das im Bild 9.3 dargestellte Konzept des entfernten Prozeduraufrufes überall gleich. ClientRoutinen (1) ServerRoutinen (10) (6) ClientStub (2) Bild 9.3 Modell des RPC (5) ServerStub (9) RPCLaufzeitsystem (7) (8) (4) RPCLaufzeitsystem (3) Ein entfernter Prozeduraufruf läuft im Allgemeinen in zehn Schritten ab: RPC-Laufzeitprozess marshal unmarshal Client-Stub 1. Der Client ruft eine lokale Prozedur, den so genannten Client-Stub auf. Dieser Stub erfüllt eine Stellvertreterfunktion, indem er sich dem Client gegenüber wie die eigentliche Server-Prozedur verhält. Der Client-Stub verpackt die Parameter des Prozeduraufrufes in eine Nachricht gemäß dem RPC-Protokoll. Dieser Vorgang wird als marshal bezeichnet. 2. Der Client-Stub sendet die RPC-Nachricht an das entfernte System. Hierzu ist ein Aufruf des RPC-Laufzeitsystems erforderlich. 3. Das RPC-Laufzeitsystem nutzt ein verbindungsloses oder ein verbindungsorientiertes Transportschichtprotokoll, um die Nachricht an das entfernte System zu übertragen. 4. Auf dem entfernten System wartet der Server-Stub auf ankommende Client-Anforderungen. Der Server-Stub hat die Aufgabe, die Parameter des Prozeduraufrufes gemäß dem RPC-Protokoll zu entpacken. Diesen Vorgang bezeichnet man als unmarshal. 5. Der Server-Stub führt einen lokalen Prozeduraufruf durch und übermittelt die Anforderung und ihre Parameter an die tatsächliche Server-Prozedur. 6. Die Server-Prozedur übergibt nach Bearbeitung der Anforderung die Rückgabewerte an den Server-Stub, der die zurückgegebenen Parameter in eine Nachricht gemäß dem RPC-Protokoll verpackt. 7. Der Server-Stub ruft das RPC-Laufzeitsystem auf und sendet die Ergebnisse der Anforderung zurück an den Client. 8. Aufsetzend auf ein Transportschichtprotokoll, überträgt das RPC-Laufzeitsystem die Nachricht an den Client. 9. Das RPC-Laufzeitsystem übergibt die RPC-Nachricht an den Client-Stub. 10. Der Client-Stub entpackt die Ergebnisparameter gemäß dem RPC-Protokoll und kehrt in seiner Stellvertreterfunktion als lokale Prozedur zur Client-Routine zurück. 9.3 RPC 225 Client- und Server-Stubs vereinfachen die Implementierung von verteilten Anwendungen, indem sie die Details der Netzwerkprogrammierung und der Datenrepräsentation vor dem Applikationsentwickler verbergen. Server-Stub Ein weiterer sehr wichtiger Aspekt des RPC ist die Lokalisierung der entfernten Prozedur innerhalb des Netzwerkes. Das Auffinden der Server-Prozedur fällt unter den Begriff des Binding. Definition 9.4 Als Binding bezeichnet man den Vorgang der Ermittlung der vollständigen Adresse des Kommunikationspartners. Binding Der Client ermittelt die Adresse des gewünschten Server-Prozesses selbst, beziehungsweise lässt diese ermitteln, wobei zwei Informationen notwendig sind. Zum einen handelt es sich um die Adresse des Rechners, auf dem der Server residiert. Zum anderen geht es um die Identifikation des Server-Prozesses auf dem Rechner selbst. Hier unterscheiden sich die Vorgehensweisen bei den einzelnen RPC-Umsetzungen. Die flexibelste Variante geht davon aus, dass jeder Server einen im Netzwerk eindeutigen Namen besitzt. Die Adressen der Server-Rechner werden in einer für das Netzwerk zentralen Datenbasis verwaltet und die Identifikation der Prozesse erfolgt durch lokale Instanzen auf den Rechnern. Der Client erhält dann die gültige Adresse des Server-Prozesses in zwei Schritten. Der Server bietet seine Dienste über eindeutig zu definierende Schnittstellen an. Client und Server können bei Einhaltung der definierten Schnittstellen getrennt voneinander entwickelt werden. 9.3.2 Client/Server-Entwicklung auf der Basis von RPC Das RPC-Laufzeitsystem stellt die eventuell notwendigen Umwandlungen und das Generieren der benötigten Dateneinheiten für den Austausch der Parameter zwischen Client und Server sicher. Weiterhin schirmt es den Programmierer von den Besonderheiten des Transferdienstes, wie zum Beispiel die Behandlung von Timeouts, ab. Für die Applikationsentwicklung mit RPC stehen Entwicklungswerkzeuge zur Verfügung, die sich in Abhängigkeit von der jeweiligen RPC-Umsetzung voneinander unterscheiden. Die im Bild 9.4 dargestellte allgemeine Vorgehensweise bei der Applikationsentwicklung mit RPC ist aber unabhängig von der RPC-Umsetzung weitgehend einheitlich. Die beispielhafte Implementierung der einfachen Arithmetik-Anwendung erfolgt mit Hilfe des Sun RPC, der nicht nur für Solaris, sondern auch für andere Plattformen, wie zum Beispiel BSD verfügbar ist. RPC-Entwicklungsprozess Zunächst hat der Programmierer eine RPC-Spezifikationsdatei zu liefern, in der die entfernten Prozeduren und eventuell verwendete benutzerdefinierte Datentypen definiert werden. Sun RPC greift hier auf eXternal Data Representation (XDR) zurück. XDR stellt eine Methode dar, Daten so zu codieren, dass sie zwischen unterschiedlichen Hardund Software-Plattformen ausgetauscht werden können. Folgende Spezifikationsdatei, arithmetic.x, beschreibt die Schnittstelle für die Implementierung der ArithmetikApplikation mittels Sun RPC: RPC-Spezifikationsdatei 226 9 Verteilte Anwendungen RPC-Spezifikationsdatei RPC-Generator/Compiler Client-Stub Server-Stub Header ClientRoutinen Client-Stub Header Header Bild 9.4 Applikationsentwicklung mit RPC ClientObjektdateien Server-Stub Compiler Compiler Client-Stub (Objekt) ServerRoutinen RPCBibliothek RPCBibliothek ServerObjektdateien Linker Linker Client Server Server-Stub (Objekt) Beispiel 9.11 const ARRAY_SIZE = 5; typedef long myArray[ARRAY_SIZE]; struct myIn { myArray inArray1; myArray inArray2; }; struct myOut { myArray outArray; }; program ARITHMETICPROG { version ARITHMETICVERS { myOut SUM_ARRAYS(myIn) = 1; } = 1; } = 0x20000076; In der Datei arithmetic.x werden die entfernte Prozedur SUM_ARRAYS() und deren Argumente und Rückgabewerte deklariert. Außerdem bekommt jede Prozedur eine Prozedurnummer und jedes Programm eine Programmnummer zugewiesen. Prozedurnummern beginnen bei 0, wobei die 0 für die so genannte Null-Prozedur bereits vergeben ist. Die Programmnummer kann der Applikationsprogrammierer aus dem Bereich zwischen 0x20000000 und 0x3FFFFFFF auswählen. Beim Sun RPC übernehmen 9.3 RPC 227 entfernte Prozeduren nur ein Argument. Deshalb legt man für die Übergabe mehrerer Argumente eine Struktur an. Auch für die Rückgabe eines benutzerdefinierten Datentyps sollte eine Struktur Verwendung finden, mit deren Hilfe man eine Fehlerbehandlung organisieren könnte. Die RPC-Spezifikationsdatei, welche die Schnittstelle zur entfernten Prozedur beschreibt, wird einem RPC-Generator oder -Compiler übergeben. Dieser generiert in Abhängigkeit von der jeweiligen Zielsprache Quelldateien mit unterschiedlichen Aufgaben. Header-Informationen stellen auf Client- und Server-Seite sicher, dass Client und Server mit den gleichen benutzerdefinierten Daten und Funktionen entwickelt werden. Neben Client- und Server-Stub, die den Programmierer von den Details der RPC-Programmierung abschirmen, lassen einige Generatoren oder Compiler auch die automatische Erstellung von Vorschlägen für die Quelldateien der eigentlichen Funktionalität der Anwendung zu. Beim Sun RPC erzeugt das Werkzeug rpcgen außerdem die benötigten XDR-Filter für den Umgang mit den Daten der eXternal Data Representation. Mit dem folgenden Aufruf von rpcgen wird dem Generator die bereits angegebene RPC-Spezifikationsdatei für die Arithmetik-Anwendung übergeben: RPCGenerator RPC-Compiler % rpcgen arithmetic.x rpcgen generiert daraufhin die Quelldateien für die Programmiersprache C. Zu den Quellen gehören eine Header-Datei, arithmetic.h, die Stub-Module arithmetic_clnt.c für den Client-Stub und arithmetic_svc.c für den Server-Stub sowie ein Quellmodul für XDR-Filter, arithmetic_xdr.c. An diesen Dateien sollte der Programmierer nichts ändern. Unter Angabe der Option -a erzeugt rpcgen neben einem Makefile, makefile.arithmetic, auch Vorschläge für die eigentliche Arithmetik-Anwendung in den Modulen arithmetic_client.c und arithmetic_server.c. Diese Dateien kann der Programmierer für seine Applikationsentwicklung benutzen. Nachdem der Programmierer die Server-Prozeduren und den Client implementiert hat, erfolgt das Übersetzen und Linken der Programme. Der Programmierer kann hierbei auf das durch rpcgen generierte Makefile zurückgreifen. Zur Verdeutlichung der Vorgehensweise werden im Folgenden die benötigten Module und Programme einzeln übersetzt: Übersetzen und Linken % cc -c arithmetic_xdr.c % cc arithmetic_server.c arithmetic_svc.c arithmetic_xdr.o -o arithmeticServer -lnsl % cc arithmetic_client.c arithmetic_clnt.c arithmetic_xdr.o -o arithmeticClient -lnsl Der erste Aufruf des C-Compiler erzeugt die Objektdatei für die XDR-Filter. Nachdem Client und Server zusammen mit ihren Stubs übersetzt wurden, ist die Objektdatei für die XDR-Filter sowohl an den Client als auch an den Server zu linken. Die mit -lnsl gelinkte Bibliothek libnsl enthält Funktionen für den RPC und für den Zugriff auf verschiedene Netzwerkdienste. Die Server-Installation erfolgt durch seinen Aufruf. Der Server-Stub enthält die Funktionen für die Registrierung der entfernten Prozedur innerhalb der durch die jeweilige RPC-Umsetzung unterstützten Datenbasen. Beim Sun RPC übernimmt ein so genannter Portmapper die Verwaltung aller auf einem Rechner verfügbaren RPC-Programme. Der Client muss also wissen, auf welchem Rechner die benötigte entfernte Prozedur ServerInstallation 228 9 Verteilte Anwendungen installiert ist. Wenn der Arithmetik-Server zum Beispiel auf einem Rechner im lokalen Netz zur Verfügung steht, so können mit Hilfe des Programmes rpcinfo und einem Broadcast an alle Portmapper Rechneradresse und Portnummer des Diensterbringers ermittelt werden: % rpcinfo -b 536871030 1 192.168.0.1.172.178 einstein Neben der Broadcast-Option übergibt man rpcinfo die Programm- und Versionsnummer des gewünschten Server-Programmes. In diesem Beispiel ermittelt rpcinfo den Rechner mit dem Namen einstein und der Internet-Adresse 192.168.0.1, auf dem der Arithmetik-Server unter der Portnummer 44210 residiert. 9.3.3 Übungen Aufgaben 9.13 Welche Vorteile bringt die Implementierung verteilter Anwendungen auf der Basis von RPC? 9.14 Der Aufruf rpcgen -a arithmetic.x erzeugt unter anderem folgendes Modul, arithmetic_server.c: #include “arithmetic.h” myOut * sum_arrays_1(argp, rqstp) myIn *argp; struct svc_req *rqstp; { static myOut result; /* * insert server code here */ return (&result); } a) Welche Aufgabe erfüllt dieses Modul? b) Stellen Sie den Quell-Code entsprechend der Aufgabe des Moduls fertig! Der zweite Parameter der Prozedur, rqstp, ist spezifisch für Sun RPC und muss bei der Implementierung nicht berücksichtigt werden. c) Warum sollten die Ergebnisse, die eine Server-Prozedur beim Sun RPC zurückliefert, in einer statischen oder globalen Variable stehen? 9.15 Für den Client schlägt rpcgen folgende Funktion vor: #include “arithmetic.h” void arithmeticprog_1(host) char *host; { CLIENT *clnt; myOut *result_1; myIn sum_arrays_1_arg; 9.4 DCE 229 clnt = clnt_create(host, ARITHMETICPROG, ARITHMETICVERS, „netpath”); if (clnt == (CLIENT *) NULL) { clnt_pcreateerror(host); exit(1); } result_1 = sum_arrays_1(&sum_arrays_1_arg, clnt); if (result_1 == (myOut *) NULL) { clnt_perror(clnt, „call failed”); } clnt_destroy(clnt); } Die Funktion clnt_create() erzeugt einen RPC-Handle für das angegebene Programm und dessen Version. Weitere Parameter sind der Rechnername und das zu verwendende Transportschichtprotokoll. Die Angabe von “netpath” überlässt die Auswahl des Transportschichtprotokolls dem RPC-Laufzeitsystem. Der mit clnt_create() erzeugte RPC-Handle ermöglicht es dem Programmierer, eine für das angegebene Programm und dessen Version definierte Prozedur aufzurufen. Implementieren Sie den Arithmetik-Client und dessen main()-Funktion unter Verwendung der durch rpcgen erzeugten Funktion arithmeticprog_1()! 9.4 DCE Zu dem herstellerübergreifenden Konsortium The Open Group, vorher Open Software Foundation (OSF), gehörten im September 1998 196 Mitglieder. Die Gruppe befasst sich schwerpunktmäßig mit der Entwicklung sicherer und leistungsfähiger Informations- und Kommunikationsstrukturen. The Open Group bietet Lösungen im Bereich verteilter Systeme auf der Grundlage von Standards, Industriestandards und in der Praxis bewährter Vorgehensweisen an. Die Entwicklungs- und Laufzeitunterstützungsumgebung Distributed Computing Environment (DCE) ist ein wichtiger Bestandteil im Programm des Konsortiums. DCE bietet eine Sammlung von Diensten und Werkzeugen für die Entwicklung, Administration und Nutzung verteilter Anwendungen in heterogener Rechnerumgebung. Im Fokus der aktuellen Version DCE 1.2.2 stehen erweiterte Sicherheitsmechanismen, die Unterstützung des World Wide Web durch DCE-Dienste sowie die Technologie der plattformunabhängigen Verteilung von Software-Objekten. Aus marktpolitischen Gründen hat sich DCE in der Praxis nur wenig durchgesetzt. Dennoch beeinflusst DCE als grundlegende Architektur die Entwicklung verteilter Systeme und verteilter Anwendungen maßgeblich. Die Bedeutung des DCE liegt also zum einen darin begründet, dass die Entwicklung, Verwaltung und Nutzung verteilter Anwendungen in einer komplett installierten Umgebung mit DCE-Diensten erfolgen kann. Zum anderen werden die Bestandteile des DCE als eine Art Referenz für verteilte Umgebungen angesehen. 9.4.1 Architektur und Organisation des DCE Aus architekturbasierter Sicht bildet DCE, wie im Bild 9.5 dargestellt, eine Middleware-Schicht zwischen dem Betriebssystem und den Anwendungen. Die DCE-Tech- The Open Group Distributed Computing Environment 230 9 Verteilte Anwendungen nologiekomponenten lassen sich in die Kategorien verteilte Dienste und verteilte Programmierung einteilen. Anwendungen Security Service Computer Integration Zukünftige Dienste Management Distributed File Service Time Service Directory Service Zukünfige Dienste Remote Procedure Call Threads Bild 9.5 DCE-Architektur Betriebssystem und Transferdienste Technologiekomponenten Alle verteilten Dienste von DCE bauen auf die Unterstützung der verteilten Programmierung durch DCE Threads und DCE Remote Procedure Call auf. Diese beiden Technologiekomponenten werden über Bibliotheken, Application Program Interfaces (APIs) und zugehörige Entwicklungswerkzeuge, wie zum Beispiel ein DCE-spezifisches make-Werkzeug, realisiert. DCE Thread Die DCE Threads unterstützen das Kreieren, die Verwaltung und die Synchronisierung von Leichtgewichtsprozessen, die als Threads bezeichnet werden. Threads sind Aktivitäten (points of execution) innerhalb eines Prozesses. Der Programmierer erzeugt bei Bedarf mehrere nebenläufige Threads, die mit einem eigenständigen Scheduling-Mechanismus ablaufen. Wenn das verwendete Betriebssystem das Konzept der Leichtgewichtsprozesse bereits unterstützt, kann man auf diese Technologiekomponente verzichten. DCE RPC Der DCE RPC bildet die Grundlage für die Client/Server-Programmierung innerhalb der verteilten Umgebung und seine Spezifikation steht als Referenz für weitere RPC-Umsetzungen. DCE RPC setzt sich aus Entwicklungswerkzeugen sowie einem Laufzeitsystem zusammen. Die Realisierung verteilter Anwendungen mit DCE RPC entspricht der im Bild 9.4 dargestellten Vorgehensweise. DCE Distributed Time Service Der DCE Distributed Time Service sorgt für eine einheitliche Zeit in den Netzwerkknoten des verteilten Systems. Die Client-Seite, der so genannte Time Clerk, veranlasst die Synchronisierung der Systemuhr, indem er periodisch Anforderungen an einen Time Server schickt. Die Anzahl der Time Server je DCE-Zelle ist konfigurierbar, wobei sich eine Hierarchie von Local, Global, Courier und Backup Courier Time Server ergibt. Der Programmierer erhält über das Distributed Time Service API direkten Zugriff auf den Time Service. Ein Time Provider Interface unterstützt das Einlesen der Zeit von einer externen Quelle, bei der es sich entweder um eine spezielle Hardware, wie zum Beispiel eine Atomuhr, oder um einen Administrator handelt. Das Zeitformat des Distributed Time Service entspricht der standardisierten Coordinated Universal Time (UTC), die auf den Beginn des gregorianischen Kalenders (15. 10. 1582) zurückgeführt wird. DCE Directory Service Der DCE Directory Service verwaltet Informationen über Ressourcen der verteilten Umgebung, wie zum Beispiel Nutzer, Rechner und RPC-Programme. Die Informationen zu den Ressourcen bestehen aus eindeutigen Namen, die in einem hierarchischen Namensraum organisiert sind, und zugehörigen Attributen, die Ressourcen näher be- 9.4 DCE 231 schreiben. Drei Dienstinstanzen mit unterschiedlichem Zuständigkeitsbereich erbringen den DCE Directory Service, den der Programmierer über das X/Open Directory Service (XDS) API erreicht. Während der DCE Cell Directory Service für den Namensraum innerhalb einer DCE-Zelle zuständig ist, verwaltet der DCE Global Directory Service Ressourcen, die nicht auf eine Zelle beschränkt sind. Ein Global Directory Agent agiert als Vermittlungsinstanz zwischen einer DCE-Zelle und dem Global Directory Service. Auch das aus dem Internet stammende Domain Name System (DNS) kann über den Global Directory Agent angesprochen werden. Der Aufbau der Datenbasis des Global Directory Service orientiert sich aufgrund seiner weitreichenden Verfügbarkeit an dem internationalen Standard CCITT X.500 beziehungsweise ISO 9594. Alle beteiligten Datenbasen sollten aufgrund hoher Anforderungen an die Verfügbarkeit als Replikate vorliegen. Der DCE Security Service behandelt die drei Aspekte Authentifizierung, Autorisierung und sichere Kommunikation in einer verteilten Umgebung. Ein Nutzer meldet sich in der verteilten Umgebung über die so genannte Login Facility beim Authentication Service an und erhält ein Sicherheitszeugnis, mit dem er sich vom Privilege Service für die Nutzung von Ressourcen autorisieren lässt. Jeder Ressource ist eine Access Control List zugeordnet, in der eine Liste von Nutzern verwaltet wird, die auf diese Ressource zugreifen dürfen. Der Registry Service ermöglicht dem Administrator die Verwaltung von Nutzern und Diensten innerhalb der verteilten Umgebung. Eine sichere Kommunikation erfolgt über Authenticated RPC, der Integration des DCE RPC in den Security Service mit der Möglichkeit, die Daten zu verschlüsseln. DCE Security Service Der DCE Distributed File Service benutzt andere DCE Services und gestattet den Zugriff auf Dateien innerhalb der verteilten Umgebung. Außerdem unterstützt er weitere Funktionalitäten, wie zum Beispiel die Replikation von Daten und die Wiederherstellung von Dateien nach Fehlerzuständen. DCE Distributed File Service Der DCE Computer Integration Service steht für die Möglichkeit der Integration unterschiedlicher Rechner in die verteilte Umgebung. Ein Beispiel ist der Diskless Support Service, der Rechnersysteme ohne permanentem Speichermedium beim Systemstart mit Konfigurationsinformationen versorgt. DCE Computer Integration Service Der DCE Management Service repräsentiert keine eigenständige Technologiekomponente innerhalb der verteilten Umgebung. Jeder DCE Service beinhaltet die für ihn spezifische Management-Funktionalität. DCE Management Service Eine Gruppe von Rechnern, die in einer Verwaltungseinheit, bestehend aus DCE-Nutzer-Maschinen, DCE-Administrator-Maschinen und DCE-Server-Maschinen, organisiert sind, wird als DCE-Zelle bezeichnet. DCE-Zellen müssen mindestens die DCE-Dienste Cell Directory Service, Security Service und Distributed Time Service enthalten. Das Distributed Computing Environment besteht entweder aus einer DCE-Zelle oder aus mehreren DCE-Zellen, die miteinander kommunizieren können. Eine Zelle geht als Bestandteil in das Environment ein, wenn sie Zugang zu mindestens einem Global Directory Service der verteilten Umgebung erhält. Verteilte Anwendungen können auf die DCE-Dienste zurückgreifen. Dabei erfolgt die Umsetzung der Anwendungen durch den Programmierer mit Hilfe der Werkzeuge und der Laufzeitumgebung des DCE RPC. DCE-Zelle 232 9 Verteilte Anwendungen 9.4.2 Client/Server-Entwicklung mit DCE RPC Obwohl die Client/Server-Entwicklung mit dem DCE RPC im Wesentlichen der im Kapitel 9.3 dargestellten Vorgehensweise entspricht, soll an dieser Stelle noch auf einige Besonderheiten der DCE-RPC-Programmierung eingegangen werden. Universal Unique Identifier Für jede RPC-Schnittstelle generiert das Werkzeug uuidgen einen Universal Unique Identifier (UUID), der ein Interface oder auch andere Ressourcen eindeutig spezifiziert. uuidgen erstellt optional ein Template für die RPC-Spezifikationsdatei, in der die Schnittstelle beschrieben wird. Die Beschreibung erfolgt in der DCE-eigenen Interface Definition Language (IDL), mit deren Hilfe der Programmierer Eingabe- und Ausgabeparameter der beteiligten Prozeduren festlegt. Der folgende Aufruf von uuidgen erzeugt ein Template für die Schnittstellenbeschreibung der Arithmetik-Anwendung: % uuidgen -i > arithmetic.idl Die Datei arithmetic.idl könnte dann folgendermaßen aussehen: Beispiel 9.12 [ uuid(2c5d9c45-f5d2-00b9-7cc0-7158f0ad9abc), version(1.0) ] interface INTERFACENAME { } Nachdem der Programmierer den Interface-Namen eingesetzt und die Schnittstelle in DCE IDL beschrieben hat, übergibt er die IDL-Datei dem IDL-Compiler und entwickelt weiter nach dem im Bild 9.4 dargestellten Entwicklungsweg. Ein weiteres Merkmal des DCE RPC besteht darin, dass die DCE Services für den Programmierer über RPC-spezifische Schnittstellen erreichbar sind. So ist ein RPCClient zum Beispiel in der Lage, den Host auf dem der gewünschte Server residiert, über den Cell Directory Service zu ermitteln. Das Binding findet dann, wie im Bild 9.6 dargestellt, in zwei Schritten statt. „Arithmetic Server?“ Client „Host X“ „Arithmetic Server?“ „Port y“ RPC Daemon Bild 9.6 Binding beim DCE RPC Cell Directory Server Arithmetic Server Host X Der Arithmetik-Server muss sich bei seinem Start sowohl lokal beim RPC Daemon, als auch innerhalb der Zelle beim Cell Directory Server anmelden. Jede DCE-Maschine, 9.5 CORBA/OMA 233 auf der RPC-Server laufen, verfügt über einen RPC Daemon, der als Prozess unter einer fest definierten Portnummer erreichbar ist. Seine Aufgabe besteht darin, die Portnummern der RPC-Server auf der jeweiligen Maschine zu verwalten. Der Arithmetik-Client ermittelt über den Cell Directory Service zunächst den Host, auf dem der ArithmetikServer residiert. Anschließend erhält er vom RPC Daemon des betreffenden Host die Portnummer, an die er seine Anforderung absetzen kann. 9.4.3 Übungen Aufgaben 9.16 Stellen Sie lokale und verteilte Umgebungen hinsichtlich folgender Aspekte der Programmierung gegenüber: Adressraum, Datenaustausch und Interprozesskommunikation, Persistenz von Daten, Parallelität bei der Abarbeitung, Behandlung von Ereignissen und Prozesssteuerung, Verwaltung und Abspeicherung von Kontextinformationen, Sicherheit beim Zugriff auf die Daten! 9.17 Welche Anforderungen sind an eine verteilte Programmier- und Laufzeitumgebung zu stellen und wie wird das Distributed Computing Environment der Open Group diesen Anforderungen gerecht? 9.18 Nennen Sie Vor- und Nachteile für das beschriebene Verfahren zur Ermittlung der vollständigen Server-Adresse (Binding) in zwei Schritten! Referat 9.1 9.5 Analysieren Sie die Architektur des Distributed Computing Environment! Gehen Sie dabei auf die Aufgaben der einzelnen Technologiekomponenten ein und erläutern Sie an Beispielen, wie die Technologiekomponenten zusammenwirken! CORBA/OMA Die 1989 gegründete Object Management Group (OMG) zählte als herstellerübergreifendes Konsortium im Februar 1998 bereits über 800 Mitglieder /9.3/. Das wichtigste Ziel der OMG besteht darin, den Software-Entwicklungsprozess zu effektivieren. Die Software-Systeme sollen einen modularen Entwicklungsprozess sowie die Portabilität, Wiederverwendbarkeit und Interoperabilität von Sofware unterstützen. Um die wachsende Komplexität von Software beherrschbar zu gestalten, entwickelte die OMG das Objekt-Management-Paradigma. Dieses Paradigma beinhaltet die Abbildung der realen Welt auf Software-Komponenten, die als Objekte bezeichnet werden und ihre Daten und Funktionalitäten über definierte Schnittstellen zur Verfügung stellen. Portabilität, Wiederverwendbarkeit und Interoperabilität der Software-Komponenten beruhen auf dem Objektmodell der Objekt-Management-Architektur. Das Objektmodell liefert eine einheitliche Sicht auf Objektkonzepte und die damit verbundene Begriffswelt. Die Objekt-Management-Architektur beschreibt, wie die Komponenten innerhalb des Software-Systems agieren und interagieren, um die Leistung der verteilten Anwendungen zu erbringen. Object Management Architecture (OMA) und Common Object Request Broker Architecture (CORBA) definieren aufgrund ihrer breiten Akzeptanz einen Standard, der Interoperabilität zwischen Software-Komponenten auf Rechnern mit unterschiedlichen Betriebssystemen und über heterogene Netzwerke ermöglicht. Object Management Group Object Management Architecture Common Object Request Broker Architecture 234 9 Verteilte Anwendungen 9.5.1 Objektmodell der Object Management Group Aus der Sicht eines Programmierers, der seine verteilten Anwendungen in eine CORBA-Umgebung integrieren möchte, ist das Verständnis des mit dieser Umgebung verbundenen Objektmodells von Bedeutung. Das Objektmodell der OMG orientiert sich an dem klassischen Objektmodell, bei dem die Objekte über Botschaften miteinander kommunizieren. Anhand der Botschaften und ihrer Parameter wird entschieden, welche Operationen zur Ausführung gelangen. Definition 9.5 Objektsystem Eine Ansammlung von Objekten, die in der Lage sind, miteinander zu interagieren, wird als Objektsystem bezeichnet. Gemäß der Rollenverteilung zwischen Client- und Server-Objekten innerhalb eines Objektsystems unterscheidet man zwei Sichtweisen auf das Objektmodell. Die Objektsemantik ergibt sich aus dem Blinkwinkel des Dienstnutzers, die Objektimplementierung hingegen ist lediglich für den Diensterbringer von Interesse. Sicht des Dienstnutzers: Objektsemantik Objektsemantik Die Rolle des Client ist durch den Aufruf einer Dienstleistung gekennzeichnet. In einem Objektsystem werden die Dienstleistungen durch Objekte erbracht, welche die im Folgenden erläuterte Semantik besitzen. • Objekt Bei einem Objekt handelt es sich um eine identifizierbare, gekapselte Instanz, die Dienstleistungen innerhalb eines Objektsystems anbietet. Clients nutzen Dienstleistungen, indem sie diese anfordern. • Anforderung (Request) Eine Anforderung richtet sich an ein durch eine Referenz bestimmtes Zielobjekt. Sie wird durch einen Namen identifiziert und kann null bis mehrere Parameter besitzen. Die Identifikation der Parameter erfolgt über ihre Position innerhalb der Parameterliste. Man unterscheidet zwischen Eingabeparametern, Ausgabeparametern und Ein-/Ausgabeparametern. Neben den Ausgabeparametern kann eine Anforderung auch ein Ergebnis als Rückgabewert liefern. Anforderungen lösen die Ausführung von Operationen beim Objekt aus. • Operation Eine Operation repräsentiert eine Dienstleistung. Sie wird durch eine Signatur beschrieben, die sich zusammensetzt aus einem Identifikator, der Spezifikation des Ergebnisses der Operation und der Spezifikation der Parameter. Optional können auch mögliche Ausnahmezustände sowie die Ausführungssemantik gegenüber dem Client spezifiziert werden. Die Ausführungssemantik gibt darüber Auskunft, ob sich der Client mit der ausgeführten Operation synchronisiert, also ein Ergebnis erwartet oder nicht. Die Operationen eines Objektes werden zu Interfaces zusammengefasst. • Interface Ein Interface beinhaltet eine Menge von Operationen, die durch einen Client aufgerufen werden können. Die Beschreibung der Interfaces erfolgt mit Hilfe der Interface Definition Language (IDL). 9.5 CORBA/OMA • Attribut Interfaces können Attribute beinhalten. Ein Attribut ist mit einem Paar von Zugriffsmethoden verbunden. Die Get-Methode liest den Wert eines Attributs aus. Die Set-Methode wird definiert, wenn die Möglichkeit der Veränderung des Wertes bestehen soll. • Typ Die Angabe eines Typs charakterisiert Parameter oder Rückgabewerte. Neben den Objekttypen unterscheidet man bei der OMG einfache Typen, wie zum Beispiel Short, Double, String, sowie die zusammengesetzten Typen Struct, Sequence, Union und Array. • Kreieren und Freigeben von Objekten Objekte werden in Abhängigkeit vom Status einer Anforderung kreiert beziehungsweise wieder freigegeben. Clients interagieren mit den Objekten über Objektreferenzen. Das Kreieren und Freigeben von Objekten läuft für den Client transparent ab. 235 Sicht des Diensterbringers: Objektimplementierung Die Objekte eines Objektsystems haben die Aufgabe, Dienste zu erbringen. Sie weisen damit ein bestimmtes Verhalten auf, dessen Realisierung durch die Objektimplementierung beschrieben wird. Das Modell der Objektimplementierung der OMG setzt sich aus dem Konstruktionsmodell und dem Ausführungsmodell zusammen. • Konstruktionsmodell (Construction Model) Das Konstruktionsmodell beinhaltet die Definition des Objektzustandes und der Methoden des Objektes. Die mit einem Objekt assoziierten Daten geben seinen Zustand wieder. Die Bearbeitung der durch ein Objekt angebotenen Dienste führt zur Ausführung von Methoden. Bei den Methoden handelt es sich um Code, der den jeweiligen Diensten zugeordnet ist. Die Abbildung zwischen Diensten und Methoden sowie alle weiteren Aktionen, die mit dem Kreieren eines Objektes verbunden sind, müssen beschrieben werden. • Ausführungsmodell (Execution Model) Die Ausführung einer Methode, bezeichnet als Methodenaktivierung (method activation), erfolgt innerhalb einer abstrakten Maschine (execution engine). Eine abstrakte Maschine stellt einen dynamischen Ausführungskontext zur Verfügung, wobei das Format einer Methode als Attribut bestimmt, welche Maschine eine Methode interpretieren kann. 9.5.2 Objektimplementierung Object Management Architecture Das Referenzmodell der Object Management Architecture beschreibt alle beteiligten Komponenten, Schnittstellen und Protokolle. In einem zweiten Teil definiert das Referenzmodell der OMG domänenspezifische Objekt-Frameworks für integrierte Lösungen innerhalb von bestimmten Applikationsdomänen. Das Bild 9.7 gibt einen Überblick über die Object Management Architecture. Grundsätzlich spezifiziert die OMG nur die Semantik sowie die Schnittstellen für die Dienste und gibt keine Implementierungen vor. Auch Applikationen, die nicht nach dem objektorientierten Paradigma realisiert sind, können in die Infrastruktur von CORBA integriert werden. Wie im Bild 9.7 am Beispiel der Application Interfaces und Object Services angedeutet, geschieht dies über die Einbettung in Objekte durch so genannte object wrapper. Bei den Object Services handelt es sich um fundamentale Dienste für CORBA-basierte Applikationen unabhängig von der Anwendungsdomäne. Die von der OMG als CORBA- Object Services 236 9 Verteilte Anwendungen Application Interfaces Domain Interfaces Common Facilities Object Request Broker Bild 9.7 OMA-Referenzmodell Object Services services spezifizierten Dienste /9.5/ werden mit der Interface Definition Language (IDL) der OMG beschrieben. So stellt ein Naming Service zum Beispiel die Schnittstelle zur Verwaltung eines Namensraumes bereit, in dem die durch einen Object Request Broker vermittelten Dienste mit eindeutigem Namen verzeichnet sind. Common Facilities Die Common Facilities werden als nutzerorientierte und von einer Anwendungsdomäne unabhängige Dienste ebenfalls mit Hilfe der Interface Definition Language (IDL) beschrieben. Obwohl die OMG in ihrer Spezifikation nur die Schnittstellen beschreibt, kann man davon ausgehen, dass beispielsweise für CORBA-basierten Datenaustausch Implementierungen verfügbar sind. Domain Interfaces Domain Interfaces spezifizieren Schnittstellen für bestimmte Anwendungsdomänen, wie zum Beispiel CORBAtelecoms /9.7/ für den Bereich der Telekommunikation. Bild 9.7 hebt durch die Darstellung der Domain Interfaces hervor, dass weitere Anwendungsdomänen in dieser Kategorie existieren. Application Interfaces Die Application Interfaces unterliegen nicht dem Stadardisierungsprozess der OMG. Application Interfaces sind anwendungsspezifische Schnittstellen, die der Anwendungsentwickler definiert. Object Framework Entsteht im Zuge der Applikationsentwicklung für die Lösung bestimmter Aufgaben eine Sammlung kooperierender Objekte, so spricht man von einem Object Framework. Ein Object Framework kann Objekte aus allen vier Interface-Kategorien beinhalten. Object Request Broker Der Object Request Broker (ORB) bildet die zentrale Instanz für die Kommunikation von Client und Server in einer verteilten Umgebung. Sein Aufbau und die Programmierschnittstellen für seine Benutzung werden in der Common Object Request Broker Architecture (CORBA) Specification /9.4/ definiert. Ein ORB stellt sicher, dass für den Client, der einen Dienst angefordert hat, sowohl die Kommunikation mit dem Objekt als auch die Lokalisierung, Aktivierung und Implementierung des Objektes vollkommen transparent sind. 9.5.3 Schnittstellen eines CORBAkonformen ORB Common Object Request Broker Architecture Der Object Request Broker (ORB) nimmt Anforderungen entgegen, lokalisiert und startet die entsprechende Objektimplementierung und organisiert die Kommunikation zwischen Client und Server. Die im Bild 9.8 dargestellte Architektur eines Common Object Request Broker gibt Auskunft über den Grundaufbau und die Schnittstellen eines CORBA-konformen ORB /9.4/. 9.5 CORBA/OMA 237 Implementation Repository Interface Repository Object Implementation Client Dynamic Invocation IDL Stubs ORB Interface Static IDL Skeleton Dynamic Skeleton Object Adapter ORB Core standardisierte Schnittstellen (identisch für alle ORBs) mehrere Object Adapter möglich mehrere jeweils auf einen Objekttyp spezialisierte Schnittstellen ORB-spezifische Schnittstellen Der Client kann Anforderungen an ein Objekt über IDL Stubs oder über das Dynamic Invocation Interface absetzen. Mit einer durch den ORB gelieferten Objektreferenz erhält er Zugriff auf die Operationen des Objekts. Ein IDL Stub hält eine statische, mit der Interface Definition Language (IDL) definierte Schnittstelle zu den Methoden eines Objektes bereit. Für den Client erscheint ein Methodenaufruf auf dem Stub wie ein lokaler Methodenaufruf. Der Stub enthält Code für das Verpacken von Methodenaufrufen und ihrer Parameter in Nachrichten, die einem ORB-internen Format entsprechen und zum Server geschickt werden können. Diese Umsetzung wird wie beim RPC als marshal bezeichnet. Ein Client, der ein Objekt benutzen möchte, über dessen Schnittstellen zur Zeit der Programmentwicklung noch keine Festlegungen in Form einer IDL-Beschreibung vorliegen, geht über das Dynamic Invocation Interface. Dieses erlaubt die dynamische Erzeugung von Objektaufrufen mit Hilfe von Informationen aus dem Interface Repository. Das Interface Repository unterstützt dynamische Methodenaufrufe, indem es eine Schnittstelle für den Zugriff auf Informationen über IDL-Interfaces von Objekten bereitstellt. Dieses hohe Maß an Flexibilität ist jedoch nur für eine kleine Gruppe verteilter Anwendungen nötig. Auf der Server-Seite bildet der Object Adapter die oberste Schicht des ORB-Kommunikationsdienstes. Er erzeugt eine Laufzeit-Umgebung für die Objekte, referenziert die Objekte und leitet die Anforderungen an die Objekte weiter. Der Object Adapter registriert die Objektklassen und ihre Instanzen im Implementation Repository, das neben den Informationen über Klassen und Instanzen auch Einträge über Zugriffsbeschränkungen auf Objekte und weitere Angaben enthalten kann. Das Static IDL Skeleton stellt eine statische Schnittstelle für Server-Dienste bereit. Es konvertiert den vom ORB übertragenen Methodenaufruf in das Format der Programmiersprache, in der ein Server realisiert ist. Dieser Vorgang kann wie beim RPC als unmarshal bezeichnet werden. Alternativ zum Static IDL Skeleton steht das Dynamic Skeleton für Interfaces von Objekt-Implementierungen zur Verfügung, die zur Zeit der Server-Entwicklung noch unbekannt sind. Dies wird zum Beispiel bei der Kommunikation zwischen zwei ORBs benutzt. Bild 9.8 Struktur eines Object Request Broker marshal unmarshal 238 9 Verteilte Anwendungen Eine Schnittstelle zu einigen ORB-Diensten, die sowohl vom Client als auch vom Server-Objekt in Anspruch genommen werden können, bietet das ORB Interface. Hier findet man zum Beispiel Funktionalitäten für die Umwandlung von Objektreferenzen in Zeichenketten und umgekehrt. Auch Schnittstelleninformationen zu einem bestimmten Objekt lassen sich über das ORB Interface ermitteln. Beispiele für konkrete ORB-Implementierungen sind ORBIX von IONA und Distributed Smalltalk von ObjectShare. Das Java Development Kit von Sun enthält seit der Version 1.2 einen CORBA-konformen ORB, der Grundlage für die hier entwickelten Programmbeispiele sein soll. 9.5.4 Interface Definition Language CORBA-basierte Client/Server-Entwicklung Die Vorgehensweise bei der Entwicklung CORBA-basierter Client/Server-Applikationen hat viele Gemeinsamkeiten mit der Client/Server-Entwicklung auf der Basis von RPC. Grundlage ist die Interface Definition Language (IDL), die einen wichtigen Bestandteil des CORBA-Standards darstellt. IDL wird verwendet, um die Schnittstellen zu beschreiben, die ein Objekt bereitstellt. IDL orientiert sich an der Syntax und der Semantik der Programmiersprache C++. Als rein deskriptive Sprache hält IDL Konstrukte für die Deklaration von Operationen, Attributen und Ausnahmen (exceptions) bereit. Sie besitzt keine Konstrukte für algorithmische Strukturen. Syntax und Semantik von IDL werden im Folgenden nur soweit erläutert, wie es das Verständnis der Abläufe und Beispiele verlangt. Die umfassende Spezifikation findet man in /9.4/. Ein IDL-Compiler übersetzt, wie im Bild 9.9 dargestellt, das mit IDL beschriebene Interface in die Programmiersprache, in der Client und Server entwickelt werden sollen. IDL Definitions Implementation Installation IDL-Compiler Interface Repository Bild 9.9 IDL Language Mapping Stubs Client Skeletons Implementation Repository Object Implementation Es existieren Sprachanbindungen für einer Reihe von Zielsprachen, wie zum Beispiel Ada, C und Smalltalk. Das nachfolgend vorgestellte Beispiel für die CORBA-basierte Implementierung der Arithmetik-Anwendung bezieht sich auf die Abbildung zwischen IDL und Java. Die Beschreibung der einfachen Arithmetik-Schnittstelle erfolgt in der Datei Arithmetic.idl. Beispiel 9.13 module ArithmeticApp { interface Arithmetic { const long ARRAY_SIZE = 5; typedef long myArray[ARRAY_SIZE]; 9.5 CORBA/OMA 239 myArray sum_arrays(in myArray inArray1, in myArray inArray2); }; }; Eine IDL-Beschreibung besteht aus der Definition von Modulen, Interfaces, Konstanten, Typen und Ausnahmezuständen. Die Hauptaufgabe eines Moduls besteht darin, eine Menge inhaltlich zusammengehörender IDL-Definitionen zu gruppieren. Ein Modul eröffnet einen neuen Namensraum, in dem alle Bezeichner definiert werden können, ohne dass sie mit Bezeichnern aus anderen Namensräumen kollidieren. Innerhalb des Moduls ArithmeticApp ist das Interface Arithmetic definiert, das die Methode sum_arrays() zur Verfügung stellt. Ein Interface wird auch als Typ bezeichnet, weil es im Wesentlichen Methoden und damit eine bestimmte Schnittstelle beschreibt. Wie ein Objekt diese Schnittstelle implementiert, bleibt ihm überlassen, so dass eine Menge von gleichartigen Objekten entstehen kann. Die Methode sum_arrays() des Interface Arithmetic hat einen Rückgabewert und zwei Eingabeparameter vom benutzerdefinierten Typ myArray. Die Beschreibung der IDL-definierten Schnittstelle der Arithmetik-Anwendung wird dem Compiler idltojava übergeben: % idltojava Arithmetic.idl idltojava generiert ein Verzeichnis ArithmeticApp mit fünf Dateien und einem Unterverzeichnis ArithmeticPackage mit zwei weiteren Dateien. Die Datei Arithmetic.java enthält die Java-Version der IDL-definierten ArithmetikSchnittstelle. Die Dateien _ArtihmeticStub.java und _ArithmeticImplBase.java stellen Client Stub und Server Skeleton dar. Die Dateien ArithmeticHelper. java und myArrayHelper.java implementieren zusätzliche Funktionalitäten und insbesondere die narrow()-Funktion für die Umwandlung von CORBA-Objektreferenzen in die entsprechenden Objekttypen. Die Dateien ArithmeticHolder.java und myArrayHolder.java behandeln IDL-Definitionen, die sich nicht so einfach auf die Java-Semantik abbilden lassen, wie zum Beispiel Rückgabeparameter, die mit dem Schlüsselwort out in IDL gekennzeichnet sind. An den durch idltojava erzeugten Dateien sollte der Programmierer keine Änderungen vornehmen. Er kann sich auf die Implementierung von Client und Server konzentrieren. Zur Realisierung des Arithmetik-Dienstes werden auf Server-Seite zwei Java-Klassen vorgeschlagen, die sich aus den nachfolgend beschriebenen QuellcodeFragmenten zusammensetzen. Zunächst muss der Programmierer die durch idltojava generierten Klassen aus dem Package ArithmeticApp, die mit dem Java Development Kit gelieferten Klassen für den Naming-Service sowie die ebenfalls mit dem Java Development Kit gelieferten Klassen für die Nutzung des Object Request Broker importieren: Beispiel 9.14 import ArithmeticApp.*; import org.omg.CosNaming.*; import org.omg.CosNaming.NamingContextPackage.*; idltojava 240 9 Verteilte Anwendungen import org.omg.CORBA.*; Die von _ArithmeticImplBase abgeleitete Klasse ArithmeticServant implementiert das in IDL beschriebene Interface Artihmetic mit der vereinbarten Funktion sum_arrays(). Während es sich bei sum_arrays() um eine einfache Java-Methode handelt, stellt das Server Skeleton _ArithmeticImplBase die Kommunikation mit dem Object Request Broker sicher. Jede Instanz vom Typ Arithmetic wird durch ArithmeticServant implementiert: Beispiel 9.15 class ArithmeticServant extends _ArithmeticImplBase { public int[] sum_arrays(int[] inArray1, int[] inArray2) { int[] outArray = new int[5]; int i; for(i = 0; i < ARRAY_SIZE; i++) outArray[i] = inArray1[i] + inArray2[i]; return(outArray); } } Die main()-Funktion der Klasse ArithmeticServer erfüllt die folgenden Aufgaben: 1. Kreieren und Initialisieren einer Instanz des Object Request Broker 2. Kreieren einer Instanz vom Typ Arithmetic und Bekanntgabe dieses neuen CORBA-Objektes an den Object Request Broker 3. Erlangen einer CORBA-Objektreferenz für den Namensdienst, in dem das neue CORBA-Objekt verzeichnet werden soll 4. Registrieren des neuen CORBA-Objektes im Namensdienst unter der Bezeichnung “Arithmetic” 5. Warten auf Client-Anforderungen Beispiel 9.16 public class ArithmeticServer { public static void main(String args[]) { try{ // (1.) ORB orb = ORB.init(args, null); // (2.) ArithmeticServant arithmRef = new ArithmeticServant(); orb.connect(arithmRef); 9.5 CORBA/OMA 241 // (3.) org.omg.CORBA.Object objRef = orb.resolve_initial_references(„NameService”); NamingContext ncRef = NamingContextHelper.narrow(objRef); // (4.) NameComponent nc = new NameComponent(„Arithmetic”, „“); NameComponent path[] = {nc}; ncRef.rebind(path, arithmRef); // (5.) java.lang.Object sync = new java.lang.Object(); synchronized (sync) { sync.wait(); } } catch (Exception e) { System.err.println(„ERROR: „ + e); e.printStackTrace(System.out); } } } Nach seiner Implementierung wird der Arithmetik-Server inklusive Server Skeleton durch den Java-Compiler, javac, übersetzt. Durch den folgenden Aufruf des mit dem Java Development Kit gelieferten Namensdienstes stellt man sicher, dass ein Namenskontext für die Eintragung des Arithmetik-Dienstes existiert: % tnameserv -ORBInitialPort 1050 Anschließend wird der Arithmetik-Server gestartet: % java ArithmeticServer -ORBInitialPort 1050 Damit steht der Arithmetik-Service als CORBA-konformer Dienst zur Verfügung und kann durch jeden CORBA-konformen Client, der das IDL-definierte Interface Arithmetic kennt, genutzt werden. Der Arithmetik-Client muss wissen, dass der Dienst unter dem Namen „Arithmetic” beim Name Service verzeichnet ist. 9.5.5 Übungen Aufgaben 9.19 Vergleichen Sie die Vorgehensweise bei der Applikationsentwicklung auf der Basis von RPC mit der Entwicklung CORBA-basierter Applikationen! Welche Gemeinsamkeiten und welche Unterschiede gibt es? 9.20 In der Datei ArithmeticClient.java wird ein möglicher Dienstnutzer für den Arithmetik-Service vorgeschlagen. a) Erläutern Sie die im Kommentar mit (1.) bis (4.) eingeleiteten Code-Passagen! javac 242 9 Verteilte Anwendungen b) Implementieren Sie den CORBA-konformen Arithmetik-Client in Java und übersetzen Sie diesen mit Hilfe von javac! c) Wie rufen Sie den gerade realisierten Arithmetik-Client auf? Beachten Sie, dass die Nummer für den Initialport des Object Request Broker eventuell über einen Kommandozeilenparameter zu übergeben ist. import ArithmeticApp.*; import org.omg.CosNaming.*; import org.omg.CORBA.*; public class ArithmeticClient { public static void main(String args[]) { int[] inArray1 = {0, 1, 2, 3, 4}; int[] inArray2 = {9, 8, 7, 6, 5}; try{ // (1.) ORB orb = ORB.init(args, null); // (2.) org.omg.CORBA.Object objRef = orb.resolve_initial_references(“NameService”); NamingContext ncRef = NamingContextHelper.narrow(objRef); // (3.) NameComponent nc = new NameComponent(“Arithmetic”, “”); NameComponent path[] = {nc}; Arithmetic arithmRef = ArithmeticHelper.narrow(ncRef.resolve(path)); // (4.) int[] outArray = arithmRef.sum_arrays(inArray1, inArray2); System.out.print(“Output: “); for (int i = 0; i < 5; i++) System.out.print(outArray[i] + “”); System.out.println(); } catch (Exception e) { System.out.println(“ERROR : “ + e) ; e.printStackTrace(System.out); } } } 9.21 Aus welchen Gründen würden Sie bei der Entwicklung verteilter Anwendungen auf CORBA zurückgreifen? 9.6 Component Object Model 9.6 243 Component Object Model Das Component Object Model (COM) ist eine Entwicklung der Microsoft Corporation und definiert eine Infrastruktur und ein Programmiermodell für die Realisierung verteilter Software-Komponenten. Vergleichbar mit CORBA/OMA der Object Management Group besteht ein wichtiges Ziel darin, die Komplexität von Software-Applikationen zu beherrschen. Applikationen im Sinne von COM bestehen aus Software-Komponenten, die sich dadurch auszeichnen, dass sie in binärer Form wiederverwendbar sind. Um die Interaktion zwischen den Komponenten zu gewährleisten, spezifiziert COM neben dem Objektmodell einen binären Interoperabiltätsstandard unabhängig von der verwendeten Programmiersprache. Die Bezeichnung Distributed Component Object Model (DCOM) unterstreicht die Umsetzung der Verteilungsaspekte von Client- und Server-Komponenten im Netzwerk. Obwohl die Netzwerktransparenz Bestandteil der COM-Spezifikation /9.8/ ist, gingen die Kommunikationsmechanismen der ersten COMImplementierungen nicht über die Rechnergrenzen hinaus. COM hat als Bestandteil der Microsoft-Windows-Umgebung weite Verbreitung gefunden und auch für andere Betriebssysteme existieren Portierungen. COM bildet die Grundlage der OLE- und der neueren ActiveX-Technologie. OLE und ActiveX bieten aufsetzend auf die Dienste von COM höhere Funktionaltitäten an, wie zum Beispiel das Einfügen von Objekten in dafür vorgesehene Container (Object Linking and Embedding) oder die entfernte Steuerung von Applikationen (Automation). Die ActiveX-Technologie erlangte ihre Bedeutung vor allem im Zusammenhang mit der Programmierung im Internet. Distributed Component Object Model ActiveX ActiveX/DCOM, entwickelt durch die Microsoft Corporation, unterstützt ähnlich wie CORBA/OMA, spezifiziert durch die Object Management Group, die Erstellung von Objektsystemen (siehe Definition 9.5). Für das Verständnis der Programmierung mit Hilfe des Component Object Model sollte der Entwickler verteilter Anwendungen einen Überblick über das Objektmodell und die Architektur von COM besitzen. 9.6.1 Objektmodell der COM-Spezifikation Die COM-Spezifikation /9.8/ definiert unter anderem die Begriffe Client, Objekt, Interface, Object Implementor und Server. • • • Client Unter den allgemeinen Begriff des Client fallen Programme oder Skripte, die Dienstleistungen von einem oder von mehreren Objekten in Anspruch nehmen. Objekt Objekte stellen Funktionalitäten über ein Interface oder mehrere Interfaces zur Verfügung. Ein COM-konformes Objekt zeichnet sich dadurch aus, dass ein Client niemals direkten Zugriff auf dieses Objekt erhält. Dienstnutzer kommunizieren mit COM-Objekten nur über Interfaces. Der Begriff des Objektes wird bei COM synonym für die Klasse und für die Instanz einer Klasse benutzt. Interface Bei einem Interface handelt es sich um eine Gruppe semantisch zusammengehörender Funktionen. Im Programm werden diese Schnittstellen über ihren Namen angesprochen, der per Konvention mit einem „I” beginnen sollte. Das COM-System verwaltet Interfaces über das Konzept der Globally Unique Identifiers (GUIDs), hinter dem sich eine 128 bit große Integer-Zahl verbirgt. GUIDs garantieren aufgrund ihrer Größe die weltweite Eindeutigkeit eines Identifikators mit sehr großer Wahrscheinlichkeit. Interfaces unterliegen keiner Versionierung, das heißt, bei jeder Globally Unique Identifiers 244 9 Verteilte Anwendungen • • COM Class Identifier Veränderung der Schnittstelle wird ein neuer GUID erzeugt. Grundsätzlich beschreiben Interfaces nur die Schnittstelle zu einem COM-konformen Dienst und nicht die Implementierung des Dienstes. Wenn ein Objekt ein Interface implementiert, so muss es alle darin definierten Funktionen realisieren und dem COM-System Referenzen auf diese Funktionen übergeben. Object Implementor Programme, die mindestens ein Objekt mit seinem Interface oder seinen Interfaces implementieren, werden als Object Implementors bezeichnet. Dabei handelt es sich meistens um Applikationen, deren Objekte ihre Funktionalität nach außen zur Verfügung stellen. Server Ein Server ist dafür zuständig, Objekte aus ihren Klassen zu erzeugen und den Objektklassen einen COM Class Identifier (CLSID) in Form eines Globally Unique Identifier zuzuweisen. Mit diesem Identifikator besteht für den Client die Möglichkeit, über das COM-System Objekte einer bestimmten Klasse anzufordern. Das COM-System veranlasst dann den Server, ein Objekt der Klasse zu erzeugen. Anschließend verbindet COM den Client mit dem neuen Objekt. Die in der COM-Spezifikation definierten Zusammenhänge zwischen den Begriffen des Objektmodells führen unmittelbar zu den Aspekten des Client/Server-Modells der COM-Spezifikation und der COM-Architektur. 9.6.2 BasisInterface Client/Server-Modell der COM-Spezifikation Das Bild 9.10 zeigt die typische Darstellungsform für COM-Objekte, ihre Interfaces und die Beziehung zwischen Client und Server. Bei dem Interface IUnknown handelt es sich um ein spezielles Basis-Interface, das von allen Objekten unterstützt wird. IUnknown ist im Top-Level-Objekt mit den Funktionen QueryInterface(), AddRef() und Release() definiert. QueryInterface() ermöglicht es dem Client, Informationen über Interfaces, die ein Objekt unterstützt, abzufragen. AddRef() und Release() haben Bedeutung für den Objektlebenszyklus. Sie greifen auf einen durch das COMSystem verwalteten Referenzzähler zu, wobei AddRef() den Zähler um eins erhöht und Release() den Zähler um eins verringert. Existiert keine Client-Referenz mehr auf das Objekt, so wird das Objekt freigegeben und aus dem Speicher entfernt. IUnknown (4) Interface Pointer Object Client Application Bild 9.10 COM Client/ Server-Modell (1) (3) COM Server (2) Ein COM-Client erhält einen Interface Pointer, indem er das gewünschte Objekt mit Hilfe des COM Class Identifier (CLSID) vom COM-System anfordert (1). Das COMSystem ermittelt die Implementierung über einen Lokalisierungsdienst, veranlasst den entsprechenden Server ein Objekt der angeforderten Klasse zu kreieren (2) und gibt dem 9.6 Component Object Model 245 Client einen Interface Pointer zurück (3). Mit Hilfe des Interface Pointer ist der Client dann in der Lage, die Funktionen der benötigten Schnittstelle aufzurufen (4). COM Library und Service Control Manager Die Spezifikation der COM-Architektur ist bereits eng mit der Implementierung verbunden. Ein wichtiges Implementierungsdetail beschreibt die Kernfunktionalität des Systems, die in eine Bibliothek mit der Bezeichnung COM Library aufgenommen wurde. Zu diesem Kern zählen die folgenden Funktionalitäten: • Service Control Manager: Lokalisierungsdienst für Implementierungen Der Service Control Manager verwaltet die Klasseninformationen über verfügbare Objekte, basierend auf der Registrierungsdatenbank des Systems. Er ist dafür verantwortlich, dass nach einem Client-Request der entsprechende Server aufgefunden und gestartet wird. • API-Funktionen zur Erstellung von COM-Applikationen Die COM Library stellt unter anderem Kernfunktionen zur Programmierung verteilter Anwendungen bereit, deren Namen per Konvention mit dem Präfix „Co” beginnen. So fordert zum Beispiel ein Client mit CoCreateInstance() ein Objekt und einen Interface Pointer vom COM-System an. • Speicherverwaltung COM stellt die Speicherverwaltung für die beteiligten Prozesse sicher, lässt es aber auch zu, dass unter der Einhaltung bestimmter Regeln der Programmierer selbst den Speicherbereich kontrolliert. So kann es zum Beispiel notwendig sein, für den Austausch von Daten zwischen unabhängig voneinander und eventuell in unterschiedlichen Programmiersprachen entwickelten Software-Komponenten spezielle Speicherbereiche zu definieren. • Remote Procedure Call Durch den im Bild 9.11 dargestellten Einsatz des Remote Procedure Call (RPC) bei der Kommunikation zwischen Client und Server ist es für den Dienstnutzer vollkommen transparent, ob sich Client- und Server-Objekte innerhalb eines Prozesses auf dem gleichen Rechner oder auf unterschiedlichen Rechnern befinden. Für die Implementierung des RPC verweist Microsoft auf die Spezifikation des DCE RPC /9.2/. • Fehlerbehandlung COM spezifiziert eine einheitliche Vorgehensweise bei der Fehlerbehandlung. Alle Interface-Funktionen und die Funktionen der COM Library sollten Status- beziehungsweise Fehlerinformationen zurückgeben. Die meisten Funktionen benutzen einen Rückgabewert vom Typ HRESULT, einem 32 Bit großen Wert mit interner Struktur. Die eigentliche Fehlerbehandlung unterstützt COM durch spezielle Makros und Funktionen. COM Library Service Control Manager Der potenzielle Anbieter einer COM-Implementierung steht vor der Aufgabe, die COM Library für die jeweilige Zielplattform zu realisieren. So implementierte zum Beispiel die Software AG Systemhaus GmbH COM unter anderem für die Betriebssysteme Sun Solaris, Digital Unix und Linux. COM Server Server können in Abhängigkeit von bestimmten Randbedingungen, wie zum Beispiel in Abhängigkeit von Belangen der Lastverteilung, in den drei unterschiedlichen Varianten In-Process Server, Local Server und Remote Server programmiert sein. Das Bild 9.11 gibt einen Überblick über die drei Implementierungsvarianten. COM Server 246 9 Verteilte Anwendungen Local Machine Local Server Process Client Process In-Process Object Client Application In-Process Server DCOM RPC Local Object Stub Local Server Local Object Proxy Remote Machine Remote Server Process DCOM Remote Object Proxy Bild 9.11 In-Process Server, Local Server, Remote Server RPC Stub Local Object DCOM Remote Server Ein Server, der sich im Speicherbereich des Client-Prozesses befindet, wird als In-Process Server bezeichnet. In einer Microsoft-Windows-Umgebung sind In-Process Server in einer Dynamic Link Library (DLL) mit der Dateierweiterung „.dll” implementiert. Unter einem Local Server versteht man einen Server, der in einem separaten Prozess auf dem gleichen Rechner wie der Client läuft. Der Local Server stellt eine eigenständige Applikation dar, deren Implementierung in Dateien vom Typ executable, Dateierweiterung „.exe”, erfolgt. Remote Server werden als Dynamic Link Library oder als Datei vom Typ executable auf einem entfernten Rechner gestartet. Für den Start entfernter Dynamic Link Libraries sind so genannte Surrogate-Prozesse zuständig, die einen Prozesskontext für die Dynamic Link Libraries bereitstellen. Die Verteilungstransparenz von Client- und Server-Objekten basiert auf dem Remote Procedure Call (RPC), der zu den Kernfunktionalitäten des COM-Systems gehört. Class Factory Ein COM Server ist dafür zuständig, neben dem eigentlichen Objekt eine so genannte Class Factory sowie die Anmeldung der Class Factory beim COM-System und das geordnete Beenden des Server zu implementieren. Jede Objektklasse besitzt eine Class Factory, deren Aufgabe darin besteht, die Objektinstanzen der jeweiligen Klasse zu kreieren. Die Class Factory kann durch einen Client über das Interface IClassFactory angesprochen und zum Kreieren eines Objektes aufgefordert werden. Standard-Interfaces StandardInterfaces Aufsetzend auf die COM-Basisfunktionalität definiert die COM-Spezifikation einen Satz von Standard-Interfaces, die in der MS-Windows-Umgebung drei in das Betriebssystem integrierte Kernkomponenten bilden. • • Persistent Storage Unter dem Begriff Persistent Storage, auch als Structured Storage bezeichnet, wird eine Reihe von Interfaces zusammengefasst, die sich mit der persistenten Speicherung von Daten befassen. Persistent Storage ermöglicht unter anderem den Zugriff unterschiedlicher Applikationen auf ein und dieselben persistenten Informationen. Persistent, Intelligent Names Persistent, Intelligent Names realisieren das Konzept, Operationen, wie zum Beispiel Datenbankanfragen, Dateien und Objekte, mit Hilfe von eindeutigen Namen anzusprechen. Diese als Monikers bezeichneten Persistent, Intelligent Names sind Objekte, deren Intelligenz darin besteht, dass sie Interfaces für den Umgang mit 9.6 • Component Object Model 247 den Namen selbst implementieren. Außerdem unterstützen Monikers Interfaces von Persistent Storage zur Gewährleistung der Persistenz der Namen. Uniform Data Transfer Uniform Data Transfer beinhaltet Interfaces für den standardisierten Datenaustausch zwischen Client und Server. Der Client kann ein Server-Objekt veranlassen, ihm Veränderungen im Datenbestand anzuzeigen. Uniform Data Transfer legt Formate und Medien für den Austausch der Daten fest, wie zum Beispiel den Austausch von ASCII-Text über den Hauptspeicher. Standard-Interfaces, wie IStream und IStorage, IMoniker sowie IDataObject und ihre Implementierungen unterstützen den Programmierer bei seiner Applikationsentwicklung, indem er ihre Funktionalität nutzen und erweitern kann. Die COM-Spezifikation /9.8/ definiert weitere Interfaces und auch andere, nicht standardisierte Komponenten, sind über COM-Schnittstellen ansprechbar. 9.6.3 Client/Server-Entwicklung mit COM Die Programmierung von Applikationen auf der Basis des Component Object Model erfolgt auf sehr unterschiedlichem Niveau. Eine sehr hohe Abstraktion findet man in der Entwicklungsumgebung von Microsofts Visual Basic, die den Programmierer von den Details der COM-Programmierung abschirmt. Der C++-Programmierer wird durch die Microsoft Foundation Classes (MFC) und durch Microsofts Active Template Library (ATL) unterstützt. ATL bildet eine in C++ geschriebene Hülle für viele COM-Funktionen. Durch das Zusammenwirken von ATL und Code-Generatoren wird der Programmierer davon entlastet, sich mit den Einzelheiten von COM zu befassen. Entwicklungsumgebungen bieten Code-Generatoren in Form so genannter AnwendungsAssistenten (Application Wizards) an. Die im Folgenden dargestellte Implementierung der einfachen Arithmetik-Anwendung als COM-Applikation erfolgt auf Server-Seite mit Hilfe des ATL-COM-Anwendungs-Assistenten der Entwicklungsumgebung Microsoft Visual C++ Version 6.0. Der ATL-COM-Anwendungs-Assistent erzeugt zunächst in Abhängigkeit vom gewählten Server-Typ ein Code-Gerüst, bestehend aus mehreren Dateien, für das neue Projekt. Im Projekt ArithmeticServer soll eine ausführbare Datei, Dateierweiterung „.exe”, erzeugt werden. Anschließend ist der Programmierer dazu angehalten, ein ATL-Objekt mit den gewünschten Eigenschaften und der gewünschten Schnittstelle in das Projekt einzufügen. Bei dem Beispiel handelt es sich um ein einfaches ATL-Objekt mit einer Schnittstelle unter der Kurzbezeichnung Arithmetic. Für diese Schnittstelle sollen die Standardeinstellungen des ATL-Objekt-Assistenten gelten. Eine wichtige Rolle für die weitere Entwicklung der Applikation spielt die generierte IDL-Datei. Die Microsoft Interface Definition Language (MIDL) stellt eine Erweiterung der IDL des unter 9.4 vorgestellten Distributed Computing Environment (DCE) dar und ermöglicht die Definition von COM-Schnittstellen. Für die einfache Arithmetik-Anwendung generiert der Anwendungs-Assistent die Datei ArithmeticServer.idl, die den Ausgangspunkt für die Definition der Arithmetik-Schnittstelle bildet. Das Beispiel 9.17 zeigt den für die weiteren Betrachtungen relevanten Teil der IDL-Datei mit Erweiterungen durch den Programmierer. Microsoft Foundation Classes Microsoft Active Template Library 248 9 Verteilte Anwendungen Beispiel 9.17 const long ARRAY_SIZE = 5; typedef SAFEARRAY(long) * pSafeArray; [ object, uuid(CC51BD77-B9C2-11D2-ABE1-0020AFF9B45F), dual, helpstring(„IArithmetic-Schnittstelle”), pointer_default(unique) ] interface IArithmetic : IDispatch { [id(1), helpstring(“Methode sum_arrays”)] HRESULT sum_arrays([in] pSafeArray inArray1, [in] pSafeArray inArray2, [in, out] pSafeArray outArray); }; TypeLibMarshalling Mit ARRAY_SIZE wird eine symbolische Konstante vereinbart und pSafeArray ist als Zeiger auf ein Array vom OLE-Typ SAFEARRAY definiert. Die Verwendung von OLE-Typen hat den Vorteil, dass die Erzeugung von applikationsspezifischem Proxy/ Stub-Code entfällt. Bei diesem auch als TypeLib-Marshalling bezeichneten Verfahren kommt der auf jedem Microsoft-Windows-System installierte OLE-Marshaller für den Zugriff auf die Funktionen des Interface zum Einsatz. Das Schlüsselwort object zeigt an, dass es sich um die Schnittstelle eines COM-Objektes handelt und nicht um eine RPC-Schnittstelle. Der Eintrag uuid() identifiziert durch einen Globally Unique Identifier (GUID) das Interface eindeutig. Die Festlegung der Schnittstelle als dual steht im engen Zusammenhang mit der Verwendung des OLE-Marshaller. Interfaces mit der Kennzeichnung dual werden wie die Schnittstelle des Beispiels IArtihmetic vom Interface IDispatch abgeleitet. Damit stehen dem Interface IArithmetic sowohl die Funktionen von IDispatch als auch die Funktionen der Basisschnittstelle IUnknown zur Verfügung. Mit dem Konstrukt helpstring() wird eine Klartextbezeichnung für Interfaces und Methoden festgelegt. pointer_default() dient der Code-Optimierung und bewirkt, dass alle nicht anders bezeichneten Zeiger in Funktionen auf eine bestimmte Art und Weise zu behandeln sind. Die einzige innerhalb von IArithmetic definierte Funktion sum_arrays() erhält mit id() die Funktionsnummer 1. sum_arrays() gibt einen Wert vom Typ HRESULT zurück und besitzt drei Parameter, wobei der letzte als Ein/Ausgabeparameter das eigentliche Ergebnis der Funktion liefert. Aus den Angaben innerhalb der IDL-Datei generiert der MIDL-Compiler Code für das Marshalling und die Type Library. Die Aufgabe des Programmierers besteht nun noch darin, eine Implementierung für die definierten Funktionen anzugeben. Die Umsetzung der Methode sum_arrays() erfolgt im Modul Arithmetic.cpp und könnte folgendermaßen aussehen: Beispiel 9.18 STDMETHODIMP CArithmetic::sum_arrays(pSafeArray inArray1, pSafeArray inArray2, pSafeArray outArray) { 9.6 Component Object Model SafeArrayLock(*inArray1); SafeArrayLock(*inArray2); SafeArrayLock(*outArray); for (int i = 0; i < ARRAY_SIZE; i++) ((long*)(*outArray)->pvData)[i] = ((long*)(*inArray1)->pvData)[i] + ((long*)(*inArray2)->pvData)[i]; SafeArrayUnlock(*inArray1); SafeArrayUnlock(*inArray2); SafeArrayUnlock(*outArray); return S_OK; } Der Aufruf einer Funktion, die als STDMETHOD deklariert ist, entspricht der PascalKonvention, was zum Beispiel bedeutet, dass die einzelnen Parameter in umgekehrter Reihenfolge auf dem Stack abgelegt werden. Außerdem geben diese Funktionen standardmäßig einen Wert vom Typ HRESULT zurück. Der Zugriff auf das Datenfeld eines SAFEARRAY sollte durch die Funktion SafeArrayLock() eingeleitet und durch SafeArrayUnlock() beendet werden, um die Gültigkeit des Zeigers pvData sicherzustellen. Auf der Basis der beteiligten Anwendungs-Assistenten, ATL und MIDL entsteht ein großer Teil der Applikation durch automatisches Generieren. Auch die für COM-Objekte notwendige Funktionalität, wie zum Beispiel die Class Factory oder der Referenzzähler, werden mit generiert. Im Allgemeinen muss der Programmierer keine Änderungen an dem generierten Code vornehmen. Bei dem beschriebenen Arithmetik-Beispiel erhält man mit dem Erstellen von ArithmeticServer.exe einen lokalen Server, der ein COM-Objekt mit dem Interface IArithmetic bereitstellt und in der lokalen Registrierungsdatenbank verzeichnet ist. Das Beispiel 9.19 implementiert einen COM Client, der den Zugriff auf das Interface IArithmetic erlangt. Der Quell-Code des Beispiels 9.19 demonstriert den typischen Ablauf innerhalb einer COM-Applikation und kann unmittelbar in die WinMain()-Funktion einer einfachen Win32-Anwendung integriert werden. Das Beispiel erfordert weiterhin die Einbeziehung der folgenden Dateien: #include <stdio.h> #include “..\ArithmeticServer\ArithmeticServer.h” #include “..\ArithmeticServer\ArithmeticServer_i.c” Beispiel 9.19 ... int char i; lpcOut[128]; IUnknown* pIUnknown = NULL; IArithmetic* pIArithmetic = NULL; SAFEARRAY *pInArray1, *pInArray2, *pOutArray; SAFEARRAYBOUND aDim[1]; 249 250 9 Verteilte Anwendungen aDim[0].lLbound = 0; aDim[0].cElements = ARRAY_SIZE; pInArray1 = SafeArrayCreate(VT_I4, pInArray2 = SafeArrayCreate(VT_I4, pOutArray = SafeArrayCreate(VT_I4, SafeArrayLock(pInArray1); SafeArrayLock(pInArray2); for (i = 0; i < ARRAY_SIZE; i++) ((long*)pInArray1->pvData)[i] = ((long*)pInArray2->pvData)[i] = } SafeArrayUnlock(pInArray1); SafeArrayUnlock(pInArray2); 1, aDim); 1, aDim); 1, aDim); { i; 9 - i; CoInitialize(NULL); CoCreateInstance(CLSID_Arithmetic, NULL, CLSCTX_ALL, IID_IUnknown, (void**) &pIUnknown); pIUnknown->QueryInterface(IID_IArithmetic, (void**) &pIArithmetic); pIUnknown->Release(); pIArithmetic->sum_arrays(& pInArray1, & pInArray2, & pOutArray); pIArithmetic->Release(); SafeArrayLock(pOutArray); sprintf(lpcOut, „Output: %d %d %d %d %d”, ((long*)pOutArray->pvData)[0], ((long*)pOutArray->pvData)[1], ((long*)pOutArray->pvData)[2], ((long*)pOutArray->pvData)[3], ((long*)pOutArray->pvData)[4]); SafeArrayUnlock(pOutArray); MessageBox(NULL, lpcOut, “ArithmeticClient”, MB_OK); CoUninitialize(); ... Nach Vereinbarung der als Parameter benötigten Felder vom OLE-Typ SAFEARRAY erfolgt zunächst die Initialisierung der COM Library durch CoInitialize(). Anschließend wird mit CoCreateInstance() eine uninitialisierte Instanz der durch CLSID_Arithmetic spezifizierten Klasse erzeugt. Der zweite mit NULL initialisierte Parameter von CoCreateInstance() ist von Bedeutung, wenn das Objekt als Teil einer Aggregation aufgerufen wird. Die Aggregation stellt eine Form der Wiederverwendung von COM-Objekten dar, bei der ein kontrollierendes Objekt die Schnittstellen von Aggregatobjekten nach außen anbietet, als seien es seine eigenen Schnittstellen. Der dritte Parameter gibt den Ausführungskontext für das Zielobjekt an, wobei CLSCTX_ALL offen lässt, ob es sich zum Beispiel um einen lokalen oder einen entfernten Server handelt. Da man als Entwickler immer davon ausgehen sollte, dass 9.6 Component Object Model die angesprochene Komponente eine andere ist als erwartet, wird zunächst auf das generell implementierte Interface IUnknown zugegriffen, das sich hinter dem Parameter IID_IUnknown verbirgt. Nach der erfolgreichen Ausführung von CoCreateInstance() enthält pIUnknown einen Zeiger auf das Basis-Interface des gewünschten Objektes. Bei Verfügbarkeit eines Zeigers auf IUnknown kann durch den Aufruf von QueryInterface() festgestellt werden, welche Schnittstellen die Komponente unterstützt. Im Beispiel 9.19 gibt QueryInterface() nach erfolgreichem Aufruf mit IID_IArithmetic in pIArithmetic einen Zeiger auf das Interface IArithmetic zurück. Dieser Interface-Zeiger ermöglicht die Ausführung der in IArithmetic enthaltenen Funktion sum_arrays(). Nicht mehr benötigte Schnittstellen sind mit Release() wieder freizugegeben, so dass die Komponente aus dem Speicher entfernt werden kann, wenn ihr Referenzzähler den Wert 0 besitzt. CoUnInitialize() gibt als Pendant zu CoInitialize() die mit der Benutzung der COM Library verbundenen Ressourcen wieder frei. Um die Anwendung zu testen, muss der Arithmetik-Client nach erfolgreicher Übersetzung auf dem lokalen Rechner aufgerufen werden. Das Ziel der Aufgabe 9.27 besteht darin, COM Client und COM Server innerhalb eines Netzwerkes zu verteilen. 9.6.4 Übungen Aufgaben 9.22 Erläutern Sie die folgenden Aussagen bezogen auf das Distributed Component Object Model! a) Ein Interface ist keine Klasse. b) Ein Interface ist kein Objekt. c) Ein Objekt kann mehrere Interfaces implementieren. d) Ein Client interagiert lediglich mit Referenzen auf Interfaces. 9.23 Welche Vorteile und welche Nachteile ergeben sich durch die fehlende Versionierung beim Interface-Konzept von COM? 9.24 Was verstehen Sie unter einem Referenzzähler im Zusammenhang mit dem Objektlebenszyklus unter COM! 9.25 Welche Aufgaben hat die Funktion QueryInterface() und wo wird sie implementiert? 9.26 Erweitern Sie den im Beispiel 9.19 implementierten Arithmetik-Client um eine Fehlerbehandlung! Beachten Sie, dass die Funktionen der COM-API vereinbarungsgemäß einen Statuswert vom Typ HRESULT zurückgeben! Sie können für Ihre Fehlerbehandlung die Makros SUCCEEDED() und FAILED() benutzen, die in Abhängigkeit von einem als Parameter übergebenen Statuswert, TRUE oder FALSE liefern. 9.27 Implementieren Sie Arithmetik-Client und Arithmetik-Server als verteilte DCOMAnwendung! a) Stellen Sie sicher, dass DCOM auf Ihrem System verfügbar ist! b) Lassen Sie den Arithmetik-Server als lokalen und als entfernten Server registrieren! Ändern Sie dazu im Modul ArithmeticServer.cpp den ersten Parameter der Methode _Module.RegisterClassObjects() von CLSCTX_LOCAL_SERVER auf CLSCTX_SERVER! Übersetzen Sie den Arithmetik-Server neu! 251 252 9 Verteilte Anwendungen c) Installieren Sie den Arithmetik-Server auf einem entfernten System! Registrieren Sie den Arithmetik-Server auf dem lokalen und auf dem entfernten System! Die Registrierung erfolgt zum Beispiel durch Übersetzung oder mit dem Aufruf von ArithmeticServer.exe -Regserver. Überprüfen Sie mit dem Programm dcomcnfg.exe, ob Sie die Rechte zum Start des entfernten Server besitzen! d) Ergänzen Sie, wenn nötig, innerhalb ihres Projektes für den Arithmetik-Client vor dem Einfügen der Header-Dateien zum Beispiel im Modul stdafx.h den Eintrag #define _WIN32_DCOM! Rufen Sie auf der Seite des Client die Funktion CoCreateInstanceEx() anstelle von CoCreateInstance() auf! COSERVERINFO ServerInfo = {0, L”x.x.x.x”, 0, 0}; MULTI_QI qi = {&IID_IUnknown, NULL, 0}; CoCreateInstanceEx( CLSID_Arithmetic, NULL, CLSCTX_ALL, &ServerInfo, 1, &qi); pIUnknown = qi.pItf; In der Serverbeschreibungsstruktur COSERVERINFO ist x.x.x.x durch die Adresse des entfernten Rechners beziehungsweise durch seinen WINS- oder DNS-Namen zu ersetzen. CoCreateInstanceEx() wird ein Zeiger auf ein Array von MULTI_QI-Strukturen übergeben. Diese bestehen aus einem Identifikator, der den Wert des zu erfragenden Interface enthält, einem Zeiger, der mit der Referenz auf das zu ermittelnde Interface gefüllt wird und einem Rückgabewert vom Typ HRESULT, der Auskunft über den Status von QueryInterface() für die jeweilige Schnittstelle gibt. Der vorletzte Parameter von CoCreateInstanceEx() spezifiziert die Anzahl der MULTI_QIStrukturen innerhalb des MULTI_QI-Feldes. e) f) Stellen Sie CoCreateInstance() und CoCreateInstanceEx() hinsichtlich ihrer Parameter gegenüber. Welche Gemeinsamkeiten und welche Unterschiede gibt es? Übersetzen Sie den Client neu und testen Sie die verteilte DCOM-Anwendung! Referat 9.2 Kennzeichnen Sie CORBA/OMA, spezifiziert durch die Object Management Group, und ActiveX/DCOM, spezifiziert durch die Microsoft Corporation, sowie die darauf aufsetzenden verteilten Anwendungen als Objektsysteme! Stellen Sie dabei Objektmodell und Architektur der Systeme gegenüber und gehen Sie auf Gemeinsamkeiten und Unterschiede ein! Welche Vor- und Nachteile ergeben sich hieraus für die Client/Server-Entwicklung mit beiden Systemen? 9.7 Verteilte Anwendungen im World Wide Web 9.7.1 Überblick Die große Verbreitung des Internet und vor allem die Entwicklungen im World Wide Web (WWW) üben maßgeblichen Einfluss auf die Technologie der verteilten Anwendungen 9.7 Verteilte Anwendungen im World Wide Web 253 aus. Bei den so genannten Web-Anwendungen handelt es sich jedoch nicht um neue Erfindungen, sondern um die Kombination von Technologien, die im Umfeld des WWW und der verteilten Anwendungen entstanden sind. Das Bild 9.12 gibt einen Überblick über die Möglichkeiten der Erstellung von Web-Anwendungen. Die auf dem Hypertext Transfer Protocol (HTTP) beruhende Kommunikation zwischen Web-Browser und Web-Server kann die verteilte Anwendung vollständig unterstützen. Da HTTP als einfaches und zustandsloses Anwendungsprotokoll lediglich für die Übertragung von Dokumenten über das Internet konzipiert wurde, eignet es sich nicht sonderlich gut für die Implementierung komplexer Applikationen. Deshalb sind im Bild 9.12 abgesetzte Client/Server-Instanzen angedeutet, die unter anderem über Remote Method Invocation (RMI) und über das Internet Inter Orb Protocol (IIOP) kommunizieren. Der Client kann eine eigenständige Applikation sein oder unter der Kontrolle des Web-Browser, zum Beispiel als Applet ablaufen. Der Server besitzt ebenfalls Eigenständigkeit bzw. wird vom Web-Server kontrolliert. Es besteht aber auch die Möglichkeit, dass sich der Server in eine komplexere Umgebung, dem so genannten Application Server, integriert. Die Java 2 Enterprise Edition (J2EE) spezifiziert zum Beispiel eine solche Umgebung. Web-Browser und Web-Server öffnen in diesem Fall ein Portal zu den verteilten Anwendungen. Web-Browser WebAnwendung Portal Web-Server CGI JavaScript JScript VBScript Fast CGI Sockets RPC RMI PHP HTML Formulare HTTP JSP Applets Servlets ActiceX Controls ASP Client DCE CORBA COM JavaBeans ... RMI IIOP HTTP API EJB XML-RPC SOAP Server .NET Web-Anwendungen wurden durch Erweiterungen an Web-Browser und Web-Server Realität. Zunächst ermöglichen in die Hypertext Markup Language (HTML) eingebettete Formulare und Skripte, die vom Browser interpretiert werden, eine erweiterte Interaktion mit dem Benutzer. Applets sind in Java geschriebene Programme, die unter der Kontrolle des Browser ablaufen. Für die Applets, die vom Web-Server geladen werden können, stellt der Browser eine definierte Umgebung, die so genannte Sandbox, mit sicherheitsrelevanten Einschränkungen bereit. Im Umfeld von Microsoft Windows existieren Browser, die auf dem Component Object Model (COM) basierende ActiveX Controls integrieren. Auf Seiten des Web-Server gibt es eine ganze Reihe von Erweiterungen. Das Common Gateway Interface (CGI) spezifiziert eine Schnittstelle zum Aufruf externer Programme durch den Web-Server und zur Übergabe von Parametern über Umgebungsvariablen. Fast CGI stellt eine Optimierung beim Umgang mit den extern erzeugten Prozessen dar. Hypertext Preprocessor (PHP), Microsofts Active Server Pages (ASP) und Java Server Pages (JSP) arbeiten nach dem Prinzip der in HTML eingebetteten Skripte, die vom Web-Server interpretiert werden. PHP ist eine an Perl und C angelehnte Skriptsprache. Die ASP-Programmierung erfolgt vorzugsweise in JScript oder VBScript, wobei dem Entwickler in einer Microsoft-Windows-Umgebung Bild 9.12 Verteilte Anwendungen im Web Formular Skript Applet CGI Fast CGI PHP ASP 254 9 Verteilte Anwendungen JSP Servlet API die gesamte Mächtigkeit des Component Object Model (COM) zur Verfügung steht. Der bei JSP in eine HTML-Seite eingebettete Java-Code wird bei seinem ersten Aufruf in ein Java Servlet übersetzt. Der Programmierer kann jedoch auch direkt Java Servlets schreiben, für die ein Web-Server Laufzeitumgebung und Ausführungskontext in Form eines Servlet-Container bereitstellen muss. Weniger durchgesetzt hat sich die serverspezifische API, da die hiermit implementierten Web-Anwendungen nicht portabel für unterschiedliche Web-Server sind. Die Erweiterungen von Web-Browser und vor allem Web-Server greifen auf die für verteilte Anwendungen geschaffenen Programmierwerkzeuge und -umgebungen zurück. Einige grundlegende Technologien, Sockets, RPC, DCE, CORBA und COM, wurden im Rahmen des 9. Kapitels bereits vermittelt. Weitere Aspekte verteilter Anwendungen im Umfeld des World Wide Web sollen im Folgenden kurz erläutert werden. Java Sockets Java Socket Sockets gibt es auch für die Programmiersprache Java. Java Sockets bieten eine Programmierschnittstelle zur Implementierung verteilter Anwendungen, aufsetzend auf den Dienst der Transportschicht mit Hilfe von Klassen aus dem Package java.net. Besonders hervorzuheben ist hier das Framework für Uniform Ressource Locators (URL-Framework), das den Umgang mit Anwendungsprotokollen aus dem Internet unterstützt. So existieren bereits einige Protokoll-Handler, zum Beispiel für FTP und HTTP. Das URL-Framework kann jedoch auch durch eigene Klassen erweitert werden, um beispielsweise einen vorhandenen Protokoll-Handler zu modifizieren oder ein eigenes Anwendungsprotokoll einzufügen. RMI Remote Method Invocation Remote Interface Mit der Java-Technologie hat sich auch Remote Method Invocation (RMI) entwickelt. RMI und Remote Procedure Call (RPC) sind sich in der Zielstellung, im Ablauf und im Entwicklungsprozess sehr ähnlich. Bei RMI ruft ein Client-Objekt eine Methode auf, die ein Server-Objekt auf einem entfernten System bereitstellt. Die Methoden des entfernten Objekts werden durch ein Remote Interface in Java beschrieben. Das Beispiel 9.20 zeigt eine mögliche Beschreibung der einfachen Arithmetik-Schnittstelle für RMI in der Datei Arithmetic.java. Beispiel 9.20 public interface Arithmetic extends java.rmi.Remote { final int ARRAY_SIZE = 5; int[] sumArrays(int[] inArray1, int[] inArray2) throws java.rmi.RemoteException; } Eine über RMI erreichbare, entfernte Schnittstelle erweitert immer das Interface java.rmi.Remote. Die Methoden eines Remote Interface müssen im Ausnahmefall die java.rmi.RemoteException generieren. Nachdem der Programmierer die Implementierung des Arithmetik-Server übersetzt hat, generiert er mit Hilfe eines RMI-Compilers die für die Netzwerkkommunikation zuständigen Stub und Skeleton aus der Implementierungsklasse. Die Aufgabe 9.28 vertieft dieses Vorgehen. 9.7 Verteilte Anwendungen im World Wide Web 255 .NET Microsofts .NET-Strategie zielt auf eine Infrastruktur kommunizierender Dienste mit einheitlichem Protokoll und objektorientierter Schnittstelle zum Betriebssystem. Angebotene Dienste sind zum Beispiel die Feststellung der Identität eines Benutzers, der Nachrichtenaustausch zwischen Benutzern, die Personalisierung von Umgebungen und dynamische Aktualisierungen der Software. Eine neue Sprache, C#, vereinigt Konzepte anderer Programmiersprachen, wie Objektorientierung, Garbage Collection, Versionierung, Namensräume, Einfach-Vererbung, Ausnahmebehandlung. Auch die alten APIs, Win32, COM, COM+, sollen weiterhin Unterstützung finden. Der Programmierer kann in einer ihm genehmen Sprache entwickeln. Seine Programme werden in einen Prozessorunabhängigen Zielcode, Intermediate Language, übersetzt, der dann durch einen Just-InTime-Compiler in native Code des Zielsystems umzusetzen ist. Die Kommunikation in einer .NET-Umgebung findet auf der Basis des Simple Object Access Protocol (SOAP) statt. Die Übertragung der Daten und Objekte erfolgt in Form von XML-Dokumenten. Im Abschnitt 9.7.3 wird auf SOAP und XML-RPC näher eingegangen. Zu erwartende und bereits verfügbare Produkte von Seiten Microsoft sind Visual Studio.NET, Office.NET, Windows.NET-Server. .Net SOAP CORBA im World Wide Web Die Weiterentwicklungen mit CORBA 3 betreffen drei Schwerpunkte – die Integration in das Internet, Maßnahmen zur Sicherung von Quality of Service (QOS) und eine Architektur für Softwarekomponenten, CORBAcomponents. Die Internet-Integration betrifft die Sicherheit, durch eine Spezifikation für Firewalls, das Internet Inter Orb Protocol (IIOP) über Secure Socket Layer (SSL) und SOCKS sowie Restriktionen für Rückrufe vom Server zum Client. Außerdem wird ein interoperabler Namensdienst definiert, der die Ermittlung von Objektreferenzen im URL-Format ermöglicht, wie zum Beispiel: iioploc://www.tu-ilmenau.de/NameService. Die Erweiterungen im QOSBereich beziehen sich auf den asynchronen Nachrichtenaustausch, die Kontrolle von QOS und eine verbesserte Fehlertoleranz. So unterstützt CORBA 3 beispielsweise das Polling, die Steuerung von Prioritäten und Redundanzen. Für Echtzeitsysteme existiert ein Real-time-CORBA und für eingebettete Systeme ein Minimum-CORBA. Zur Implementierung von Softwarekomponenten auf der Seite des Servers spezifiziert CORBA 3 ein Komponentenmodell. Dieses umfasst einen Container auf Basis des Portable Object Adapter (POA), neue IDL-Konstrukte für die Laufzeitumgebung von Komponenten und weitere Modifikationen der IDL-Grammatik. CORBAcomponents beinhaltet ein Meta- und Programmiermodell, Festlegungen zur Persistenz von Komponenten und eine Möglichkeit, Komponenten als in XML beschriebene Softwarepakete einem Prozess des Packaging und Deployment zu unterwerfen. Die Technologie von Komponentenmodellen wird am Beispiel der Enterprise JavaBeans (EJB), für die auch eine Abbildungsvorschrift in CORBA 3 existiert, im Abschnitt 9.7.2 näher erläutert. CORBA 3 www EJB, XML-RPC und SOAP sind besonders durch die Verbreitung des Internet und die Entwicklungen im World Wide Web geprägt und sollen deshalb im Folgenden näher vorgestellt werden. 9.7.2 EJB Enterprise JavaBeans (EJB) sind ein grundlegender Bestandteil der Java 2 Enterprise Edition (J2EE) und definieren eine Komponentenarchitektur für verteilte objektorientierte Anwendungen. Sie unterscheiden sich von den JavaBeans unter anderem durch den EJB 256 9 Verteilte Anwendungen Softwarekomponente Session Bean Entity Bean Messagedriven Bean Prozess des Deployment. JavaBeans stellen eher benutzer- und oberflächenorientierte Komponenten dar, zum Beispiel für Applets und Servlets. Die Spezifikationen von EJB und JavaBeans verstehen unter einer Softwarekomponente den Programm-Code, der eine Menge wohl definierter Schnittstellen implementiert, einen verwaltbaren, diskreten Teil von Applikationslogik darstellt und eine Laufzeitumgebung, den Container, benötigt. EJB benennt drei Arten von Beans, Session Beans, Entity Beans und Message-driven Beans. Session Beans repräsentieren die Applikationslogik und können zustandsorientiert und zustandslos ausgelegt sein. Die persistenten Daten, beispielsweise aus Datenbanken, werden auf Entity Beans abgebildet. Das Management der Persistenz kann über ein Bean selbst oder über den Container erfolgen. Message-driven Beans dienen der Integration des Messaging Service in EJB. Sie sind über eine Nachrichtenwarteschlange (Point-to-Point) oder über die Zuordnung zu bestimmten Themen (Publish/Subscribe) ansprechbar. Der Enterprise JavaBeans Container stellt, wie im Bild 9.13 gezeigt, den Kontext und die Laufzeitumgebung für die Enterprise JavaBeans bereit. Das Bild 9.13 verdeutlicht den Aufbau der Session Beans und Entity Beans sowie den Laufzeitprozess zur Bearbeitung einer Client-Anforderung. Home Interface (1) new EJB Object Client Code EJB Container Home Object Database (3) Object Reference (2) create() (7) Return (5) Bild 9.13 EJB-Container und der Laufzeitprozess Home Object Home Interface (4) Method Call EJB Object Remote Interface Enterprise Beans (6) Der Programmierer muss ein Home Interface, ein Remote Interface und die Implementierung des Enterprise Bean erstellen. Die durch den Container generierte Implementierung des Home Interface, das Home Object, arbeitet als Factory für Enterprise Beans und generiert diese auf Anforderung eines Clients. Das Home Interface im Beispiel 9.21 beschreibt eine mögliche create()-Methode für ein einfaches Session Bean zur Realisierung der Arithmetik-Anwendung. Beispiel 9.21 import import import import java.io.Serializable; java.rmi.RemoteException; javax.ejb.CreateException; javax.ejb.EJBHome; public interface ArithmeticHome extends EJBHome { Arithmetic create() throws RemoteException, CreateException; } Remote Interface EJB Object Das Remote Interface definiert die öffentlichen Methoden des Enterprise Bean. Die vom Container generierte Implementierung des Remote Interface, das EJB Object, enthält die Netzwerkfunktionalität und delegiert die Methodenaufrufe zu den Bean-Instanzen. Das Beispiel 9.22 zeigt ein mögliches Remote Interface für die Arithmetik-Anwendung. 9.7 Verteilte Anwendungen im World Wide Web Beispiel 9.22 import javax.ejb.EJBObject; import java.rmi.RemoteException; public interface Arithmetic extends EJBObject { final int ARRAY_SIZE = 5; public int[] sumArrays(int[] inArray1, int[] inArray2) throws RemoteException;} Die Implementierung des Enterprise Bean enthält die im Remote Interface definierten Methoden. Für die Arithmetik-Anwendung soll ein zustandsloses Session Bean, wie im Beispiel 9.23 angegeben, kreiert werden. Beispiel 9.23 import java.rmi.RemoteException; import javax.ejb.SessionBean; import javax.ejb.SessionContext; public class ArithmeticBean implements SessionBean { final int ARRAY_SIZE = 5; public int[] sumArrays(int[] inArray1, int[] inArray2) { int[] outArray = new int[ARRAY_SIZE]; int i; for (i = 0; i < ARRAY_SIZE; i++) { outArray[i] = inArray1[i] + inArray2[i]; } return (outArray); } public public public public public public ArithmeticBean() {} void ejbCreate() {} void ejbRemove() {} void ejbActivate() {} void ejbPassivate() {} void setSessionContext(SessionContext sc) {} } Die Methoden ejbCreate(), ejbRemove(), ejbActivate(), ejbPassivate(), setSessionContext() müssen mit dem Interface javax.ejb.SessionBean implementiert werden. Der Container ruft diese als Call Back im Zusammenhang mit der Kontrolle des Lebenszyklus bzw. zur Verwaltung des Kontexts eines Session Bean auf. Sie besitzen für die einfache Arithmetik-Anwendung jedoch keine Bedeutung und bleiben deshalb ohne Funktionalität. Im Falle eines Entity Bean kann zu den Aufgaben des Programmierers eine Java-Klasse für den Primary Key zur eindeutigen Identifikation eines Beans hinzukommen. Nach dem Übersetzen werden, wie im Bild 9.14 dargestellt, alle beteiligten Klassen zu einem Java-Archiv zusammengefasst. 257 258 9 Verteilte Anwendungen Web Component Enterprise Bean Client DD Java Classes EJB DD EJB Classes Remote Interface Home Interface Web Comp. DD JSP Files Servlet Classes GIF/JPEG Files HTML Files .jar .jar .war J2EE Client Assembly J2EE Appl. Assembly Deployment Stub Class J2EE Appl. DD .jar .ear Deployment Bild 9.14 EJB-Entwicklungsprozess Deployment Descriptor Stub Classes J2EE Server Für eine komplette Applikation besteht die Möglichkeit, Ressourcen für den Client und die Web-Funktionalitäten hinzuzufügen. Diese Aufgabe übernehmen Werkzeuge, die Bestandteil der jeweiligen J2EE-Umgebung sind. Die Werkzeuge generieren auch den Deployment Descriptor (DD). Der Deployment Descriptor spezifiziert, in XML codiert, die Anforderungen des Enterprise Bean an die Middleware und informiert den Container über die Einstellungen für das Bean bezüglich Verwaltung, Lebenszyklus, Transaktionen, Persistenz und Sicherheit. Im Anschluss an das Zusammenfügen (Assembly) der J2EEApplikation erfolgt die Bereitstellung (Deployment) beim J2EE-Server ebenfalls mit Hilfe von entsprechenden Werkzeugen. Im Ergebnis dieses Prozesses können durch den Container generierte Klassen für die netzwerktransparente Kommunikation des Client, Stub Classes, angefordert werden. Die Aufgabe 9.29 vertieft das Vorgehen bei der Implementierung von Enterprise JavaBeans und weiterführende Informationen zu J2EE und EJB findet man zum Beispiel in /9.9/. 9.7.3 XML-RPC XML-RPC und SOAP Der XML-RPC soll es Programmen auf unterschiedlichen Rechnern, mit unterschiedlichen Betriebssystemen und Programmiersprachen gestatten, durch Remote Procedure Call (RPC) über das Internet zu kommunizieren. Die Spezifikation des XML-RPC liegt in den Händen der Firma UserLand Software, Inc. und ist unter www.xmlrpc.com abrufbar. XML-RPC benutzt das Hypertext Transfer Protocol (HTTP) für den Transport der in der Extensible Markup Language (XML) codierten Daten. Eine Client-Anforderung wird in einen HTTP-POST-Request verpackt, der als Entität eine in XML codierte Struktur <methodCall> mit dem Namen der entfernten Operation <methodName> und allen Parametern <params> enthält. Die Antwort des XML-RPC-Servers besteht aus einem HTTP-Response mit einer Struktur <methodResponse>, die sich entweder aus den Ergebnisparametern <params> oder einer Fehlerstruktur <fault>, zusammensetzt. Erlaubte Datentypen sind <int>, <boolean>, <string>, <double>, <dateTime.iso8601>, <base64>, <struct> und <array>. Diese müssen vom XML-Parser in die jeweilige Zielsprache übersetzt werden. Implementierungen des XML-RPC existieren für zahlreiche Plattformen und Programmiersprachen. Zur 9.7 Verteilte Anwendungen im World Wide Web Umsetzung der einfachen Arithmetik-Anwendung wird im Beispiel 9.24 ein Server auf der Basis des Apache XML-RPC in Java entwickelt. Beispiel 9.24 import java.io.*; import org.apache.xmlrpc.*; public class ArithmeticServer { public static void main(String[] args) { try { WebServer webserver = new WebServer (8555); webserver.addHandler (“Arithmetic”, new ArithmeticHandler()); } catch (IOException e) { System.out.println(e.getMessage()); } } } Zur Distribution des Apache XML-RPC gehört ein Web-Server, dessen Start im Beispiel 9.24 auf Port 8555 erfolgt. Die Methode addHandler() registriert ein Objekt, das die entfernten Methoden unter einem bestimmten Namen implementiert, in diesem Fall unter dem Namen Arithmetic. Die Aufgabe 9.30 befasst sich mit der Realisierung der Klasse ArithmeticHandler. Das Beispiel 9.25 demonstriert eine mögliche Implementierung des zugehörigen Arithmetik-Client. Beispiel 9.25 import java.io.*; import org.apache.xmlrpc.*; import java.util.Vector; public class ArithmeticClient { public static void main(String[] args) { final int ARRAY_SIZE = 5; Vector inArray1 = new Vector(); Vector inArray2 = new Vector(); for (int i = 0; i < ARRAY_SIZE; i++) { inArray1.add(i, new Integer(i)); inArray2.add(i, new Integer(9 - i)); } try { XmlRpcClient xmlrpc = new XmlRpcClient (“http://einstein:8555/”); Vector params = new Vector (); params.addElement (inArray1); params.addElement (inArray2); Vector outArray = (Vector) xmlrpc.execute ( “Arithmetic.sumArrays”, params); System.out.println(outArray); } catch (Exception e) { 259 260 9 Verteilte Anwendungen System.out.println(e.getMessage()); } } } Der Client generiert einen XML-RPC-Request mit dem Aufruf der Methode execute(). Hier werden der Name des Handler-Objekts und der Name der entfernten Methode nach dem Schema „HandlerName.MethodenName” sowie die Parameter, verpackt in ein Objekt der Klasse java.util.Vector, übergeben. Die Übertragung von java.util.Vector erfolgt beim XML-RPC über den Datentyp <array>. Nach dem Response wird das Ergebnis geparst und die entsprechenden Java-Objekte als Ergebnis zurückgegeben. SOAP XML-RPC soll einfach zu implementieren sein und ein leicht zu erweiterndes Format zur Verfügung stellen. Das Simple Object Access Protocol (SOAP) geht bei den Aspekten der Erweiterbarkeit und Flexibilität über XML-RPC hinaus. SOAP spezifiziert die Kommunikation verteilter Anwendungen in vier Teilen – Nachrichtenformat, Codierungsregeln, RPC und Transportbindungen. SOAP definiert ein Rahmenwerk zur Beschreibung von Nachrichten und der Verarbeitung ihres Inhalts. Die in XML codierten Nachrichten bestehen aus einem Umschlag (Envelope), optionalen Kopfinformationen (Header) und den Anwendungsdaten (Body). Die Codierungsregeln ermöglichen eine flexible Definition der Anwendungsdaten. SOAP trifft allgemeinere Festlegungen für den Remote Procedure Call (RPC). Der RPC-Mechanismus kann mit Hilfe der Codierungsregeln beschrieben werden und hängt von dem für den Transport benutzten Protokoll ab. Zum Transport der Nachrichten definiert SOAP so genannte Bindungen an das unterliegende Protokoll. Ähnlich dem XML-RPC enthält die Spezifikation für SOAP eine Bindung an das Hypertext Transfer Protocol (HTTP), wobei jedoch auch andere Protokolle möglich sind. Der Spezifikationsprozess für das Simple Object Access Protocol ist noch nicht abgeschlossen. Aktuelle Informationen zu SOAP findet man beim W3C, dem Standardisierungsgremium für das World Wide Web, unter www.w3c.org. 9.7.4 Übungen Aufgaben 9.28 Implementieren Sie Arithmetik-Server und Arithmetik-Client als verteilte Anwendung mit RMI! a) Informieren Sie sich über die in Ihrer Umgebung zur Verfügung stehenden Werkzeuge zur RMI-Programmierung und den Gebrauch der RMI-Registry! b) Implementieren Sie Arithmetik-Server und Arithmetik-Client im Modul ArithmeticImpl.java bzw. ArithmeticClient.java! Benutzen Sie den Bootstrap Naming Service (rmiregistry) von RMI zur Registrierung des Server und zum Binding des Client. Beachten Sie, dass Server und Client einen Security Manager installieren müssen und für den Start eine Datei mit einer Security Policy benötigen. c) Was sind die Prozesse, die zur Laufzeit von RMI stattfinden? Testen Sie die verteilte Anwendung mit und ohne Web-Server! d) Welche Gemeinsamkeiten und Unterschiede im Entwicklungsprozess erkennen Sie bei RMI, RPC und CORBA? 9.8 Literatur 9.29 Realisieren Sie die einfache Arithmetik-Anwendung als J2EE-konforme Applikation mit Hilfe von Enterprise JavaBeans! a) Wenn Sie keinen Application Server, wie zum Beispiel BEA WebLogic oder IBM WebSphere besitzen, installieren Sie bitte die Referenzimplementierung J2SDKEE von Sun Microsystems (java.sun.com). b) Informieren Sie sich über den Start und die Einrichtung des J2EE-Server sowie über die Werkzeuge für Assembly und Deployment! c) Orientieren Sie sich bei der Entwicklung des Enterprise Bean an den Beispielen 9.21, 9.22 und 9.23! d) Ergänzen Sie den im Folgenden vorgeschlagenen J2EE-Client für die einfache Arithmetik-Anwendung um den entfernten Methodenaufruf mit den entsprechenden Parametern und die Ausgabe des Ergebnisses. import javax.naming.Context; import javax.naming.InitialContext; import javax.rmi.PortableRemoteObject; public class ArithmeticClient { public static void main(String[] args) { try { Context initial = new InitialContext(); Object objref = initial.lookup (“java:comp/env/ejb/Arithmetic”); ArithmeticHome home = (ArithmeticHome)PortableRemoteObject.narrow (objref, ArithmeticHome.class); Arithmetic arithmRef = home.create(); } catch (Exception e) { System.err.println(“Caught an exception!”); e.printStackTrace(); } } } e) f) Übersetzen Sie die benötigten Klassen und führen Sie Assembly und Deployment mit den Werkzeugen Ihres Application Server durch! Testen Sie die verteilte Anwendung gemäß den Vorgaben Ihrer J2EE-Umgebung! 9.30 Entwickeln Sie eine Java-Klasse für das im Beispiel 9.24 aufgerufene Objekt ArithmeticHandler im Modul ArithmeticHandler.java! Das Objekt stellt eine Implementierung der Methode sumArrays() zur Verfügung. Beachten Sie, dass dieser Methode, wie im Beispiel 9.25 gezeigt, als Parameter zwei Objekte der Klasse java.util.Vector übergeben werden. Testen Sie die verteilte Anwendung mit Hilfe der Distribution Apache XML-RPC, verfügbar unter www.apache.org. 9.8 Literatur /9.1/ W. Richard Stevens: UNIX Network Programming. – Prentice Hall PTR, 1998 /9.2/ Open Software Foundation: X/Open DCE Remote Procedure Call. – X/Open CAE Specification, 1994 261 www 262 9 Verteilte Anwendungen /9.3/ Object Management Group: A Discussion of the Object Management Architecture. – 1997 /9.4/ Object Management Group: The Common Object Request Broker Architecture and Specification. Revision 2.2. – 1998 /9.5/ Object Management Group: CORBAservices: Common Object Services Specification. – 1997 /9.6/ Object Management Group: Common Facilities Architecture. – 1995 /9.7/ Object Management Group: CORBAtelecoms: Telecommunications Domain Specifications. Version 1.0. – 1998 /9.8/ Microsoft Corporation: The Component Object Model Specification. Draft Version 0.9. – 1995 /9.9/ Denninger, S.; Peters, I.: Enterprise JavaBeansTM 2.0. – Addison-Wesley, 2001