Compaq TCP/IP Services for OpenVMS
ONC RPC Programming


Previous Contents Index

2.3.2 Implementing the Procedure Declared in the Protocol Specification

Example 2-6 (see SYS$COMMON:[SYSHLP.EXAMPLES.TCPIP.RPC]DIR_SERVER.C) consists of the dir_server.c program that implements the remote READDIR procedure from the previous RPC protocol specification file.

Example 2-6 Remote Procedure Implementation

/* 
** dir_server.c: remote OpenVMS readdir implementation 
*/ 
#include <errno.h> 
#include <rms.h> 
#include <rpc/rpc.h>   /* Always needed */ 
#include "dir.h"       /* Created by RPCGEN */ 
 
extern int SYS$PARSE(struct FAB *); 
extern int SYS$SEARCH(struct FAB *); 
 
extern char *malloc(); 
 
readdir_res * 
readdir_1(dirname) 
    nametype *dirname; 
{ 
    char   expanded_name[NAM$C_MAXRSS+1]; 
    struct FAB fab; 
    struct NAM nam; 
    namelist       nl; 
    namelist      *nlp; 
    static readdir_res res; /* must be static! */ 
    char   resultant_name[NAM$C_MAXRSS+1]; 
    int exit(); 
 
    /* 
    ** Initialize the FAB. 
    */ 
    fab = cc$rms_fab; 
    fab.fab$l_fna = *dirname; 
    fab.fab$b_fns = strlen(*dirname); 
    fab.fab$l_dna = "SYS$DISK:[]*.*;*"; 
    fab.fab$b_dns = strlen(fab.fab$l_dna); 
 
    /* 
    ** Initialize the NAM. 
    */ 
    nam = cc$rms_nam; 
    nam.nam$l_esa = expanded_name; 
    nam.nam$b_ess = NAM$C_MAXRSS; 
    nam.nam$l_rsa = resultant_name; 
    nam.nam$b_rss = NAM$C_MAXRSS; 
    fab.fab$l_nam = &nam; 
 
    /* 
    ** Parse the specification and see if it works. 
    */ 
    if (SYS$PARSE(&fab) & 1) { 
 /* 
 ** Free previous result 
 */ 
 xdr_free(xdr_readdir_res, &res); 
 
        /* 
        ** Collect directory entries. 
        ** Memory allocated here will be freed by xdr_free 
        ** next time readdir_1 is called 
        */ 
        nlp = &res.readdir_res_u.list; 
        while (SYS$SEARCH(&fab) & 1) { 
     resultant_name[nam.nam$b_rsl] = '\0'; 
            nl = (namenode *) malloc(sizeof(namenode)); 
     *nlp = nl; 
            nl->name = (char *) malloc(nam.nam$b_name + 
                                       nam.nam$b_type + 
                                       nam.nam$b_ver + 1); 
     strcpy(nl->name, nam.nam$l_name); 
            nlp = &nl->next; 
            } 
        *nlp = NULL; 
 
        /* 
        ** Return the result 
        */ 
        res.Errno = 0; 
        } /* SYS$PARSE() */ 
    else 
        res.Errno = fab.fab$l_sts; 
    
    return &res; 
    } 
 

2.3.3 The Client Program that Calls the Remote Procedure

Example 2-7 (see SYS$COMMON:[SYSHLP.EXAMPLES.TCPIP.RPC]RLS.C) shows the client program, rls.c , that calls the remote server procedure.

Example 2-7 Client Program that Calls the Server

/* 
* rls.c: Remote directory listing client 
*/ 
#include <errno.h> 
#include <rms.h> 
#include <stdio.h> 
#include <rpc/rpc.h>    /* always need this */ 
#include "dir.h" 
 
main(argc, argv) 
     int   argc; 
     char *argv[]; 
{ 
     CLIENT *cl; 
     char   *dir; 
     namelist nl; 
     readdir_res *result; 
     char   *server; 
     int exit(); 
 
    if (argc != 3) { 
        fprintf(stderr, "usage: %s host directory\n", argv[0]); 
        exit(1); 
        } 
 
    server = argv[1]; 
    dir = argv[2]; 
 
    /* 
    ** Create client "handle" used for calling DIRPROG on 
    ** the server designated on the command line.  Use 
    ** the tcp protocol when contacting the server. 
    */ 
    cl = clnt_create(server, DIRPROG, DIRVERS, "tcp"); 
    if (cl == NULL) { 
        /* 
        ** Couldn't establish connection with server. 
        ** Print error message and stop. 
        */ 
        clnt_pcreateerror(server); 
        exit(1); 
        } 
 
    /* 
    ** Call the remote procedure readdir on the server 
    */ 
    result = readdir_1(&dir, cl); 
    if (result == NULL) { 
        /* 
        ** An RPC 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->Errno != 0) { 
        /* 
        ** A remote system error occurred. 
        ** Print error message and stop. 
        **/ 
        errno = result->Errno; 
        perror(dir); 
        exit(1); 
        } 
 
    /* 
    ** Successfully got a directory listing. 
    ** Print it out. 
    */ 
    for (nl = result->readdir_res_u.list; 
             nl != NULL; 
                 nl = nl->next) 
        printf("%s\n", nl->name); 
    exit(0); 
    } 

2.3.4 Running RPCGEN

As with the simple example, you must run the RPCGEN protocol compiler on the RPC protocol specification file DIR.X:


$ RPCGEN DIR.X 

RPCGEN creates a header file, DIR.H, an output file of client skeleton routines, DIR_CLNT.C, and an output file of server skeleton routines, DIR_SVC.C. For this advanced example, RPCGEN also generates the file of XDR routines, DIR_XDR.C.

2.3.5 Compiling the File of XDR Routines

The next step is to compile the file of XDR routines, DIR_XDR.C:


$ CC/DECC DIR_XDR 

2.3.6 Compiling the Client and Server Programs

After the XDR compilation, use two CC and LINK sequences to create the client program and the server program:

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, in your LINK options file.

2.3.7 Copying the Server to a Remote System and Running It

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


$ RUN/DETACHED DIR_SERVER 

From the local system earth invoke the RLS program to provide a directory listing on the system where dir_server is running in background mode. The following example shows the command and output (a directory listing of /usr/pub on system space ):


$ MCR SYS$DISK:[]RLS "space" "/usr/pub" 
. 
.. 
ascii 
eqnchar 
kbd 
marg8 
tabclr 
tabs 
tabs4 

Note

Client code generated by RPCGEN does not release the memory allocated for the results of the RPC call. You can call xdr_free to deallocate the memory when no longer needed. This is similar to calling free , except that you must also pass the XDR routine for the result. For example, after printing the directory listing in the preceding example, you could call xdr_free as follows:


xdr_free(xdr_readdir_res, result); 

2.4 Debugging Applications

It is difficult to debug distributed applications that have separate client and server processes. To simplify this, you can test the client program and the server procedure as a single program by linking them with each other rather than with the client and server skeletons. To do this, you must first remove calls to client creation RPC library routines (for example, clnt_create ). To create the single debuggable file RLS.EXE, compile each file and then link them together as follows:


$ CC/DECC RLS.C 
$ CC/DECC DIR_CLNT.C 
$ CC/DECC DIR_SERVER.C 
$ CC/DECC DIR_XDR.C 
% LINK RLS,DIR_CLNT,DIR_SERVER,DIR_XDR,TCPIP$RPC:TCPIP$RPCXDR/LIBRARY 

The procedure calls are executed as ordinary local procedure calls and you can debug the program with a local debugger. When the program is working, link the client program to the client skeleton produced by RPCGEN and the server procedures to the server skeleton produced by RPCGEN.

There are two kinds of errors possible in an RPC call:

  1. A problem with the remote procedure call mechanism.
    This occurs when a procedure is unavailable, the remote server does not respond, the remote server cannot decode the arguments, and so on. As in Example 2-7, an RPC error occurs if result is NULL .
    The program can print the reason for the failure by using clnt_perror , or it can return an error string through clnt_sperror .
  2. A problem with the server itself.
    As in Example 2-6, an error occurs if opendir fails; that is why readdir_res is of type union . The handling of these types of errors is the responsibility of the programmer.

2.5 The C Preprocessor

The C preprocessor, CC/DECC/PREPROCESSOR, runs on all input files before they are compiled, so all the preprocessor directives are legal within an .X file. RPCGEN may define up to five macro identifiers, depending on which output file you are generating. The following table lists these macros:
Identifier Usage
RPC_HDR For header file output
RPC_XDR For XDR routine output
RPC_SVC For server skeleton output
RPC_CLNT For client skeleton output
RPC_TBL For index table output

Also, RPCGEN does some additional preprocessing of the input file. Any line that begins with a percent sign ( % ) passes directly into the output file, without any interpretation. Example 2-8 demonstrates this processing feature.

Example 2-8 Using the Percent Sign to Bypass Interpretation of a Line

/* 
 * time.x: Remote time protocol 
 */ 
program TIMEPROG { 
     version TIMEVERS { 
          unsigned int TIMEGET(void) = 1; 
     } = 1; 
} = 44; 
 
#ifdef RPC_SVC 
%int * 
%timeget_1() 
%{ 
%    static int thetime; 
% 
%    thetime = time(0); 
%    return (&thetime); 
%} 
#endif 

Using the percent sign feature does not guarantee that RPCGEN will place the output where you intend. If you have problems of this type, do not use this feature.

2.6 RPCGEN Programming

The following sections contain additional RPCGEN programming information about network types, defining symbols, INETd support, and dispatch tables.

2.6.1 Network Types

By default, RPCGEN generates server code for both UDP and TCP transports. The /TRANSPORT option creates a server that responds to requests on the specified transport. The following command creates a UDP server from a file called PROTO.X:


$ RPCGEN /TRANSPORT=UDP PROTO.X 

2.6.2 User-Provided Define Statements

The RPCGEN protocol compiler provides a way to define symbols and assign values to them. These defined symbols are passed on to the C preprocessor when it is invoked. This facility is useful when, for example, invoking debugging code that is enabled only when you define the DEBUG symbol. For example, to enable the DEBUG symbol in the code generated from the PROTO.X file, use the following command:


$ RPCGEN /DEFINE=DEBUG PROTO.X 

2.6.3 INETd Support

The RPCGEN protocol compiler can create RPC servers that INETd can invoke when it receives a request for that service. For example, to generate INETd support for the code generated for the PROTO.X file, use the following command:


$ RPCGEN /INET_SERVICE PROTO.X 

The server code in proto_svc.c supports INETd. For more information on setting up entries for RPC services, see Section 3.7.

In many applications, it is useful for services to wait after responding to a request, on the chance that another will soon follow. However, if there is no call within a certain time (by default, 120 seconds), the server exits and the port monitor continues to monitor requests for its services. You can use the /TIMEOUT_SECONDS option to change the default waiting time. In the following example, the server waits only 20 seconds before exiting:


$ RPCGEN /INET_SERVICE /TIMEOUT_SECONDS=20 PROTO.X 

If you want the server to exit immediately, use /TIMEOUT_SECONDS = 0; if you want the server to wait forever (a normal server situation), use /TIMEOUT_SECONDS = -1.

2.6.4 Dispatch Tables

Dispatch tables are often useful. For example, the server dispatch routine may need to check authorization and then invoke the service routine, or a client library may need to control all details of storage management and XDR data conversion. The following RPCGEN command generates RPC dispatch tables for each program defined in the protocol description file, PROTO.X, and places them in the file PROTO_TBL.I (the suffix .I indicates index):


$ RPCGEN /TABLE PROTO.X 

Each entry in the table is a struct rpcgen_table defined in the header file, PROTO.H, as follows:


               struct rpcgen_table { 
                   char        *(*proc)(); 
                   xdrproc_t   inproc; 
                   unsigned    len_in; 
                   xdrproc_t   outproc; 
                   unsigned    len_out; 
               }; 

In this definition:

The table dirprog_1_table is indexed by procedure number. The variable dirprog_1_nproc contains the number of entries in the table. The find_proc routine in the following example shows how to locate a procedure in the dispatch tables.


struct rpcgen_table * 
find_proc(proc) 
     long    proc; 
{ 
     if (proc >= dirprog_1_nproc) 
 
          /* error */ 
     else 
          return (&dirprog_1_table[proc]); 
} 

Each entry in the dispatch table (in the file input_file_TBL.I) contains a pointer to the corresponding service routine. However, the service routine is not defined in the client code. To avoid generating unresolved external references, and to require only one source file for the dispatch table, the actual service routine initializer is RPCGEN_ACTION(proc_ver) . The following example shows the dispatch table entry for the procedure printmessage with a procedure number of 1:


     ..... 
     (char *(*)())RPCGEN_ACTION(printmessage_1), 
     xdr_wrapstring,       0, 
     xdr_int,              0, 
     ..... 

With this feature, you can include the same dispatch table in both the client and the server. Use the following define statement when compiling the client:


#define RPCGEN_ACTION(routine)  0 

Use the following define statement when compiling the server:


#define RPCGEN_ACTION(routine)  routine 

2.7 Client Programming

The following sections contain client programming information about default timeouts and client authentication.

2.7.1 Timeout Changes

A call to clnt_create sets a default timeout of 25 seconds for RPC calls. RPC waits for 25 seconds to get the results from the server. If it does not get any results, then this usually means that one of the following conditions exists:

In such cases, the function returns NULL ; you can print the error with clnt_perrno .

Sometimes you may need to change the timeout value to accommodate the application or because the server is slow or far away. Change the timeout by using clnt_control . The code segment in the following example demonstrates the use of clnt_control .


struct timeval tv; 
CLIENT *cl; 
 
cl = clnt_create("somehost", SOMEPROG, SOMEVERS, "tcp"); 
if (cl == NULL) { 
     exit(1); 
} 
tv.tv_sec = 60; /* change timeout to 1 minute */ 
tv.tv_usec = 0; /* this should always be set  */ 
clnt_control(cl, CLSET_TIMEOUT, &tv); 

2.7.2 Client Authentication

By default, client creation routines do not handle client authentication. Sometimes, you may want the client to authenticate itself to the server. This is easy to do, as shown in the following code segment:


CLIENT *cl; 
 
cl = clnt_create("somehost", SOMEPROG, SOMEVERS, "udp"); 
if (cl != NULL) { 
     /* To set UNIX style authentication */ 
     cl->cl_auth = authunix_create_default(); 
} 

For more information on authentication, see Section 3.6.

2.8 Server Programming

The following sections contain server programming information about system broadcasts and passing data to server procedures.

2.8.1 Handling Broadcasts

Sometimes, clients broadcast to determine whether a particular server exists on the network, or to determine all the servers for a particular program and version number. You make these calls with clnt_broadcast (for which there is no RPCGEN support). Refer to Section 3.5.2.

When a procedure is known to be called with broadcast RPC, it is best for the server not to reply unless it can provide useful information to the client. Otherwise, servers could overload the network with useless replies. To prevent the server from replying, a remote procedure can return NULL as its result; the server code generated by RPCGEN can detect this and prevent a reply.

In the following example, the procedure replies only if it acts as an NFS server:


void * 
reply_if_nfsserver() 
{ 
     char notnull;   /* just here so we can use its address */ 
     if (access("/etc/exports", F_OK) < 0) { 
          return (NULL);  /* prevent RPC from replying */ 
     } 
     /* 
      * return non-null pointer so RPC will send out a reply 
      */ 
     return ((void *)&notnull); 
} 

If a procedure returns type void * , it must return a nonnull pointer if it wants RPC to reply for it.

2.8.2 Passing Data to Server Procedures

Server procedures often need to know more about an RPC call than just its arguments. For example, getting authentication information is useful to procedures that want to implement some level of security. This information is supplied to the server procedure as a second argument. (For details, see the structure of svc_req in Section 3.6.2.) The following code segment shows the use of svc_req , where the first part of the previous printmessage_1 procedure is modified to allow only root users to print a message to the console:


int * 
printmessage_1(msg, rqstp) 
     char **msg; 
     struct svc_req  *rqstp; 
{ 
     static int result;      /* Must be static */ 
     FILE *f; 
     struct authunix_parms *aup; 
 
     aup = (struct authunix_parms *)rqstp->rq_clntcred; 
     if (aup->aup_uid != 0) { 
          result = 0; 
          return (&result); 
     } 
/* Same code as before */ 


Previous Next Contents Index