ONC+ Developer's Guide

Converting Local Procedures to Remote Procedures

Assume that an application runs on a single computer and you want to convert it to run in a "distributed" manner on a network. This example shows the stepwise conversion of this program that writes a message to the system console. Example 3-1 shows the original program.


Example 3-1 Single Process Version of printmesg.c

/* printmsg.c: print a message on the console */
#include <stdio.h>
 
main(argc, argv)
	int argc;
	char *argv[];
{
	char *message;
 
	if (argc != 2) {
		fprintf(stderr, "usage: %s <message>\n",
					argv[0]);
		exit(1);
	}
	message = argv[1];
	if (!printmessage(message)) {
		fprintf(stderr,"%s: couldn't print your
					message\n",argv[0]);
	exit(1);
	}
	printf("Message Delivered!\n");
	exit(0);
}
 
/* Print a message to the console.
 * Return a boolean indicating whether
 * the message was actually printed. */
 
printmessage(msg)
	char *msg;
{
	FILE *f;
 
	f = fopen("/dev/console", "w");
	if (f == (FILE *)NULL) {
		return (0);
	}
	fprintf(f, "%s\n", msg);
	fclose(f);
	return(1);
}

For local use on a single machine, this program could be compiled and executed as follows:

$ cc printmsg.c -o printmsg
 $ printmsg "Hello, there."
 Message delivered!
 $

If the printmessage() function is turned into a remote procedure, it can be called from anywhere in the network. rpcgen makes it easy to do this.

First, determine the data types of all procedure-calling arguments and the result argument. The calling argument of printmessage() is a string, and the result is an integer. We can write a protocol specification in RPC language that describes the remote version of printmessage(). The RPC language source code for such a specification is:

/* msg.x: Remote msg printing protocol */
 program MESSAGEPROG {
     version PRINTMESSAGEVERS {
        int PRINTMESSAGE(string) = 1;
 	 } = 1;
} = 0x20000001;

Remote procedures are always declared as part of remote programs. The code above declares an entire remote program that contains the single procedure PRINTMESSAGE. In this example, the PRINTMESSAGE procedure is declared to be procedure 1, in version 1 of the remote program MESSAGEPROG, with the program number 0x20000001. (See Appendix B, RPC Protocol and Language Specification for guidance on choosing program numbers.) Version numbers are incremented when functionality is changed in the remote program. Existing procedures can be changed or new ones can be added. More than one version of a remote program can be defined and a version can have more than one procedure defined.

Note that the program and procedure names are declared with all capital letters. This is not required, but is a good convention to follow.

Note also that the argument type is string and not char * as it would be in C. This is because a char * in C is ambiguous. char usually means an array of characters, but it could also represent a pointer to a single character. In RPC language, a null-terminated array of char is called a string.

There are just two more programs to write:

Example 3-2 is a remote procedure that implements the PRINTMESSAGE procedure in Example 3-1.


Example 3-2 RPC Version of printmsg.c

/*
 * msg_proc.c: implementation of the
 * remote procedure "printmessage"
 */
#include <stdio.h>
#include "msg.h"				/* msg.h generated by rpcgen */
 
int *
printmessage_1(msg, req)
	char **msg;	
	struct svc_req *req;		/* details of call */
{
	static int result;			/* must be static! */
	FILE *f;
 
	f = fopen("/dev/console", "w");
	if (f == (FILE *)NULL) {
		result = 0;
		return (&result);
	}
	fprintf(f, "%s\n", *msg);
	fclose(f);
	result = 1;
	return (&result);
}

Note that the declaration of the remote procedure printmessage_1() differs from that of the local procedure printmessage() in four ways:

  1. It takes a pointer to the character array instead of the pointer itself. This is true of all remote procedures when the -N option is not used: They always take pointers to their arguments rather than the arguments themselves. Without the -N option, remote procedures are always called with a single argument. If more than one argument is required the arguments must be passed in a struct.

  2. It is called with two arguments. The second argument contains information on the context of an invocation: the program, version, and procedure numbers, raw and canonical credentials, and an SVCXPRT structure pointer (the SVCXPRT structure contains transport information). This information is made available in case the invoked procedure requires it to perform the request.

  3. It returns a pointer to an integer instead of the integer itself. This is also true of remote procedures when the -N option is not used: They return pointers to the result. The result should be declared static unless the -M (multithread) or -A (Auto mode) options are used. Ordinarily, if the result is declared local to the remote procedure, references to it by the server stub are invalid after the remote procedure returns. In the case of -M and -A options, a pointer to the result is passed as a third argument to the procedure, so the result is not declared in the procedure.

  4. An _1 is appended to its name. In general, all remote procedures calls generated by rpcgen are named as follows: the procedure name in the program definition (here PRINTMESSAGE) is converted to all lowercase letters, an underbar (_) is appended to it, and the version number (here 1) is appended. This naming scheme allows multiple versions of the same procedure.

Example 3-3 shows the main client program that calls the remote procedure.


Example 3-3 Client Program to Call printmsg.c

/*
 * rprintmsg.c: remote version
 * of "printmsg.c"
 */
#include <stdio.h>
#include "msg.h"			/* msg.h generated by rpcgen */
 
main(argc, argv)
	int argc;
	char *argv[];
{
	CLIENT *clnt;
	int *result;
	char *server;
	char *message;
 
	if (argc != 3) {
		fprintf(stderr, "usage: %s host 
					message\n", argv[0]);
		exit(1);
	}
 
	server = argv[1];
	message = argv[2];
	/*
	 * Create client "handle" used for 
 * calling MESSAGEPROG on the server
	 * designated on the command line.
	 */
	clnt = clnt_create(server, MESSAGEPROG,
								PRINTMESSAGEVERS,
								"visible");
	if (clnt == (CLIENT *)NULL) {
		/*
		 * Couldn't establish connection
    * with server.
		 * Print error message and die.
		 */
		clnt_pcreateerror(server);
		exit(1);
	}
	
	/*
	 * Call the remote procedure
 * "printmessage" on the server
	 */
	result = printmessage_1(&message, clnt);
	if (result == (int *)NULL) {
		/*
		 * An error occurred while calling 
    * the server.
		 * Print error message and die.
		 */
		clnt_perror(clnt, server);
		exit(1);
	}
	/* Okay, we successfully called 
 * the remote procedure. 
 */
	if (*result == 0) {
		/*
		 * Server was unable to print 
    * our message.
		 * Print error message and die.
		 */
		fprintf(stderr,
		"%s: could not print your message\n",argv[0]);
		exit(1);
	}
 
	/* The message got printed on the
 * server's console
 */
	printf("Message delivered to %s\n",
				server);
	clnt_destroy( clnt );
	exit(0);
}

Note the following about Example 3-3:

  1. First, 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. (The client handle can be created in other ways as well. See Chapter 4, The Programmer's Interface to RPC for details.) If no more calls are to be made using the client handle, destroy it with a call to clnt_destroy() to conserve system resources.

  2. 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 this, see the /etc/netconfig file and its description in Transport Interfaces Programming Guide.

  3. 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. It also returns a pointer to the result instead of the result.

  4. The remote procedure call can fail in two ways. The RPC mechanism can fail or there can be an error 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.

Here are the compile commands for the printmsg example:

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

First, rpcgen was 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.

Here is what rpcgen did with the input file msg.x:

  1. It 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.

  2. It created the client stub routines in the msg_clnt.c file. Here there is only one, the printmessage_1() routine, that 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.

  3. It created 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.

Once created, the server program is installed on a remote machine and run. (If the machines are homogeneous, the server binary can just be copied. If they are not, 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. It is not necessary 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.