9Verteilte Anwendungen

Werbung
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
Herunterladen