# Compaq TCP/IP Services for OpenVMS
ONC RPC Programming

Previous | Contents | Index

# 1.6 External Data Representation (XDR)

RPC can handle arbitrary data structures, regardless of the byte order or structure layout convention on a particular system. It does this by converting them to a network standard called external data representation (XDR) before sending them over the network. XDR is a system-independent description and encoding of data that can communicate between diverse systems, such as a VAX, Sun workstation, IBM PC, or CRAY.

Converting from a particular system representation to XDR format is called serializing; the reverse process is deserializing.

# 1.7 Assigning Program Numbers

Program numbers are assigned in groups of 0x20000000 according to the following chart:

`0x00000000` --- `0x1fffffff` | Defined by Sun Microsystems
`0x20000000` --- `0x3fffffff` | Defined by user
`0x40000000` --- `0x5fffffff` | Transient
`0x60000000` --- `0x7fffffff` | Reserved
`0x80000000` --- `0x9fffffff` | Reserved
`0xa0000000` --- `0xbfffffff` | Reserved
`0xc0000000` --- `0xdfffffff` | Reserved
`0xe0000000` --- `0xffffffff` | Reserved

Sun Microsystems administers the first range of numbers, which should be identical for all ONC RPC users. An ONC RPC application for general use should have an assigned number in this first range. The second range of numbers is for specific, user-defined customer applications, and is primarily for debugging new programs. The third, called the Transient group, is reserved for applications that generate program numbers dynamically. The final groups are reserved for future use, and are not used.

To register a protocol specification, send a request by network mail to rpc@sun.com, or write to:

                   RPC Administrator
                   Sun Microsystems
                   2550 Garcia Ave.
                   Mountain View, CA 94043

Include a compilable RPCGEN .X file describing your protocol. You will then receive a unique program number. See Chapter 2 for more information about RPCGEN .X files.

# Chapter 2
Writing RPC Applications with the RPCGEN Protocol Compiler

# 2.1 The RPCGEN Protocol Compiler

The RPCGEN protocol compiler accepts a remote program interface definition written in RPC language, which is similar to C. It then produces C language output consisting of: client skeleton routines, server skeleton routines, XDR filter routines for both arguments and results, a header file that contains common definitions, and optionally, dispatch tables that the server uses to invoke routines that are based on authorization checks.

The client skeleton interface to the RPC library hides the network from the client program, and the server skeleton hides the network from the server procedures invoked by remote clients. You compile and link output files from RPCGEN as usual. The server code generated by RPCGEN supports INETd. You can start the server using INETd or at the command line.

You can write server procedures in any language that has system calling conventions. To get an executable server program, link the server procedure with the server skeleton from RPCGEN. To create an executable client program, write an ordinary main program that makes local procedure calls to the client skeletons, and link the program with the client skeleton from RPCGEN. If necessary, the RPCGEN options enable you to suppress skeleton generation and specify the transport to be used by the server skeleton.

The RPCGEN protocol compiler helps to reduce development time in the following ways:

- It greatly reduces network interface programming.
- It can mix low-level code with high-level code.
- For speed-critical applications, you can link customized high-level code with the RPCGEN output.
- You can use RPCGEN output as a starting point, and rewrite as necessary.

Refer to the RPCGEN command description at the end of this chapter for more information about programming applications that use remote procedure calls or for writing XDR routines that convert procedure arguments and results into their network format (or vice versa). For a discussion of RPC programming without RPCGEN, see Chapter 3.

# 2.2 Simple Example: Using RPCGEN to Generate Client and Server RPC Code

This section shows how to convert a simple routine ---one that prints messages to the system console on a single system (OPCOM on OpenVMS)---to an ONC RPC application that runs remotely over the network. To do this, the RPCGEN protocol compiler is used to generate client and server RPC code. Example 2-1 (see file SYS$COMMON:[SYSHLP.EXAMPLES.TCPIP.RPC]PRINTMSG.C) shows the routine before conversion.

Compile and run the program shown in the example (you will need OPER privileges):

    $ CC/DECC PRINTMSG
    $ LINK PRINTMSG
    $ MCR SYS$DISK:[]PRINTMSG "Red rubber ball"
    %%%%%%%%%%%  OPCOM  27-SEP-1995 14:39:22.59  %%%%%%%%%%%
    Message from user GEORGE on BOSTON
    Red rubber ball

    Message Delivered!
    $

If the `printmessage` procedure at the bottom of the `printmsg.c` program of Example 2-1 were converted into a remote procedure, you could call it from anywhere in the network, instead of only from the program where it is embedded. Before doing this, it is necessary to write a protocol specification in RPC language that describes the remote procedure, as shown in the next section.

Example 2-1 Printing a Remote Message Without ONC RPC

    /*
    ** printmsg.c: OpenVMS print a message on the console
    */
    #include <descrip.h>
    #include <opcdef.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    extern int SYS$SNDOPR(struct dsc$descriptor_s *, unsigned short);

    static int printmessage(char *);

    main(argc, argv)
        int   argc;
        char *argv[];
    {
        char *message;
        int exit();

        if (argc != 2) {
            fprintf(stderr, "usage: %s <message>\n", argv[0]);
            exit (1);
            }
        message = argv[1];

        if (!printmessage(message)) {
            fprintf(stderr,"%s: couldn't print your message\n", argv[0]);
            exit (1);
            }
        printf("Message Delivered!\n");
        exit (0);
        }

    /*
    ** Print a message to the console.  Return a Boolean indicating
    ** whether the message was actually printed.
    */
    static int
    printmessage(msg)
        char *msg;
    {
        struct dsc$descriptor_s desc;
        union {
            char  buffer[256]; /* Preallocate space for text */
            struct opcdef opc;
            } message;
        int status;

        /*
        ** Build the message request block.
        */
        message.opc.opc$b_ms_type   = OPC$_RQ_RQST;
        message.opc.opc$b_ms_target = OPC$M_NM_CENTRL;
        message.opc.opc$w_ms_status = 0;
        message.opc.opc$l_ms_rqstid = 0;
        strcpy((char *) &message.opc.opc$l_ms_text, msg);
        desc.dsc$a_pointer = (char *) &message.opc;
        desc.dsc$w_length  = (char *) &message.opc.opc$l_ms_text -
                             (char *) &message +
                             strlen((char *) &message.opc.opc$l_ms_text);
        /*
        ** Send the message to the console.
        */
        status = SYS$SNDOPR(&desc,        /* MSGBUF */
                             0);          /* CHAN */
        if (status & 1)
            return 1;
        return 0;
        }

## 2.2.1 RPC Protocol Specification File Describing Remote Procedure

To create the specification file, you must know all the input and output parameter types. In Example 2-1, the `printmessage` procedure takes a string as input, and returns an integer as output. Example 2-2 (see SYS$COMMON:[SYSHLP.EXAMPLES.TCPIP.RPC]MSG.X) is the RPC protocol specification file that describes the remote version of the `printmessage` procedure.

Remote procedures are part of remote programs, so Example 2-2 actually declares a remote program containing a single procedure, `PRINTMESSAGE` . By convention, all RPC services provide for a NULL procedure (procedure 0), normally used for pinging. The RPC protocol specification file in Example 2-2 declares the `PRINTMESSAGE` procedure to be in version 1 of the remote program. No NULL procedure (procedure 0) is necessary in the protocol definition because RPCGEN generates it automatically.

In RPC language, the convention (though not a requirement) is to make all declarations in uppercase characters. Notice that the argument type is `string` , not `char *` , because a `char *` in C is ambiguous. Programmers usually intend it to mean a null-terminated string of characters, but it could also be a pointer to a single character or to an array of characters. In RPC language, a null-terminated string is unambiguously of type `string` .

Example 2-2 RPC Protocol Specification File Simple Example

    /*
     * msg.x: Remote message printing protocol
     */
    program MESSAGEPROG {
         version MESSAGEVERS {
              int PRINTMESSAGE(string) = 1;
         } = 1;
    } = 0x20000099;

## 2.2.2 Implementing the Procedure Declared in the Protocol Specification

Example 2-3 (see SYS$COMMON:[SYSHLP.EXAMPLES.TCPIP.RPC]MSG_SERVER.C) defines the remote procedure declared in the RPC protocol specification file of the previous example.

Example 2-3 Remote Procedure Definition

    /*
    ** msg_server.c: OpenVMS implementation of the remote procedure
    ** "printmessage"
    */

    #include <descrip.h>  /* OpenVMS descriptor definitions */
    #include <opcdef.h>   /* OpenVMS $SNDOPR() definitions */
    #include <rpc/rpc.h>  /* always needed */ (1)
    #include "msg.h"      /* msg.h will be generated by RPCGEN */

    extern int SYS$SNDOPR(struct dsc$descriptor_s *, unsigned short);

    /*
    ** Remote version of "printmessage"
    */
    int *
    printmessage_1(msg) (2)
        char **msg; (3)
    {
        struct dsc$descriptor_s desc;
        union {
            char  buffer[256]; /* Preallocate space for text */
            struct opcdef opc;
            } message;
        static int result;
        int status;

        /*
        ** Build the message request block.
        */
        message.opc.opc$b_ms_type   = OPC$_RQ_RQST;
        message.opc.opc$b_ms_target = OPC$M_NM_CENTRL;
        message.opc.opc$w_ms_status = 0;
        message.opc.opc$l_ms_rqstid = 0;
        strcpy((char *) &message.opc.opc$l_ms_text, *msg);
        desc.dsc$a_pointer = (char *) &message.opc;
        desc.dsc$w_length  = (char *) &message.opc.opc$l_ms_text -
                             (char *) &message +
                             strlen((char *) &message.opc.opc$l_ms_text);
        status = SYS$SNDOPR(&desc,        /* MSGBUF */
                             0);          /* CHAN */
        if (status & 1)
     result = 1;
        else
            result = 0;
        return &result; (4)
        }

In this example, the declaration of the remote procedure, `printmessage_1` , differs from that of the local procedure `printmessage` in four ways:

1. It includes the `<rpc/rpc.h>` file and the `"msg.h"` header files. The `rpc/rpc.h` file is located in the directory TCPIP$RPC:. To ensure portability in header files references, most of the examples in this manual assume you have defined the symbol RPC to be equal to TCPIP$RPC:

       $ DEFINE RPC TCPIP$RPC:

   before using the RPCGEN compiler and the DECC compiler.
2. It has `_1` appended to its name. In general, all remote procedures called by RPCGEN skeleton routines are named by the following rule: The name in the procedure definition (here, `PRINTMESSAGE` ) is converted to all lowercase letters, and an underscore (_) and version number (here, 1) is appended to it.
3. It takes a pointer to a string instead of a string itself. This is true of all remote procedures -- they always take pointers to their arguments rather than the arguments themselves; if there are no arguments, specify `void` .
4. It returns a pointer to an integer instead of an integer itself. This is also characteristic of remote procedures---they return pointers to their results. Therefore, it is important to have the result declared as a `static` ; if there are no arguments, specify `void` .

## 2.2.3 The Client Program That Calls the Remote Procedure

Example 2-4 declares the main client program, `rprintmsg.c` , that calls the remote procedure. (See SYS$COMMON:[SYSHLP.EXAMPLES.TCPIP.RPC]RPRINTMSG.C.)

Example 2-4 Client Program that Calls the Remote Procedure

    /*
    ** rprintmsg.c: remote OpenVMS version of "printmsg.c"
    */

    #include <stdio.h>
    #include <rpc/rpc.h>     /* always needed  */
    #include "msg.h"         /* msg.h will be generated by RPCGEN */

    main(argc, argv)
        int   argc;
        char *argv[];
    {
        CLIENT *cl;
        char   *message;
        int    *result;
        char   *server;

        if (argc != 3) {
            fprintf(stderr, "usage: %s host message\n", argv[0]);
            exit(1);
            }
        server = argv[1];
        message = argv[2];

        /*
        ** Create client "handle" used for calling MESSAGEPROG on
        ** the server designated on the command line.  We tell
        ** the RPC package to use the TCP protocol when
        ** contacting the server.
        */
        cl = clnt_create(server, MESSAGEPROG, MESSAGEVERS, "tcp"); (1)
        if (cl == NULL) {
            /*
            ** Couldn't establish connection with server.
            ** Print error message and stop.
            */
            clnt_pcreateerror(server);
            exit(1);
            }

        /*
        ** Call the remote procedure "printmessage" on the server
        */
        result = printmessage_1(&message, cl); (2)

        if (result == NULL) { (3)
            /*
            ** An error occurred while calling the server.
            ** Print error message and stop.
            */
            clnt_perror(cl, server);
            exit(1);
            }

        /*
        ** Okay, we successfully called the remote procedure.
        */
        if (*result == 0) { (4)
            /*
            ** Server was unable to print our message.
            ** Print error message and stop.
            */
            fprintf(stderr, "%s: %s couldn't print your message\n", argv[0], server);
            exit(1);
            }

        /*
        ** The message got printed on the server's console
        */
        printf("Message delivered to %s!\n", server);
        exit(0);
        }

In this example, the following events occur:

1. First, the RPC library routine `clnt_create` creates a client "handle." The last parameter to `clnt_create` is `"tcp"` , the transport on which you want to run your application. (Alternatively, you could have used `"udp"` .)
2. Next, the program calls the remote procedure `printmessage_1` in exactly the same way as specified in `msg_server.c` , except for the inserted client handle as the second argument.
3. The remote procedure call can fail in two ways: The RPC mechanism itself can fail or there can be an error in the execution of the remote procedure. In the former case, the remote procedure, `printmessage_1` , returns `NULL` .
4. In the later case, error reporting is application-dependent. In this example, the remote procedure reports any error via `*result` .

## 2.2.4 Running RPCGEN

Use the RPCGEN protocol compiler on the RPC protocol specification file, MSG.X, (from Example 2-2) to generate client and server RPC code automatically:

    $ RPCGEN MSG.X

Using RPCGEN like this---without options---automatically creates the following files from the input file MSG.X:

- A header file called MSG.H that contains `#define` statements for `MESSAGEPROG` , `MESSAGEVERS` , and `PRINTMESSAGE` so you can use them in the other modules. You must include MSG.H in both the client and server modules.
- A file containing client skeleton routines. RPCGEN forms the client skeleton file name, MSG_CLNT.C, by appending _CLNT to the file name and substituting the file type suffix, .C. The MSG_CLNT.C file contains only one client skeleton routine, `printmessage_1` , referred to in the `rprintmsg` client program.
- A file containing server skeleton routines. RPCGEN forms the server skeleton file name, MSG_SVC.C, by appending _SVC to the file name and substituting the file type suffix, .C. The `msg_svc.c` program calls the `printmessage_1` routine in the `msg_server.c` program.

Note The /TABLE option of RPCGEN creates an additional output file of index information for dispatching service routines. See Section 2.6.4 for more information about dispatch tables.

## 2.2.5 Compiling the Client and Server Programs

After the RPCGEN protocol compilation, use two `cc` compilation statements to create a client program and a server program:

- To create the client program called `rprintmsg` , compile the client program, `rprintmsg.c` , and the the client skeleton program ( `msg_clnt.c` ) from the original RPCGEN compilation, then link the two object files together with the RPC object library:

      $ CC/DECC RPRINTMSG.C
      $ CC/DECC MSG_CLNT.C
      $ LINK RPRINTMSG,MSG_CLNT,TCPIP$RPC:TCPIP$RPCXDR/LIBRARY

- To create a server program called `msg_server` , compile the server program `msg_server.c` and the server skeleton program ( `msg_svc.c` ) from the original RPCGEN compilation, then link the two object files together with the RPC object library:

      $ CC/DECC MSG_SERVER.C
      $ CC/DECC MSG_SVC.C
      $ LINK MSG_SERVER,MSG_SVC,TCPIP$RPC:TCPIP$RPCXDR/LIBRARY

Note If you want to use the shareable version of the RPC object library, reference the shareable version of the library, SYS$SHARE:TCPIP$RPCXDR_SHR/SHARE, in your LINK options file.

## 2.2.6 Copying the Server to a Remote System and Running It

Copy the server program `msg_server` to a remote system called `space` in this example. Then, run it as a detached process there:

    $ RUN/DETACHED MSG_SERVER

Note You can invoke servers generated by RPCGEN from the command line as well as with port monitors such as INETd, if you generate them with the /INET_SERVICE option.

From a local system ( `earth` ) you can now print a message on the console of the remote system `space` :

    $ MCR SYS$DISK:[]RPRINTMSG "space" "Hello out there..."

The message `Hello out there...` appears on the console of the system `space` . You can print a message on any console (including your own) with this program if you copy the server to that system and run it.

# 2.3 Advanced Example: Using RPCGEN to Generate XDR Routines

Section 2.2 explained how to use RPCGEN to generate client and server RPC code automatically to convert a simple procedure to one that runs remotely over the network. The RPCGEN protocol compiler can also generate the external data representation (XDR) routines that convert local data structures into network format (and vice versa).

The following sections present a more advanced example of a complete RPC service---a remote directory listing service that uses RPCGEN to generate both the client and server skeletons as well as XDR routines.

## 2.3.1 The RPC Protocol Specification

As with the simple example, you must first create an RPC protocol specification file. This file, DIR.X, is shown in Example 2-5 (see SYS$COMMON:[SYSHLP.EXAMPLES.TCPIP.RPC]DIR.X).

Note You can define types (such as `readdir_res` in Example 2-5) by using the `struct` , `union` , and `enum` keywords, but do not use these keywords in later variable declarations of those types. For example, if you define `union results` , you must declare it later by using `results` , not `union results` . The RPCGEN protocol compiler compiles RPC unions into C structures, so it is an error to declare them later by using the `union` keyword.

Running RPCGEN on DIR.X creates four output files:

- Header file (DIR.H)
- Client skeleton file (DIR_CLNT.C)
- Server skeleton file (DIR_SVC.C)
- File of XDR routines (DIR_XDR.C)

The first three files have already been described. The fourth file, DIR_XDR.C, contains the XDR routines that convert the declared data types into XDR format (and vice versa). For each data type present in the .X file, RPCGEN assumes that the RPC/XDR library contains a routine with the name of that data type prefixed by `xdr_` , for example, `xdr_int` . If the .X file defines the data type, then RPCGEN generates the required XDR routines (for example, DIR_XDR.C). If the .X file contains no such data types, then RPCGEN does not generate the file. If the program uses a data type but does not define it, then you must provide that XDR routine. This enables you to create your own customized XDR routines.

Example 2-5 RPC Protocol Specification File---Advanced Example

    /*
     * dir.x: Remote directory listing protocol
     */

    /* maximum length of a directory entry */
    const MAXNAMELEN = 255;
    /* a directory entry */
    typedef string nametype<MAXNAMELEN>;

    /* a link in the listing */
    typedef struct namenode *namelist;
    /*
     * A node in the directory listing
     */
    struct namenode {
         nametype name;          /* name of directory entry */
         namelist next;          /* next entry */
    };

    /*
     * The result of a READDIR operation.
     */
    union readdir_res switch (int Errno) {
    case 0:
         namelist list;  /* no error: return directory listing */

    default:
         void;           /* error occurred: nothing else to return */
    };
    /*
     * The directory program definition
     */
    program DIRPROG {
         version DIRVERS {
              readdir_res
              READDIR(nametype) = 1;
         } = 1;
    } = 0x20000076;

Previous | Next | Contents | Index