Sun Microsystems, Inc.
spacerspacer
spacer www.sun.com docs.sun.com |
spacer
black dot
 
 
3.  rpcgen Programming Guide rpcgen Tutorial Converting Local Procedures to Remote Procedures  Previous   Contents   Next 
   
 

In the example code, a client handle is created by the RPC library routine clnt_create(). This client handle is passed to the stub routine that calls the remote procedure. See Chapter 4, Programmer's Interface to RPC for details on how the client handle can be created in other ways. If no more calls are to be made using the client handle, destroy it with a call to clnt_destroy() to conserve system resources.

The last parameter to clnt_create() is visible, which specifies that any transport noted as visible in /etc/netconfig can be used. For further information on transports, see the /etc/netconfig file and its description in Programming Interfaces Guide.

The remote procedure printmessage_1() is called exactly the same way as it is declared in msg_proc.c, except for the inserted client handle as the second argument. The remote procedure also returns a pointer to the result instead of the result.

The remote procedure call can fail in two ways. The RPC mechanism can fail or an error can occur in the execution of the remote procedure. In the former case, the remote procedure printmessage_1() returns a NULL. In the latter case, the error reporting is application dependent. Here, the error is returned through *result.

The compile commands for the printmsg example are:

$ rpcgen msg.x
$ cc rprintmsg.c msg_clnt.c -o rprintmsg -lnsl
$ cc msg_proc.c msg_svc.c -o msg_server -lnsl

rpcgen is used to generate the header files (msg.h), client stub (msg_clnt.c), and server stub (msg_svc.c). Then, two programs are compiled: the client program rprintmsg and the server program msg_server. The C object files must be linked with the library libnsl, which contains all of the networking functions, including those for RPC and XDR.

In this example, no XDR routines were generated because the application uses only the basic types that are included in libnsl.

rpcgen received the input file msg.x and created:

  • A header file called msg.h that contained #define statements for MESSAGEPROG, MESSAGEVERS, and PRINTMESSAGE for use in the other modules. This file must be included by both the client and server modules.

  • The client stub routines in the msg_clnt.c file. Only one routine, the printmessage_1() routine, was called from the rprintmsg client program. If the name of an rpcgen input file is FOO.x, the client stub's output file is called FOO_clnt.c.

  • The server program in msg_svc.c that calls printmessage_1() from msg_proc.c. The rule for naming the server output file is similar to that of the client: for an input file called FOO.x, the output server file is named FOO_svc.c.

After the server program is created, it is installed on a remote machine and run. If the machines are homogeneous, the server binary can just be copied. If they are not homogeneous, the server source files must be copied to and compiled on the remote machine. For this example, the remote machine is called remote and the local machine is called local. The server is started from the shell on the remote system:

remote$ msg_server

Server processes generated with rpcgen always run in the background. You do not have to follow the server's invocation with an ampersand (&). Servers generated by rpcgen can also be invoked by port monitors like listen() and inetd(), instead of from the command line.

Thereafter, a user on local can print a message on the console of machine remote as follows:

local$ rprintmsg remote "Hello, there."

Using rprintmsg, a user can print a message on any system console, including the local console, when the server msg_server is running on the target system.

Passing Complex Data Structures

"Converting Local Procedures to Remote Procedures" shows how to generate client and server RPC code. rpcgen can also be used to generate XDR routines, which are the routines that convert local data structures into XDR format and the reverse.

The following code example presents a complete RPC service: a remote directory listing service, built using rpcgen to generate both stub routines and the XDR routines.


Example 3-4 RPC Protocol Description File: dir.x

/*
 * dir.x: Remote directory listing protocol
 *
 * This example demonstrates the functions of rpcgen.
 */
 
const MAXNAMELEN = 255;						/* max length of directory
entry */
typedef string nametype<MAXNAMELEN>;		/* director entry */
typedef struct namenode *namelist;			/* 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
 *
 * a truly portable application would use
 * an agreed upon list of error codes
 * rather than (as this sample program
 * does) rely upon passing UNIX errno's
 * back.
 *
 * In this example: The union is used 
 * here to discriminate between successful
 * and unsuccessful remote calls.
 */
 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; 

You can redefine types (like readdir_res in the previous example) using the struct, union, and enum RPC language keywords. These keywords are not used in later declarations of variables of those types. For example, if you define a union, foo, you declare using only foo, and not union foo.

rpcgen compiles RPC unions into C structures. Do not declare C unions using the union keyword.

Running rpcgen on dir.x generates four output files:

  • Header file

  • Client stub

  • Server skeleton

  • XDR routines in the file dir_xdr.c.

The dir_xdr.c file contains the XDR routines to convert declared data types from the host platform representation into XDR format, and the reverse.

For each RPC data type used in the.x file, rpcgen assumes that libnsl contains a routine with a name that is the name of the data type, prepended by the XDR routine header xdr_ (for example, xdr_int). If a data type is defined in the.x file, rpcgen generates the required xdr_ routine. If there is no data type definition in the .x source file (for example, msg.x), then no _xdr.c file is generated.

You can write a.x source file that uses a data type not supported by libnsl, and deliberately omit defining the type in the.x file. In doing so, you must provide the xdr_ routine. This is a way to provide your own customized xdr_ routines. See Chapter 4, Programmer's Interface to RPC for more details on passing arbitrary data types. The server-side of the READDIR procedure is shown in the following example.


Example 3-5 Server dir_proc.c Example

/*
 * dir_proc.c: remote readdir
 * implementation
 */
#include <dirent.h>
#include "dir.h"                /* Created by rpcgen */
 
extern int errno;
extern char *malloc();
extern char *strdup();
 
readdir_res *
readdir_1(dirname, req)
	nametype *dirname;
	struct svc_req *req;
 
{
	DIR *dirp;
	struct dirent *d;
	namelist nl;
	namelist *nlp;
	static readdir_res res; /* must be static! */
	
	/* Open directory */
	dirp = opendir(*dirname);
	if (dirp == (DIR *)NULL) {
		res.errno = errno;
		return (&res);
	}
	/* Free previous result */
	xdr_free(xdr_readdir_res, &res);
	/*
	 * Collect directory entries.
 * Memory allocated here is free by
	 * xdr_free the next time readdir_1
 * is called
	 */
	nlp = &res.readdir_res_u.list;
	while (d = readdir(dirp)) {
		nl = *nlp = (namenode *) 
							malloc(sizeof(namenode));
		if (nl == (namenode *) NULL) {
			res.errno = EAGAIN;
			closedir(dirp);
			return(&res);
		}
		nl->name = strdup(d->d_name);
		nlp = &nl->next;
	}
	*nlp = (namelist)NULL;
	/* Return the result */
	res.errno = 0;
	closedir(dirp);
	return (&res);
}

 
 
 
  Previous   Contents   Next