Using SUN RPCgen


I. Introduction

Remote procedure calls are a well known method of transferring control from one process to another. The intent of RPC's is to make it appear to programmers that a local procedure call is taking place, thus hiding all the network code into its stub procedures. There are a number of steps involved in a remote procedure call. They are illustrated in Figure 8 and explained below [1].



2. Implementation[1][2]

For the grades program which will be developed, Sun RPCgen will be used. RPCgen is a compiler which takes a remote procedure interface, and generates the client and server stubs. Minimally RPCgen requires coding three files to implement a simple client server model. The client (or main function), The server (or remote procedures), and the RPC specification file. The client and server procedures must be written in 'C' and the RPC specification file must be written in Protocol Definition Language (PDL). A handout is provided with the full description of RPCgen PDL as given in [2]. All PDL files must end with the extension .x for the rpcgen compiler to recognize it. The rpcgen (given a file written in PDL as an argument) compiler will produce the following functions in 'C' which utilize Low Level RPC calls:

rpcgen pdl_file.x

from a command line will produce.

a) pdl_file.h - consists of common includes. This file must be included by the programmer in the client and server routines.

b) pdl_file_xdr.c - consists of filters used by both client and server routines.

c) pdl_file_clnt.c - consists of the client stub.

d) pdl_file_svc.c - consists of the server stub.

EX1 : Simple Example of a client choosing from two servers to 'manipulate' a structure.


/*

"program  pdl.x"

Written in PDL, has 1 program section. It is the RPC 
specification file.

*/

struct newgr{
  char nm[30];
  char inet_id[30];
  int num;
};

typedef newgr *newgrptr;

program SERVER_1_PROG{            /*program section for server1*/
  version SERVER_1_VERSION
    {
       newgr TEST_STRUCT1(newgr)=1;               /*procedure 1*/  
       newgr TEST_STRUCT2(newgr)=2;               /*procedure 2*/

/* other procedures used go here:  with different procedure #'s*/

    }=1;                                       /*version number*/
}=0x31234567;                                  /*program number*/

RPCgen generates the actual remote procedure names by converting the procedure names to lower case and appending an underscore and the version number to them.


/*

"program client.c"

Our client program is written in 'C Language'. The executable 
file is designed to take the servers' address as its first and 
second argument.  

*/

#include <stdio.h>
#include <rpc/rpc.h> /* standard include file must be included */
                                          /* in client program */
#include "pdl.h"   /* generated by rpcgen and must be included */
                                         /* in client program  */

CLIENT *client_handle;
char *server1_id,*server2_id;

main(argc,argv)
int argc;
char *argv[];
{  
newgr *ngptr;
newgrptr *ptr;
int serv;

server1_id=argv[1];                     /*server1 is argument 1*/
server2_id=argv[2];                   /* server2 is argument 2 */

if (argc!=3) 
  {perror("usage: clnt [server1.addr] [server2.addr]"); exit(0);} 

printf("CHOOSE A SERVER 1 OR 2 \n");
scanf("%d",&serv);           /* prompt user to choose a server */

switch (serv)             /* according to the server chosen do */
{
 case (1):{     
         if ((client_handle=clnt_create(server1_id,SERVER_1_PROG,
            SERVER_1_VERSION,"tcp")) == NULL)
            {
              clnt_pcreateerror(argv[1]);
              exit(0);
            }
         ngptr=(newgrptr)malloc(sizeof(newgr));    /*get memory*/
         strcpy(ngptr->inet_id,server1_id);
                                      /* call remote procedure */
         ngptr=test_struct1_1(ngptr,client_handle);
         printf("nm:%s inet_id:%s \n",ngptr->nm,ngptr->inet_id);
         clnt_destroy(client_handle);
          }  break;

 case (2):{
         if ((client_handle=clnt_create(server2_id,SERVER_1_PROG,
            SERVER_1_VERSION,"tcp")) == NULL)   
            {
              clnt_pcreateerror(argv[2]);
              exit(0);
            }
         ngptr=(newgrptr)malloc(sizeof(newgr));    /*get memory*/
         strcpy(ngptr->inet_id,server2_id);
                                      /* call remote procedure */
         ngptr=test_struct2_1(ngptr,client_handle); 
         printf("nm:%s inet_id:%s \n",ngptr->nm,ngptr->inet_id);
         clnt_destroy(client_handle);
         }  break;
 } /*end switch*/      
 exit(0);
} /* end main */


All remote procedure calls return NULL upon failure.


/*

"program  server.c"
  
Remote procedures called by the server stub. i.e our server 
procedures. 

*/

#include <string.h>
#include <rpc/rpc.h>             /* must be included by server */
#include "pdl.h"                 /* must be included by server */

struct newgr* test_struct1_1(ng)/*remote procedure 1 for server*/
struct newgr* ng;
{
  ng->nm[0]=NULL;
  strcat(ng->nm,"running server 1");
  printf("running server1\n");
  return ng;
}

struct newgr* test_struct2_1(ng)/*remote procedure 2 for server*/
struct newgr* ng;
{
  ng->nm[0]=NULL;
  strcat(ng->nm,"running server 2");
  printf("running server2\n");
  return ng;
}

2.1 Things to note

2.1.1 Procedure names : reference

The remote procedures defined in the Program section of the pdl_file.x file (RPC specification file written in PDL), must be transformed to lower case and an underscore and version number must be attached at the end. In EX1 the Program sections of our pdl.x file define two procedures.


a) newgr TEST_STRUCT1(newgr)=1;/* procedure 1 in SERVER_1_PROG */
b) newgr TEST_STRUCT2(newgr)=2;/* procedure 2 in SERVER_1_PROG */

The procedures have a version number of one (so an underscore and a number one must be appended to the lower case procedure name when defined or calling the procedures). They must be referenced by our client program as follows:

/*procedure 1*/     var=test_struct1_1(arg,client_handle);
/*procedure 2*/     var=test_struct2_1(arg,client_handle);

Where var is of appropriate return type in 'C' ('not' PDL) (note PDL defines some data types differently than 'C'). The appropriate client handle must be passed as the last parameter to any remote procedure. All remote procedures accept only two arguments. The first argument will be the 'message' sent, and the second the client handle. If more arguments must be sent through, they must be placed in a structure and the structure must be passed as the single argument.

2.1.2 Procedure names : definition

Like RPC procedure calling, the RPC procedure definitions (in our server program) must be transformed to lower case and an underscore and version number must be attached at the end.

/*procedure 1*/   struct newgr* test_struct1_1(ng) { ... }
/*procedure 2*/   struct newgr* test_struct2_1(ng) { ... }

2.1.3 RPC and Pointers

All RPC remote procedures must return pointer addresses and must take only pointers as arguments (must send and receive pointers). If local variables (defined at the server end) are returned from the remote procedures, the variables must be declared as static because their values must remain defined after the return statement passes control to the server stub.

2.1.4 Include files

Both the client and server programs must include the following:


#include <rpc/rpc.h>              /* standard RPC include file */
#include "pdl_file.h"               /* generated by pdl_file.x */

2.1.4 Program numbers

When coding the Program section of our pdl_file.x the program number of our defined section should be in the range 0x20000000 - 0x3fffffff . These are program numbers reserved by the operating system for user definitions.

2.2 RPC Library Calls[2]

Only two RPC library Calls will be necessary for the implementation of the grades program. Both will be used by the client routines. Manual pages are available on SunOS on these and other RPCgen functions and compiler options.

clnt_create( )

Creates a CLIENT handle for the specified server host, program, and version numbers. Parameter host is the Interned address in dotted decimal notation. Parameters prognum and versnum are the Program and Version numbers specified in Specification File (.x file). Parameter protocol specifies the use of either tcp or udp communication protocols. clnt_create(3N) will return NULL upon failure.


CLIENT* clnt_create(host,prognum,versnum,protocol)
char *host;
u_long prognum, versnum;
char* protocol;

clnt_destroy( )

Deallocates memory associated with private data structures for the named CLIENT handle.


clnt_destroy(clnt)
CLIENT *clnt;

3. Run and Compilation

There are a few steps to actually running an RPC program. They are as follows.

1) Generate header (pdl_file.h), XDR filters (pdl_file_xdr.c), client stub (pdl_file_clnt.c), and server stub (pdl_file_svc.c) from our Specification file (pdl_file.x). from the command line type:


>rpcgen  pdl_file.x
2) Link the client code with the client stub and xdr filters:

>cc client_file.c pdl_file_clnt.c pdl_file_xdr.c -o client_exec
3) Link the server code with the server stub and xdr filters:

>cc server_file.c pdl_file_svc.c pdl_file_xdr.c -o server_exec
EX2 : Compile and run the code described in EX1.

compilation:

>rpcgen  pdl.x

>cc  client.c  pdl_clnt.c  pdl_xdr.c  -o  clnt  

>cc  server.c  pdl_svc.c  pdl_xdr.c  -o  svc
NOTE: This example assumes servers running in heron (server1), and condor (server2).
>clnt  129.108.42.32  129.108.42.34 


[1]  Stevens, W. R. Unix Network Programming, Prentice Hall Inc. 1990.

[2] Bloomer, J. Power Programming with RPC, O'Reilly & Associates, Inc. 1992.

[3] Comer, D.
      Stevens, D. Internetworking with TCP/IP vol.iii, Prentice Hall Inc. 1993.
  
[4] Corbin, J. The Art of Distributed Applications, Springer-Verlag 1991.

David Morales

dmorales@cs.utep.edu