|
|
The example in ``Converting local procedures into remote procedures'' illustrated the automatic generation of client and server RPC code. You can also use rpcgen to generate XDR routines, that is, the routines necessary to convert local data structures into XDR format and vice-versa.
This example presents a complete RPC service: a remote directory listing service, built using rpcgen not only to generate stub routines, but also to generate the XDR routines.
This is the protocol description file:
/* * 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; } = 0x20000076;
readdir_res
in the example above) can be defined using
the struct, union and enum keywords.
These keywords should not be used in later declarations of variables
of those types.
For example, if you define a union foo,
you should declare using only foo and not
union foo.
rpcgen compiles RPC unions into C structures. It is an error to declare RPC unions using the union keyword.
Running rpcgen on dir.x creates four output files. First are the basic three described in ``Converting local procedures into remote procedures'': the header file, client stub routines and server skeleton.
The fourth contains the XDR routines necessary for converting instances of declared data types from host platform representation into XDR format, and vice-versa. These routines are output in the file dir_xdr.c.
For each data type used in the .x file, rpcgen assumes that the RPC/XDR library contains a routine whose name is the name of the datatype, prepended by xdr_ (for example, xdr_int). If a data type is defined in the .x file, then rpcgen generates the required xdr_ routine.
If there are no such data types definitions, in an RPC source file (for example, msg.x), then an _xdr.c file will not be generated.
An RPC programmer may write a .x source file that uses a data type not supported by the RPC/XDR library, and deliberately omit defining the type (in the .x file); if so, the programmer has to provide that xdr_ routine. This is a way for programmers to provide their own customized xdr_ routines. See ``Remote Procedure Call programming'' for more details on passing arbitrary data types.
This is the server-side implementation of the READDIR procedure:
/* * dir_proc.c: remote readdir implementation */ #include <rpc/rpc.h> /* Always needed */ #include <dirent.h> #include "dir.h" /* Created by rpcgen */This is the client side program to call the server:extern int errno; extern char *malloc(); extern char *strdup();
readdir_res * readdir_1(dirname) nametype *dirname; { DIR *dirp; struct dirent *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); }
/* * rls.c: Remote directory listing client */Again using the hypothetical systems named local and remote, the files can be compiled and run as follows on remote:#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 any visible transport when contacting the server. */
cl = clnt_create(server, DIRPROG, DIRVERS, "visible"); if (cl == NULL) { /* * Couldn't 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\n", nl->name); }
exit(0); }
After installing an executable copy of rls on system local, a user on that system can list the contents of /usr/share/lib on system remote by running the command rls remote /usr/share/lib on local. This produces output such as:
. .. ascii eqnchar greek kbd marg8 tabclr tabs tabs4 local$