[ Next Article | Previous Article | Book Contents | Library Home | Legal | Search ]
Communications Programming Concepts

Generating XDR Routines Example

The Converting Local Procedures into Remote Procedures Example demonstrates the automatic generation of client and server Remote Procedure Call (RPC) code. The rpcgen protocol compiler may also be used to generate eXternal Data Representation (XDR) routines that convert local data structures into network format, and vice versa. The following protocol description file presents a complete RPC service that is a remote directory listing service that uses the rpcgen protocol compiler to generate not only stub routines, but also XDR routines.

/*
 * dir.x: Remote directory listing protocol
 */
 const MAXNAMELEN = 255;/* maximum length of a directory entry */
 typedef string nametype<MAXNAMELEN>;    /* a directory entry */
 typedef struct namenode *namelist;   /* a link in the listing */
 /*
  * 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;
 } = 76;
 
Note: Types (like readdir_res in the example above) can be defined using the struct, union and enum keywords, but these keywords should not be used in subsequent declarations of variables of those types. For example, if you define a union, foo, you should declare using only foo and not union foo. In fact, the rpcgen protocol compiler compiles RPC unions into C structures, in which case it is an error to declare these unions using the union keyword.

Running the rpcgen protocol compiler on the dir.x file creates four output files. Three are the same as before: header file, client stub routines, and server skeleton. The fourth file contains the XDR routines necessary for converting the specified data types into XDR format, and vice versa. These are output in the dir_xdr.c file.

Following is the implementation of the READDIR procedure:

/*
 * dir_proc.c: remote readdir implementation
 */
 #include <rpc/rpc.h>
 #include <sys/dir.h>
 #include "dir.h"
 extern int errno;
 extern char *malloc();
 extern char *strdup();
 readdir_res *
 readdir_1(dirname)
         nametype *dirname;
 {
         DIR *dirp;
         struct direct *d;
         namelist nl;
         namelist *nlp;
         static readdir_res res; /* must be static */
         /*
          * Open directory
          */
         dirp = opendir(*dirname);
         if (dirp == NULL) {
                 res.errno = errno;
                 return (&res);
         }
         /* 
          * 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 (d = readdir(dirp)) {
            nl = *nlp = (namenode *) malloc(sizeof(namenode));
            nl->name = strdup(d->d_name);
            nlp = &nl->next; 
         }
          *nlp = NULL;
       
          /*
          * Return the result
          */
          res.errno = 0;
         closedir(dirp);
         return (&res);
 }

The client side program calls the server as follows:

/*
 * rls.c: Remote directory listing client
 */
 #include <stdio.h>
 #include <rpc/rpc.h>    /* always need this */
 #include "dir.h"        /* will be generated by rpcgen */
 extern int errno;
 main(argc, argv)
         int argc;
         char *argv[];
 {
         CLIENT *cl;
         char *server;
         char *dir;
         readdir_res *result;
         namelist nl;
         if (argc != 3) { 
                fprintf(stderr, "usage: %s host directory\n",
                   argv[0]);
                 exit(1);
         }
         /*
          * Remember what our command line arguments refer to
          */
         server = argv[1];
         dir = 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, DIRPROG, DIRVERS, "tcp");
         if (cl == NULL) {
              /*
               * Could not establish connection with server.
               * Print error message and die.
               */
               clnt_pcreateerror(server);
               exit(1);
         }
         /*
          * Call the remote procedure readdir on the server
          */
         result = readdir_1(&dir, cl);
         if (result == NULL) {
                 /*
                  * An error occurred while calling the server.
                  * Print error message and die.
                  */
                 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 die.
                 */
                 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\en", nl->name);
         }
         exit(0);
 }  

Finally, in regard to the rpcgen protocol compiler, the client program and the server procedure can be tested together as a single program by simply linking them with each other rather than with client and server stubs. The procedure calls are executed as ordinary local procedure calls and the program can be debugged with a local debugger such as dbx. When the program is working, the client program can be linked to the client stub produced by the rpcgen protocol compiler. The server procedures can be linked to the server stub produced by the rpcgen protocol compiler.

Note: If you do this, you may want to comment out calls to RPC library routines and have client-side routines call server routines directly.

[ Next Article | Previous Article | Book Contents | Library Home | Legal | Search ]