Programmierung verteilter Anwendungen mit RPCFerngesteuertvon Peter Wächtler |
|
Erfunden wurden die remote procedure calls (RPC) von Sun Microsystems, und dies bereits in den Achtzigerjahren. Sie sind heute integraler Bestandteil von DCE, dem "Distributed Computing Environment" der Open Group (ehemals Open Software Foundation) - einem Industriestandard, der festlegt, mit welchen Mitteln verteilte Anwendungen realisiert werden und wie sie interagieren. Regen Gebrauch machen aber auch andere Hersteller von Netzwerksoftware, z.B. erfolgt die Anmeldung des NT-Client for Netware mit Hilfe der RPC.
Hauptaufgabe der RPC ist es, dem Anwendungsprogrammierer die fehlerträchtige Programmierung der Kommunikation zwischen Client und Server zu vereinfachen bzw. komplett abzunehmen. Durch den Mechanismus der RPC und Zuhilfenahme einer maschinenunabhängigen Datenrepräsentation (XDR bzw NDR) ergibt sich für den Anwendungsentwickler kein Unterschied zwischen dem lokalen Aufruf einer Funktion im gleichen Adressraum und dem netzwerktransparenten entfernten Aufruf einer Funktion auf einem anderen Rechner durch RPC.
Die wohl meist eingesetzte Transporthilfe für InterNet-Dienste (http, ftp, smtp) sind die BSD-Sockets. Diese stellen eine Voll-Duplex-Verbindung her - vergleichbar mit einer Streams-Pipe unter SysV. Voll-Duplex bedeutet die Möglichkeit des gleichzeitigen Sendens und Empfangens - wie beim Telefonieren. Normale Unix Pipes sind eine Einbahnstraße (simplex): der Datenfluß funktioniert nur in eine Richtung - wie bei einem Telefax. Der Vollständigkeit halber sei noch duplex erklärt: Senden und Empfangen sind möglich, aber nicht gleichzeitig - analog zum Sprechfunkverkehr. Client und Server verwenden ein internes Protokoll um miteinander zu kommunizieren. Für Lehrzwecke gut geeignet ist das Simple Mail Transfer Protokoll SMTP (siehe Abbl. 1):
| Abb.1: exemplarische Sitzung mit SMTP |
peewee@picklock:/home/peewee > telnet localhost 25 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. 220 picklock.fido.de Smail3.1.29.1 #1 ready at Fri, 2 Jan 98 13:28 MET help 250-The following SMTP commands are recognized: 250- 250- HELO hostname - startup and give your hostname 250- MAIL FROM:<sender-address> - start transaction from sender 250- RCPT TO:<recipient-address> - name recipient for message 250- VRFY <address> - verify deliverability of address 250- EXPN <address> - expand mailing list address 250- DATA - start text of mail message 250- RSET - reset state, drop transaction 250- NOOP - do nothing 250- DEBUG [level] - set debugging level, default 1 250- HELP - produce this help message 250- QUIT - close SMTP connection 250- 250-The normal sequence of events in sending a message is to state the 250-sender address with a MAIL FROM command, give the recipients with 250-as many RCPT TO commands as are required (one address per command) 250-and then to specify the mail message text after the DATA command. 250 Multiple messages may be specified. End the last one with a QUIT. VRFY peewee 250 peewee VRFY otto 550 otto ... not matched: unknown user quit 221 picklock.fido.de closing connection Connection closed by foreign host. |
Hier wird also jeweils ein Parser programmiert, der die ankommenden Eingaben zeilenweise untersucht und entsprechend reagiert. Üblich ist die Verwendung von lex und yacc (bzw. flex und bison), mit deren Hilfe man die Parser baut, die bei Erkennung von definierten Eingabemustern entsprechende Routinen anspringen. Diese Art der Programmierung ist stark an die bekannte Komandozeile (Shell) angelehnt - ich habe die Kommandos schließlich auch einfach eingetippt. Das dahinterliegende Muster ist aber immer ähnlich: es wird ein Befehl erteilt, der entweder eine Ausgabe, einen internen Statuswechsel oder eine Fehlermeldung erzeugt. Dieses "Programmiermodell" liegt auch den RPCs zu Grunde - mit einem Unterschied: der fehlerträchtige Parser entfällt !
Die sogenannten r-commands (rsh, rlogin, rcp ) von BSD haben nichts mit RPC zu tun - sie bedienen sich auch der BSD-Sockets. Die Authorisierung erfolgt entweder interaktiv und somit über die Dateien /etc/passwd bzw. /etc/shadow, über entsprechende Einträge in der ~/.rhosts oder auch über MIT-Kerberos (ab BSD 4.3-Reno) einem ticket-basiertem sicherem Authentisierungsverfahren. Weiterhin wird der Zugriff oft über den tcpd geregelt. Dieser sitzt zwischen inetd und dem angeforderten Service und überprüft anhand von Einträgen in /etc/hosts.deny und /etc/hosts.allow ob ein Zugriff erlaubt ist oder nicht. Da bei der interaktiven Authorisierung die Paßwörter im Klartext über das Netz geschickt werden, und IPv4 über keinerlei Verschlüsselungsmechanismen verfügt, ist das Ausspähen von Paßwörtern in einer solchen Umgebung extrem leicht - wie auch bei telnet, ftp und herkömmlichem pop3. Wer nicht gleich zu Kerberos greifen möchte hat mit ssh eine anerkannt sichere Alternative. Die Secure Shell [16] verschlüsselt den Datenstrom zwischen Client und Server. Ein Ausspähen von Paßwörtern (Schlüsseln) ist nahezu unmöglich. Dateien sollten mit scp übertragen werden, selbst X11-Sessions lassen sich mittels ssh verschlüsseln - ganz zu Schweigen von der Möglichkeit beliebige TCP/IP-Ports über ssh verschlüsseln zu lassen!
Ein verteiltes RPC-Programmsystem besteht aus folgenden Komponenten:
Der wohl bekannteste RPC-Dienst dürfte NFS (Network File System - ebenfalls von Sun) sein - daneben sind noch Dienste wie rstat, rusers und rwall häufig anzutreffen. Die Serverdienste setzen beim Start einen laufenden portmap vorraus, ohne den ein Client die augenblickliche Portnummer für diesen Dienst nicht ermitteln kann und mit einer Fehlermeldung abbricht - ein "beliebter" Fehler beim Aufsetzen von NFS (mount clntudp_create: RPC: Program not registered).
Ein RPC-Server oder Client beinhaltet den Anwendungscode, einen oder mehrere RPC-Rümpfe und die RPC-Laufzeitumgebung. Der Anwendungscode ist speziell für eine Anwendung vom Anwendungsprogrammierer entwickelt worden. Der Anwendungscode implementiert und ruft entfernte Prozeduren sowie evtl. weitere benötigte RPC-Laufzeitroutinen auf. Ein RPC-Rumpf ist ein schnittstellenabhängiges Modul, der die RPC Laufzeitumgebung nutzt um Argumente und Rückgabewerte auszutauschen. Server und Client haben sich ergänzende RPC Rümpfe für eine Schnittstelle, die sie selbst gemeinsam bilden. Die RPC-Laufzeitumgebung handhabt die Kommunikation für Anwendungen. Zusätzlich stellt sie eine Programmierschnittstelle (API) zur Verfügung, anhand derer sich Kommunikationsparameter einstellen, Informationen über Server manipulieren und weitere Aufgaben erledigen lassen - z.B. die entfernte Wartung von Servern oder Zugriff auf sicherheitsrelevante Informationen.
Beim Start des Clients kontaktiert dieser erstmal den portmap auf dem angegebenen Rechner (im Falle von rwall sogar jeden Rechner im Subnetz) auf Port 111 (siehe /etc/services) und erfragt die Portnummer für ein bestimmtes Programm. So liefert rpcinfo auf dem Server Informationen über registrierte RPC-Programme:
| Abb.3: Ausgabe von rpcinfo -p |
$ rpcinfo -p program vers proto port 100000 2 tcp 111 portmapper 100000 2 udp 111 portmapper 100002 2 udp 1025 rusersd 100002 3 udp 1025 rusersd 100008 1 udp 1027 walld 100005 1 udp 1016 mountd 100005 2 udp 1016 mountd 100005 1 tcp 1019 mountd 100005 2 tcp 1019 mountd 100003 2 udp 2049 nfs 100003 2 tcp 2049 nfs 150001 1 udp 663 pcnfsd 150001 2 udp 663 pcnfsd 150001 1 tcp 666 pcnfsd 150001 2 tcp 666 pcnfsd 788585389 1 udp 622 |
In Abhängigkeit von bereits laufenden Diensten sieht die Zuordnung zwischen Programmnummer und Portnummer anders aus. Analog zu den "assigned numbers" für die IP-Dienste (/etc/services ) sind die RPC-Programmnummern unterteilt und können bei Sun - bei allgemeinem Interesse - registriert werden . Die Nummern sind in Gruppen von 0x20000000 unterteilt:
| Tabelle 1: Aufteilung der RPC-Programmnummern | |
| 00000000 - 1fffffff | von Sun vergeben |
| 20000000 - 3fffffff | von Benutzern zu vergeben |
| 40000000 - 5fffffff | vorrübergehend - für Benutzerprogramme |
| 60000000 - 7fffffff | reserviert |
| 80000000 ... ffffffff | reserviert |
Im Laufe der Zeit haben die ursprünglichen "Sun-RPC" Erweiterungen erfahren. So sind für System V in Zusammenarbeit mit AT&T die TI (Transport Independant) RPC entwickelt worden, die das Transport Level Interface (TLI) von System V ausnutzen und somit in ihrer Wahl des Transportmittels flexibler sind - die ursprünglichen RPC-Programme setzen ihrerseits auf Sockets auf und müssen für ein neues Transportmittel neu übersetzt werden. Für DCE wurden die RPC-Services in den DCE-Namensdienst integriert bei dem sich die Server registrieren, damit der Client "von alleine" herausbekommt, welchen Rechner er zu kontaktieren hat. Dies geschieht hier über das Name Service Interface (NSI) innerhalb der Cell Directory Services (CDS).
DCE stellt Dienste und Werkzeuge zur Verfügung, die die Erstellung, Benutzung und Wartung von verteilten Anwendungen in heterogenen Umgebungen ermöglichen. Zu diesen Werkzeugen gehören DCE Threads, DCE Directory Services, DCE Distributed Time Service (DTS), DCE Security Service, DCE Distributed File Service (DFS) und als zugrundeliegendes Transportmittel die DCE-RPC. Der RPC-Dienst gliedert sich in Entwicklungswerkzeug (Sprache und Compiler) und Laufzeitumgebung (dced/rpcbind/portmapper sowie der Client-/Serverprogramme). Fast sämtliche Dienste von DCE bedienen sich des Mittlers RPC. Zur Programmierung existiert eine spezielle IDL (Interface Definition Language) und ein Compiler, der Rümpfe für Client und Server generiert.
Es sind also drei Abkömmlinge zu unterscheiden:
In Tabelle 2 ist der zeitliche Ablauf der einzelnen Abarbeitungsschritte in einer DCE-Umgebung dargestellt. Es fällt auf, daß der Aufruf bzw. die Ausführung der entfernten Prozedur nur einen Bruchteil der Arbeitsschritte ausmachen. Schritte 4 und 6 sind DCE-spezifisch. Bei den Sun-RPC, die ja transportabhängig sind, entfällt auch Schritt 1.
| Tabelle 2: Grundlegende Aufgaben einer RPC Applikation | |
|
Client Tasks |
Server Tasks |
|---|---|
| 1. Auswahl des Netzwerkprotokolls | |
| 2. RPC Schnittstelle registrieren | |
| 3. Registration von Endpunkten in endpoint map | |
| 4 . Bekanntgabe der RPC Schnittstelle im Namensraum | |
| 5. Auf Anrufe warten (listen) | |
| 6. Suche nach kompatiblen Server, der gesuchte Dienste anbietet | |
| 7. Aufruf der entfernten Prozedur | |
| 8. Bindung zum Server etablieren | |
| 9. Konvertierung der Eingangsargumente in network data | |
| 10. Übertragung der Argumente zum Server | |
| 11. Anruf erhalten | |
| 12 . Aufbereitung der Argumente in lokale Datenrepräsentation | |
| 13. Ermittlung und Aufruf der geforderten Prozedur | |
| 14. Ausführung der entfernten Prozedur | |
| 15. Konvertierung der Ausgangsargumente und Rückgabewerte in network data | |
| 16. Übertragung der Ergebnisse zum Client | |
| 17. Erhalt der Ergebnisse | |
| 18. Aufbereitung der Rückgabewerte in lokale Datenrepräsentation | |
| 19. Rückgabe der Ergebnisse und Kontrolle an aufrufenden Programmkode | |
Rpcgen ist der Compiler, der aus der RPC Programmier Sprache (RPCL - bei DCE: IDL) C-Code für Client und Server generiert. Mit Hilfe der RPCL läßt sich das "Protokoll" zwischen Client und Server festlegen, dessen Funktionalität in anderen Quellcodemodulen implementiert wird. Eine vollständige Beschreibung der RPCL ist unter [1] zu finden. Mittels der RPCL werden entfernte Funktionen und deren Parameter deklariert. Dabei sorgt der RPC-Compiler für die Kodierung der Daten in eine unabhängige Datenrepräsentation (XDR bzw NDR) und erzeugt ein Modul, das die Umsetzung implementiert. Die Funktionalität von rpcgen geht also über einen reinen Präprozessor hinaus - auch im Vergleich zu einem Präprozessor für Embedded SQL.

Anhand eines simplen RPC-Programms, das ein Verzeichnis eines entfernten Rechners anzeigt ( rsh host ls ) kann man sich schon einen guten Eindruck über die Vorgehensweise machen. Listing 1 zeigt die Datei xdir.x , die mit rpcgen übersetzt werden muss. Rpcgen generiert daraus einen Stub (Rumpf) für Server und Client (dir_svc.c und dir_clnt.c ) , die Header-Datei dir.h und ein Modul dir_xdr.c, das für die Verpackung der zu transferierenden Daten zuständig ist. Die Programmiererin muss dann noch die eigentliche Programmfunktion für den Server ( dir_proc.c in Listing 2 ) und den Client ( rls.c in Listing 3 ) implementieren.Übersetzt wird das Ganze wie folgt:
$ rpcgen dir.x $ cc -c dir_xdr.c $ cc rls.c dir_clnt.c dir_xdr.o -o rls $ cc dir_svc.c dir_proc.c dir_xdr.o -o dir_svc
Sind die Programmdateien dir_svc und rls auf zwei vernetzte Rechner kopiert worden, startet man den Server einfach mit
picklock:/\> dir_svc &
und kann von einem anderen Rechner den Inhalt von Verzeichnissen abrufen, z.B.:
jimmy:/\> rls picklock /tmp
| Listing 1: dir.x - RPCL Definition |
/* dir.x: Remote directory listing protocol (vereinfacht) */
const MAXNAMELEN = 255; /* maximale Dateinamenlaenge */
typedef string nametype<MAXNAMELEN>; /* ein Verzeichniseintrag*/
typedef struct namenode *namelist; /* Zeiger einer verketteten Liste */
/* Ein Eintrag im Verzeichnis */
struct namenode {
nametype name; /* Name des Eintrages */
namelist next; /* naechster Eintrag */
};
union readdir_res switch (int errno) {
case 0:
namelist list; /* kein Fehler: Rueckgabe der Eintragsliste */
default:
void; /* Fehlerzustand: keine weitere Rueckgabe */
};
/* Programm Definition */
program DIRPROG {
version DIRVERS {
readdir_res READDIR(nametype) = 1;
} = 1;
} = 0x20000076;
|
| Listing 2: dir_proc.c - Definition der entfernt aufzurufenden Funktion |
/* dir_proc.c: entfernte "readdir" Implementation */
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <dirent.h>
#include "dir.h" /* wird von rpcgen generiert */
readdir_res * readdir_1_svc(nametype * dirname, struct svc_req *req) {
DIR *dirp;
struct dirent *d;
namelist nl;
namelist *nlp;
static readdir_res res; /* muss statisch sein! */
dirp = opendir(*dirname); /* Verzeichnis oeffnen */
if (dirp != (DIR *)NULL) {
/* voriges Ergebnis freigegeben (sonst: memory leaks) */
xdr_free(xdr_readdir_res, &res);
nlp = &res.readdir_res_u.list;
res.errno = 0;
while (d = readdir(dirp)) {
nl = *nlp = (namenode *) malloc(sizeof(namenode));
if (nl == (namenode *) NULL) {
res.errno = EAGAIN;
break;
}
nl->name = strdup(d->d_name);
nlp = &nl->next;
}
*nlp = (namelist)NULL;
closedir(dirp);
} else
res.errno = errno;
return (&res);
}
|
| Listing 3: Definition des Clients |
/* rls.c: Remote directory listing client */
#include <stdio.h>
#include <errno.h>
#include "dir.h" /* wird von rpcgen generiert */
main(int argc, char *argv[]) {
CLIENT *clnt;
char *server;
char *dir;
readdir_res *result;
namelist nl;
if (argc != 3) {
fprintf(stderr, "usage: %s host directory\n",argv[0]);
exit(1);
}
server = argv[1];
dir = argv[2];
clnt = clnt_create(server, DIRPROG, DIRVERS, "tcp");
if (clnt == (CLIENT *)NULL) {
clnt_pcreateerror(server);
exit(1);
}
result = readdir_1(&dir, clnt);
if (result == (readdir_res *)NULL) {
clnt_perror(clnt, server); /* RPC schlug fehl */
exit(1);
}
if (result->errno != 0) {
errno = result->errno; /* Fehler beim Server */
perror(dir);
exit(1);
}
for (nl = result->readdir_res_u.list;nl != NULL; nl = nl->next) {
printf("%s\n", nl->name);
}
xdr_free(xdr_readdir_res, result);
clnt_destroy(clnt);
exit(0);
}
|
Eher zur Anschauung denn als praktische Programmiereinführung folgt eine Implementierung des obigen Beispiels mit dem DCE-RPC Laufzeitsystem. Für Linux ist (noch) keine vollständige Portierung von DCE verfügbar. Unter [3] erhält man aber eine teilweise Portierung des DCE-RPC-Subsystems. Hier funktioniert u.a. der Transport über UDP nicht. Mir ist es außerdem nicht gelungen ein endpoint mapping über den rpcd vorzunehmen. In diesem Falle kann man das Beispiel aber dennoch über eine explizit angegebene Bindung testen. Außerdem soll noch erwähnt werden, daß die Programme auch einfacher über eine automatische Bindung zueinander gefunden hätten, einen funktionierenden Name Service vorrausgesetzt ... leider auch (noch) nicht für Linux verfügbar. Die ganzen Aufrufe um eine Bindung herzustellen kann komplett vom Laufzeitsystem übernommen werden. Hat man das Paket von [3] erfolgreich installiert, kann das Ganze mit
$ make
übersetzt werden. Über die Datei dir.acl lassen sich weitere Attribute des Interface verändern - in diesem Beispiel wird nur angegeben, daß Fehler über die Variable status zurückgegeben werden. Um einen eindeutigen Bezeichner für das entwickelte Interface zu generieren, benutzt man das Werkzeug uuidgen, das den Part vor interface in Listing 5 erstellt. Der Compiler idl erzeugt aus dir.idl und dir.acf die Dateien dir.h, dir_sstub.c , dir_cstub.c und kann diese auch gleich übersetzen lassen.
Würde der rpcd korrekt funktionieren und bereitsseinen Dienst verrichten, reichte ein Aufruf
$ ./dir_server $ ./dir_client -s localhost /tmp
Leider muß ein explizites Binding angegeben werden. Dazu veranlaßt man den Server, sich nicht in der endpoint map einzutragen:
$ ./dir_server -e dir RPC Server Starting... BINDING: ncacn_ip_tcp:192.168.227.47[1206] BINDING: ncadg_ip_udp:192.168.227.47[1072]
Jetzt kann man den Client mit folgenden Informationen zur Bindung aufrufen:
$ ./dir_client -p ncacn_ip_tcp -s 192.168.227.47 -# 1206 /tmp
Und tatsächlich erhält man eine Liste der Dateien im Verzeichnis /tmp.
| Listing 4: DCE-Interface Attribut-Beschreibung dir.acf |
/* Simple (dir) RPC application*/
/* Die ACF-Datei veranlaßt die RPC Laufzeitumgebung Fehler
in der Kommunikation und auf Seite des
Servers ueber die status Variable anzuzeigen */
interface dir {
dir_readdir([comm_status] status);
}
|
| Listing 5: DCE-IDL Version dir.idl |
/*
uuidgen -io dir.idl
Simple (dir) RPC application
*/
[
uuid(f9d944b6-a536-11d1-99f1-000000301490),
version(1.0)
]
interface dir
{
const short MAXNAMELEN = 255;
typedef [string] char nametype[MAXNAMELEN];
typedef struct namenode_tag {
nametype name;
[ptr] struct namenode_tag *next;
} namenode;
typedef [ptr] namenode *namelist;
typedef union switch (long error) readdir_res_u {
case 0:
namelist list;
case 1:
[ptr] long *dummy;
} readdir_res;
void dir_readdir(
[in] handle_t h,
[in] nametype dirname,
[out] readdir_res **dir,
[out] error_status_t *status
);
}
|
| Listing 6: DCE-Version von dir_proc.c |
/* dir_proc.c: entfernte "readdir" Implementation */
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <dirent.h>
#include "dir.h" /* wird von idl generiert */
void dir_readdir(handle,dirname,dirlist,status)
handle_t handle;
nametype dirname;
readdir_res **dirlist;
error_status_t *status;
{
DIR *dirp;
struct dirent *d;
namelist nl;
namelist *nlp;
static readdir_res res; /* muss statisch sein! */
dirp = opendir(dirname); /* Verzeichnis oeffnen */
if (dirp != (DIR *)NULL) {
*dirlist=NULL;
nlp = &res.readdir_res_u.list;
res.error = 0;
while (d = readdir(dirp)) {
/* rpc runtime gibt den Speicher
nach der Uebertragung autom. frei */
nl = *nlp = (namenode *) rpc_ss_allocate(sizeof(namenode));
if (nl == (namenode *) NULL) {
res.error = EAGAIN;
break;
}
strncpy(nl->name,d->d_name,sizeof(nl->name));
nlp = &nl->next;
}
*nlp = (namelist)NULL;
closedir(dirp);
*dirlist=&res;
} else
res.error = errno;
return;
}
|
| Listing 7: DCE-Server Code dir_server.c |
/* Simple (dir) RPC application - server */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dce/rpc.h>
#include <dce/dce_error.h>
#include "dir.h"
void print_error(char *caller, error_status_t status) {
dce_error_string_t error_string;
int print_status;
dce_error_inq_text(status, error_string, &print_status);
fprintf(stderr, "%s: %s\n", caller, error_string);
}
void usage(char *name) {
printf("USAGE: %s [-d -f -e -h]\n"
"\t-d = Turn ON debugging\n"
"\t-f = Run in foreground\n"
"\t-e = Turn OFF endpoint mapping\n"
"\t-h = this help message\n",name);
exit(1);
}
int main(int argc, char **argv) {
unsigned32 status;
rpc_binding_vector_t *binding_vector;
int i, foreground = 0, debug = 0, register_endpoint = 1;
unsigned_char_t *string_binding;
extern int optind;
extern char *optarg;
while ((i=getopt(argc,argv,"dfeh"))!=EOF) {
switch (i){
case 'd': foreground++; debug++;break;
case 'f': foreground++;break;
case 'e': register_endpoint=0;break;
default : usage(argv[0]);
}
}
printf("dir RPC Server Starting...\n");
/* INTERFACE registrieren */
rpc_server_register_if(dir_v1_0_s_ifspec, NULL, NULL, &status);
if(status!=error_status_ok) {
print_error("server main() calling rpc_server_register_if()",
status);
/* return 1; */
}
/* Alle verfuegbaren Protokolle verwenden */
rpc_server_use_all_protseqs(rpc_c_protseq_max_reqs_default, &status);
if(status!=error_status_ok) {
print_error("server main() calling rpc_server_use_all_protseqs()\n",
status);
return 1;
}
/* Binding Handle erfragen: */
rpc_server_inq_bindings(&binding_vector, &status);
if(status!=error_status_ok) {
print_error("server main() calling rpc_server_inq_bindings()\n",
status);
return 1;
}
/* Beim Endpoint mapper registrieren, falls gewuenscht */
if(register_endpoint) {
rpc_ep_register(dir_v1_0_s_ifspec, binding_vector, NULL,
(unsigned_char_p_t)"dir Logging Server version 1.0",
&status);
if(status!=error_status_ok)
print_error("server main() calling rpc_ep_register()", status);
}
/* Normalerweise koennte man die Bindung an das CDS exportieren */
/* Bindung ausgeben */
for(i=0; i < binding_vector->count; i++) {
rpc_binding_to_string_binding(binding_vector->binding_h[i],
&string_binding, &status);
if(status!=error_status_ok)
print_error("server main() calling rpc_binding_to_string_binding()",
status);
printf("BINDING: %s\n", string_binding);
rpc_string_free(&string_binding, &status);
if(status!=error_status_ok)
print_error("server main() calling rpc_string_free()", status);
}
/* falls gewuenscht, ab in den Hintergrund */
if(!foreground) {
setpgrp();
if(fork()) exit(0);
}
/* Auf reinkommende Rufe warten */
rpc_server_listen(rpc_c_listen_max_calls_default, &status);
if(status!=error_status_ok) {
print_error("server main() calling rpc_server_listen()", status);
return 1;
}
printf("Server Shutting Down\n");
return 0;
}
|
| Listing 8: DCE-Client dir_client.c |
/* Simple (dir) RPC application - client */
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dce/rpc.h>
#include <dce/dce_error.h>
#include <dce/pthread_exc.h>
#include "dir.h"
void print_error(char *caller, error_status_t status) {
dce_error_string_t error_string;
int print_status;
dce_error_inq_text(status, error_string, &print_status);
fprintf(stderr, "%s: %s\n", caller, error_string);
}
void usage(char *name) {
printf("USAGE: %s [-p -s -# -h]\n"
"\t-p protocol_sequence\n"
"\t-s server_ip_address\n"
"\t-# port\n\t-h = this help message\n",name);
exit(1);
}
int main(int argc, char **argv) {
int i;
handle_t binding_handle;
unsigned_char_t *actual;
error_status_t status;
char *protocol = "ncacn_ip_tcp";
char *serverip = "192.168.227.47";
char *port = 0;
readdir_res *dirlist;
namelist nlp;
extern int optind;
extern char *optarg;
setbuf(stdout, NULL);
while ((i=getopt(argc,argv,"hp:s:#:"))!=EOF)
switch (i){
case 'p': protocol = optarg;break;
case 's': serverip = optarg;break;
case '#': port = optarg;break;
default : usage(argv[0]);
}
/* binding handle erzeugen */
rpc_string_binding_compose(NULL, protocol, serverip,
port, NULL, &actual, &status);
if(status!=error_status_ok) {
print_error("client main() calling rpc_string_binding_compose()\n",
status);
return 1;
}
printf("Composed Binding: %s\n", actual);
/* binding handle umwandeln: */
rpc_binding_from_string_binding(actual, &binding_handle, &status);
if(status!=error_status_ok) {
print_error("client main() calling rpc_binding_from_string_binding()",
status);
return 1;
}
/* string binding freigeben: */
rpc_string_free(&actual, &status);
if(status!=error_status_ok) {
print_error("client main() calling rpc_string_free()", status);
return 1;
}
/* endpoint suchen, falls kein port angegeben wurde: */
if(port==NULL) {
printf("Finding PORT in the Endpoint map\n");
rpc_ep_resolve_binding(binding_handle, dir_v1_0_c_ifspec, &status);
if(status!=error_status_ok) {
print_error("client main() calling rpc_ep_resolve_binding()",
status);
return 1;
}
/* in String zurueckwandeln, um endgueltige Bindung zu zeigen: */
rpc_binding_to_string_binding(binding_handle, &actual,&status);
if(status!=error_status_ok) {
print_error("client main() calling rpc_binding_to_string_binding()",
status);
return 1;
}
printf("Final Binding: %s\n", actual);
rpc_string_free(&actual, &status);
if(status!=error_status_ok)
print_error("client main() calling rpc_string_free()", status);
}
/* Verzeichniseintraege lesen */
while (optind < argc){
TRY
printf ("readdir: %s\n",argv[optind]);
dir_readdir(binding_handle,argv[optind],&dirlist,&status);
if(status!=error_status_ok)
print_error("client main() doing RPC call dir_readdir()",
status);
else {
printf ("\n");
nlp=(*dirlist).readdir_res_u.list;
while (nlp){
printf("%s\n",nlp->name);
nlp=nlp->next;
}
}
optind++;
CATCH_ALL {
printf("Caught Something!\n");
}
ENDTRY;
}
}
|
| Listing 9: Makefile für dir.idl |
# Makefile fuer dir -- DCE Example
#
CC = gcc
IFNAME = dir
INCDIR = /opt/dcelocal/include
INCFLAGS =-I. -I$(INCDIR)
IDLFLAGS =-keep c_source -I$(INCDIR) -cc_cmd "gcc \
-D_BSD -DLINUX -D_REENTRANT -c"
IDL = /opt/dcelocal/bin/idl
CFLAGS = $(INCFLAGS) -DLINUX -D_REENTRANT
LIBDIR = /opt/dcelocal/lib
LIBS = $(LIBDIR)/libnck.a \
$(LIBDIR)/libidl.a \
$(LIBDIR)/libpthreads-ext.a -lpthread -lm
SSTUB_SRC = ${IFNAME}_sstub.c
SSTUB = ${IFNAME}_sstub.o
CSTUB_SRC = ${IFNAME}_cstub.c
CSTUB = ${IFNAME}_cstub.o
all: dir_client dir_server
#---------------------------------------------------------------------
# CLIENT BUILD
#
dir_client: dir_client.c ${IFNAME}.h ${CSTUB} ${SSTUB}
@echo + building client program
$(CC) $(CFLAGS) dir_client.c dir_cstub.o -o dir_client $(LIBS)
#---------------------------------------------------------------------
# SERVER BUILD
#
dir_server: dir_server.c dir_proc.c ${IFNAME}.h ${CSTUB} ${SSTUB}
@echo + building server program
$(CC) $(CFLAGS) dir_server.c dir_proc.c dir_sstub.o -o dir_server $(LIBS)
#---------------------------------------------------------------------
# INTERFACE BUILD
#
${IFNAME}.h ${SSTUB_SRC} ${CSTUB_SRC}: ${IFNAME}.idl
@echo + generating interface header and stub sources
$(IDL) $(IDLFLAGS) ${IFNAME}.idl
clean:
rm -f dir_client dir_server *~
rm -f *.o *_?stub* ${IFNAME}.h
|
| Infos |
| [1] http://docs.sun.com Sun Microsystems Online Documentation |
| [2] http://www.transarc.com/afs/transarc.com/public/www/Public/Documentation/dce/1.1/index.html |
| [3] http://www.aa.net/~mtp/ DCE-RPC for Linux |
| [4] http://www.camb.opengroup.org/dce/ OpenGroup DCE |
| [5] http://www.camb.opengroup.org/dce/info/faq-mauney.html DCE FAQ |
| [6] UNIX Network Programming, W. Richard Stevens (Prentice Hall Software Series, 1990) |
| [7] Power Programming with RPC, John Bloomer (O'Reilly & Associates, Inc, 1992) |
| [8] RFC-1014 XDR: External Data Representation Standard |
| [9] RFC-1050 RPC: Remote Procedure Call Protocol Specification |
| [10] RFC-1057 RPC: Remote Procedure Call Protocol specification version 2 (obsoletes RFC1050) |
| [11] RFC-1790 An Agreement between the Internet Society and Sun Microsystems, Inc. in the Matter of ONC RPC and XDR Protocols |
| [12] RFC-1831 RPC: Remote Procedure Call Protocol SpecificationVersion 2 |
| [13] RFC-1832 XDR: External Data Representation Standard |
| [14] RFC-1833 Binding Protocols for ONC RPC Version 2 |
| [15] ftp://ftp.gcom.com/pub/linux/src/streams-1-15-98 SystemV Streams Implementation für Linux |
| [16] http://www.cs.hut.fi/ssh Secure Shell - verschlüsselte Verbindungen |
| Glossar |
| DCE: Distributed Computing Environment - ein Industriestandard der Open Group, der festlegt, mit welchen Mitteln und Methoden verteilte Anwendungen zu implementieren sind |
| NDR: Network Data Representation - hardwareunabhängige Datenbeschreibung; verwendet von DCE |
| OSF: Open Software Foundation - Vorgänger Konsortium der Open Group |
| RPC: Remote Procedure Call - Mechanismus zum Aufruf entfernter Prozeduren in einem anderen Adreßraum |
| Sun-RPC: siehe TS-RPC |
| TI-RPC: Transport Independent RPC - nicht auf einen bestimmten Transportdient angewiesen, setzt auf Transport-Level-Interface (TLI) von SystemV auf; auch ONC-RPC genannt: Open Network Computing (Sun Terminus) |
| TS-RPC: Transport Specific RPC - direkte Kopplung an TCP/IP als Transportmittel; auch Sun-RPC genannt |
| XDR: External Data Representation: hardwareunabhängige Datenbeschreibung; verwendet bei TS- und TI-RPC |
| Der Autor |
|
Peter Wächtler arbeitet als Software-Entwickler bei L.I.Consulting, Hildesheim. Er entwickelt dort kundenspezifische Lösungen im Client/Server-Umfeld unter Unix und MS-Windows/Delphi. Der "erste Kontakt" mit Linux fand vor mehr als 4 Jahren mit 0.99pl12 statt. |
Copyright © 1998 Linux-Magazin Verlag