Inhalt

8. Der RPC-Generator rpcgen

8.1 Programmerzeugung

Die XDR/RPC-Programmierung beinhaltet oft Editiertätigkeit, die teilweise automatisiert werden kann. Die Erzeugung von C-Code aufgrund einer etwas allgemeinerer Beschreibung in einer speziell dazu geschaffenen C-ähnlichen Sprache RPC-Sprache (RPCL) kann rpcgen übernehmen. Es folgt eine Diagrammdarstellung, welche Files aus welchen generiert werden können. Die RPCL-Files haben die Extension ».x« .

     ---------------           ---------------
     | transf.data |           | XDR-filter  | --
     | structure   | --------> |             |  |
     | definitions |  rpcgen   | data_xdr.c  | ------
     |  data.x     |           ---------------  |   |
     ---------------           -------------    |   |
           .                   | remote    |    |   |
           .                   | procedure | -------|
           .                   |  proc.c   |    |   |        --------------
           .                   -------------    |   |        | Server     |
           .                   ---------------  |   |------> | executable |
           .           ------> | server stub |  |   |  cc    |            |
           .           |       | file_svc.c  | -----|        --------------
     ---------------   |       ---------------  |   |
     | remote proc.| ---       ----------       |   |
     | interface   | --------> | header | -----------
     | definition  | ---       | file.h | ------|
     |  file.x     |   |       ----------       |
     ---------------   |       ---------------  |
                       ------> | client stub | -|            --------------
                      rpcgen   | file_clnt.c |  |            | Client     |
                               ---------------  |----------> | executable |
                               ------------     |      cc    |            |
                               | client   |     |            --------------
                               | program  | -----
                               | client.c |
                               ------------
     Bild 15

Für rpcgen gibt es zwei Quellen in RPCL: data.x und file.x, die auch in einer Datei zusammengefaßt werden können, wie es durch die Punkte angedeutet ist. Data.x enthält struct-Definitionen für die Parameterübergabe beim Aufruf der entfernten Prozedur. Aus diesen Definitionen werden XDR-Filter erzeugt. Falls die primitive Filter ausreichen, kann dieser Teil ganz entfallen. File.x enthält Definition der Schnittstelle zu der entfernten Prozedur. Aus ihr werden Server- und Client-Stubs erzeugt. Zusätzlich wird ein Headerfile mit gemeinsamen #define-Konstanten generiert, das in den Stubs included ist. Die entfernte Prozedur und das Client-Programm, das sie aufruft, muß der Benutzer natürlich selbst schreiben. Die so gelieferten C-Files werden zu dem Client und dem Server-Programm übersetzt und zusammengebunden. Oft ist es sinnvoll den rpcgen-Aufruf im Makefile zu halten, nur die rpcgen-Quellen zu editieren und die generierten C-Files nicht mehr zu ändern.

Einige zusätzlich zu beachtende Details werden an den Beispielen erläutert.

        /* msg.x: remote procedure interface definition */
        program MESSAGEPROG {
            version MESSAGEVERS {
                int PRINTMESSAGE (string) = 1;
            } = 1;
        } = 0x20000099;
Der obige Text im File msg.x kann als Schnittstellenbeschreibung für den Input von rpcgen dienen. Damit werden die üblichen Angaben über die Programmnummer, Prozedurnummer und Prozedurversion gemacht: in unserem Fall entsprechend 0x20000099, 1 und 1, sowie Angaben über Inputparameter (String) und Returnwert der Prozedur (int). Der Name der Prozedur, hier PRINTMESSAGE, wird in dem C-File zu printmessage_1() geändert. Die Versionsnummer (hier »_1«) wird immer angehängt. Die Konvention der Schreibweise mit Großbuchstaben erleichtert diese Unterscheidung. Entsprechend muß auch die Prozedur im Client aufgerufen werden. Das Client-Programm muß vom Benutzer geliefert werden:


        #include "msg.h"

        main ()
        {
            CLIENT  *cl;
            int     *result;
            char    *text = "hello";
            .
            .
            cl = clnt_create (servername, MESSAGEPROG, MESSAGEVERS, "tcp");
            .
            .
            result = printmessage_1 (&text, cl);
            .
            .
        }

Es wird auf einige Merkmale hingewiesen:

  1. Der Client-Handle cl muß explicit erzeugt werden (clnt_create-Aufruf).
  2. printmessage_1 braucht noch cl als zweiten Parameter.
  3. printmessage_1 nimmt als Parameter nur einen Zeiger auf die Daten und gibt ebenfalls einen Zeiger zurück.

Der letzte Punkt ist die Konsequenz einer von XDR bekannten Regel: Die Filter nehmen als Parameter nicht die Daten selbst, sondern Adressen, von wo sie sie abholen, bzw. wo sie sie hinschreiben. Die tatsächliche Grösse der transferierten Daten kann der Empfänger von vornherein ja noch nicht wissen.

Zusammen mit dem von rpcgen in clnt_msg.c gelieferten Client-Stub, sowie msg.h, kann der Client jetzt kompiliert und gebunden werden. Für den Server braucht man noch die entfernte Prozedur selbst, die so aussehen könnte:


        #include "msg.h"

        int    *printmessage_1 (text)
        char   **text
        {
                static int   result;
            .
            .
            .
            return (&result);
        }

Die Variable result ist static, weil sie auch nach dem Beenden der Prozedur printmessage_1() gültig sein muß. Nicht der Client, der sich in einem anderen Rechner befindet, sondern der Server-Stub braucht eine gültige Adresse, von wo er die Daten (hier ein int) abholt. Falls printmesage_1() keinen Wert liefert (return (NULL)), wird an den Client auch keine Antwort geschickt. Void-Prozeduren, die keine Ergebnisse liefern, sind vereinbarungsgemäß als void-Pointer deklariert ((void *)&variable muß nicht NULL sein). Natürlich läßt der Server-Stub in diesem Fall trotzdem eine Antwort als Bestätigung der abgeschlossenen Aufgabe aus, jedoch ohne Ergebnisdaten.

Mit dem C-Code für printmessage_1() kann der Server-Stub hergestellt werden.

8.2 Ein Erweitertes Beispiel

In einem komplexeren Beispiel haben die Parameter- und Ergebnisstypen keine primitiven XDR-Filter, so daß zu den geänderten rpcgen-Quellen noch die Definitionen dieser Typen hinzukommen:


        const   MAXLEN = 256;
        typedef string  msgtext<MAXLEN>;

        struct msglist {
            msgtext text;
            msglist *next;
        };

        /* result-typ   */
        union result switch (int errno) {
        case 0:
            msglist *list;
        default:
            void;
        }

        /* remote procedure interface defintion */
        program MESSAGEPROG {
            version MESSAGEVERS {
                result GETMESSAGE (void) = 1;
            } = 1;
        } = 0x20000098;

Beachten Sie, daß die Schlüsselworte »struct« und »union« vor den Typnamen in der PRINTMESSAGE-Prototyp-Zeile nicht mehr vorkommen. In Wirklichkeit wandelt rpcgen unions in structs um und fügt alle nötigen typedef-Statements hinzu.

Als Ergebnis liefert jetzt rpcgen auch das File msg_xdr.c mit den XDR-Filtern. Die entfernte Prozedur muß jetzt wie folgt geändert werden:


        #include "msg.h"
        #define MSGSZ   sizeof(struct msglist)

        result    *getmessage_1 (void)
        {
            static result            r = { 0 };
            register struct msglist  *l = r->list, **lp = &l;
            register msgtext         t;

            if (!r.errno)           /* free space from previous call */
                xdr_free (xdr_msglist, l);
            while (t = readtext ()) {
                l = *lp = (struct msglist *)malloc (MSGSZ);
                l->text = t;
                lp      = &l->next;
            }
            lp = NULL;
            if (!t) r.errno = errno;
            return (&r);
        }

Die nichttriviale Konstruktion der Message-Liste könnte auch ohne die Variable lp erfolgen, die lediglich dazu dient Adreßberechnungen zu sparen.

8.3 Die rpcgen-Optionen

Außer dem generierten C-Text werden von rpcgen die fünf folgenden #define-Konstanten angeboten:

                        RPC_HDR
                        RPC_XDR
                        RPC_SVC
                        RPC_CLNT
                        RPC_TBL
Durch die Zeile: #ifdef RPC_... im x-Source können sie dazu dienen C-Text einer .x-Quelle nur an den Server- oder nur an den Clientstub zu transferieren. Das %-Zeichen am Anfang der Zeile dient im x-Source als Kommentarzeichen. Der nachfolgende Text wird ohne %-Zeichen an die Ergebnisdateien weitergereicht. Dies kann sinnvoll dazu eingesetzt werden die generierten C-Files nach Möglichkeit nicht mehr zu editieren.

#define-Variablen können nach dem Muster von cc in der rpcgen-Aufrufzeile eingeschaltet werden, wie z.B.:

        rpcgen -DDEBUG proto.x
Mit der »-s« Option kann nur die Erzeugung der Server-Seite erreicht werden. Mit »-I« wird ein Code generiert, der inetd unterstützt (siehe hierzu $5.7). Mit »-K Zahl« kann die Länge des defaultmäßig zweiminütigen Wartens vor dem Ausstieg zum inetd verändert werden.

8.4 Dispatcher Tabellen

Die »-T« Option erzeugt Dispatchtabellen im .i-File. Der Tabelleneintrag hat folgende Struktur:

struct rpcgen_table {
     char        *(*proc)();  /* Service Routine                       */
     xdrproc_t   xdr_arg;     /* Zeiger auf den Input des XDR-Filters  */
     unsigned    len_arg;     /* Länge in Bytes des Input Argumentes   */
     xdrproc_t   xdr_res;     /* Zeiger auf den Output des XDR-Filters */
     unsigned    len_res;     /* Länge in Bytes des Output Argumentes  */
};
Mit solchen Tabellen kann man den Serverstub für viele Serviceroutinen gleichzeitig erzeugen. Um nicht auflößbare Linker-Referenzen zu vermeiden, werden die Einträge mit RPCGEN_ACTION initialisiert. Mit
        #define RPCGEN_ACTION(routine)  0
oder
        #define RPCGEN_ACTION(routine)  routine
kann der gleiche Sourcecode sowohl im Client als auch im Server verwendet werden.

8.5 Die Sprachbeschreibung

Die RPC-Sprache (RPCL) ist eine Erweiterung der XDR-Sprache (siehe XDR Protokoll - Spezifikation, Syntax) um folgende drei Elemente:

        program-def:
                "program" identifier "{"
                        version-def
                        version-def *
                "}" "=" constant ";"

        version-def:
                "version" identifier "{"
                        procedure-def
                        procedure-def *
                "}" "=" constant ";"

        procedure-def:
                type-specifier identifier "(" type-specifier ")"
                "=" constant ";"
sowie zusätzliche keywords: »program«, »version«.


Inhalt