ONC+ Developer's Guide

Simplified Interface

The simplified interface is the easiest level to use because it does not require the use of any other RPC routines. It also limits control of the underlying communications mechanisms. Program development at this level can be rapid, and is directly supported by the rpcgen compiler. For most applications, rpcgen and its facilities are sufficient.

Some RPC services are not available as C functions, but they are available as RPC programs. The simplified interface library routines provide direct access to the RPC facilities for programs that do not require fine levels of control. Routines such as rusers() are in the RPC services library librpcsvc. Example 4-1 is a program that displays the number of users on a remote host. It calls the RPC library routine, rusers().


Example 4-1 rusers Program

#include <rpc/rpc.h>
#include <rpcsvc/rusers.h>
#include <stdio.h>
 
/*
 * a program that calls the
 * rusers() service
 */
 
main(argc, argv)
	int argc;
	char **argv;
{
	int num;
 
	if (argc != 2) {
		fprintf(stderr, "usage: %s hostname\n",
						argv[0]);
		exit(1);
	}
	if ((num = rnusers(argv[1])) < 0) {
		fprintf(stderr, "error: rusers\n");
		exit(1);
	}
	fprintf(stderr, "%d users on %s\n", num,
						argv[1] );
	exit(0);
}

Compile the program in Example 4-1 with:

cc program.c -lrpcsvc -lnsl

Client

There is just one function on the client side of the simplified interface: rpc_call(). It has nine parameters:

int	0 or error code
rpc_call (  
       char				*host			/* Name of server host */
       rpcprog_t		prognum		/* Server program number */
       rpcvers_t		versnum		/* Server version number */
       rpcproc_t		procnum		/* Server procedure number */
       xdrproc_t		inproc		/* XDR filter to encode arg */
       char				*in			/* Pointer to argument */
       xdr_proc_t	outproc		/* Filter to decode result */
       char				*out			/* Address to store result */
       char				*nettype		/* For transport selection */
);

This function calls the procedure specified by prognum, versum, and procnum on the host. The argument to be passed to the remote procedure is pointed to by the in parameter, and inproc is the XDR filter to encode this argument. The out parameter is an address where the result from the remote procedure is to be placed. outproc is an XDR filter which will decode the result and place it at this address.

The client blocks on rpc_call() until it receives a reply from the server. If the server accepts, it returns RPC_SUCCESS with the value of zero. It will return a non-zero value if the call was unsuccessful. This value can be cast to the type clnt_stat, an enumerated type defined in the RPC include files and interpreted by the clnt_sperrno() function. This function returns a pointer to a standard RPC error message corresponding to the error code.

In the example, all "visible" transports listed in /etc/netconfig are tried. Adjusting the number of retries requires use of the lower levels of the RPC library.

Multiple arguments and results are handled by collecting them in structures.

The example in Example 4-1, changed to use the simplified interface, looks like Example 4-2.


Example 4-2 rusers Program Using Simplified Interface

#include <stdio.h>
#include <utmpx.h>
#include <rpc/rpc.h>
#include <rpcsvc/rusers.h>
 
/* a program that calls the RUSERSPROG
 * RPC program
 */
 
main(argc, argv)
	int argc;
	char **argv;
{
	unsigned int nusers;
	enum clnt_stat cs;
 
	if (argc != 2) {
		   fprintf(stderr, "usage: rusers hostname\n");
		   exit(1);
	}
	if( cs = rpc_call(argv[1], RUSERSPROG,
			 	RUSERSVERS,	RUSERSPROC_NUM, xdr_void,
					(char *)0, xdr_u_int, (char *)&nusers,
     "visible")  !=  RPC_SUCCESS )  {
		          clnt_perrno(cs);
		          exit(1);
	           }
	fprintf(stderr, "%d users on %s\n", nusers,
						argv[1] );
	exit(0);
}

Since data types may be represented differently on different machines, rpc_call() needs both the type of, and a pointer to, the RPC argument (similarly for the result). For RUSERSPROC_NUM, the return value is an unsigned int, so the first return parameter of rpc_call() is xdr_u_int (which is for an unsigned int) and the second is &nusers (which points to unsigned int storage). Because RUSERSPROC_NUM has no argument, the XDR encoding function of rpc_call() is xdr_void() and its argument is NULL.

Server

The server program using the simplified interface is very straightforward. It simply calls rpc_reg() to register the procedure to be called, and then it calls svc_run(), the RPC library's remote procedure dispatcher, to wait for requests to come in.

rpc_reg() has the following arguments:

rpc_reg (
      rpcprog_t			prognum			/* Server program number */
      rpcvers_t			versnum			/* Server version number */
      rpcproc_t			procnum			/* server procedure number */
      char						*procname		/* Name of remote function */
      xdrproc_t			inproc			/* Filter to encode arg */
      xdrproc_t			outproc			/* Filter to decode result */
      char						*nettype			/* For transport selection */
);

svc_run() invokes service procedures in response to RPC call messages. The dispatcher in rpc_reg() takes care of decoding remote procedure arguments and encoding results, using the XDR filters specified when the remote procedure was registered. Some notes about the server program:

Hand-Coded Registration Routine

You can sometimes implement faster or more compact code than can rpcgen. rpcgen handles the generic code-generation cases. The following program is an example of a hand-coded registration routine. It registers a single procedure and enters svc_run() to service requests.

#include <stdio.h>
 #include <rpc/rpc.h>
 #include <rpcsvc/rusers.h>
 void *rusers();
 
 main()
 {
 	if(rpc_reg(RUSERSPROG, RUSERSVERS,
						RUSERSPROC_NUM, rusers,
 					xdr_void, xdr_u_int,
						"visible") == -1) {
 		fprintf(stderr, "Couldn't Register\n");
 		exit(1);
 	}
 	svc_run();     /* Never returns */
 	fprintf(stderr, "Error: svc_run
						returned!\n");
 	exit(1);
 }

rpc_reg() can be called as many times as is needed to register different programs, versions, and procedures.

Passing Arbitrary Data Types

Data types passed to and received from remote procedures can be any of a set of predefined types, or can be programmer-defined types. RPC handles arbitrary data structures, regardless of different machines' byte orders or structure layout conventions, by always converting them to a standard transfer format called external data representation (XDR) before sending them over the transport. The conversion from a machine representation to XDR is called serializing, and the reverse process is called deserializing.

The translator arguments of rpc_call() and rpc_reg() can specify an XDR primitive procedure, like xdr_u_int(), or a programmer-supplied routine that processes a complete argument structure. Argument processing routines must take only two arguments: a pointer to the result and a pointer to the XDR handle.

Table 4-1 XDR Primitive Type Routines

XDR Primitive Routines 

xdr_int()

xdr_netobj()

xdr_u_long()

xdr_enum()

xdr_long()

xdr_float()

xdr_u_int()

xdr_bool()

xdr_short()

xdr_double()

xdr_u_short()

xdr_wrapstring()

xdr_char()

xdr_quadruple()

xdr_u_char()

xdr_void()

xdr_hyper()

xdr_u_hyper()

For the convenience of ANSI C programmers who are accustomed to the fixed-width integer types found in int_types.h, the routines xdr_char(), xdr_short(), xdr_int(), and xdr_hyper() (and the unsigned versions of each) have equivalent functions with names familiar to ANSI C, as indicated in Table 4-2.

Table 4-2 Primitive Type Equivalences

Function 

Equivalent 

xdr_char()

xdr_int8_t()

xdr_u_char()

xdr_u_int8_t()

xdr_short()

xdr_int16_t()

xdr_u_short()

xdr_u_int16_t()

xdr_int()

xdr_int32_t()

xdr_u_int()

xdr_u_int32_t()

xdr_hyper()

xdr_int64_t()

xdr_u_hyper()

xdr_u_int64_t()

The nonprimitive xdr_string(), which takes more than two parameters, is called from xdr_wrapstring().

For an example of a programmer-supplied routine, the structure:

struct simple {
 	int a;
 	short b;
 } simple;

contains the calling arguments of a procedure. The XDR routine xdr_simple() translates the argument structure as shown in Example 4-3.


Example 4-3 xdr_simple Routine

#include <rpc/rpc.h>
#include "simple.h"
 
bool_t
xdr_simple(xdrsp, simplep)
	XDR *xdrsp;
	struct simple *simplep;
{
	if (!xdr_int(xdrsp, &simplep->a))
		return (FALSE);
	if (!xdr_short(xdrsp, &simplep->b))
		return (FALSE);
	return (TRUE);
}

An equivalent routine can be generated automatically by rpcgen.

An XDR routine returns nonzero (a C TRUE) if it completes successfully, and zero otherwise. A complete description of XDR is provided in Appendix C, XDR Protocol Specification.

Table 4-3
 Prefabricated Routines

xdr_array()

xdr_bytes()

xdr_reference()

xdr_vector()

xdr_union()

xdr_pointer()

xdr_string()

xdr_opaque()

For example, to send a variable-sized array of integers, it is packaged in a structure containing the array and its length:

struct varintarr {
 	int *data;
 	int arrlnth;
 } arr;

Translate the array with xdr_varintarr(), as shown in Example 4-4.


Example 4-4 xdr_varintarr Syntax Use

bool_t
xdr_varintarr(xdrsp, arrp)
	XDR *xdrsp;
	struct varintarr *arrp;
{
	return(xdr_array(xdrsp, (caddr_t)&arrp->data,
		(u_int *)&arrp->arrlnth, MAXLEN,
		sizeof(int), xdr_int));
}

The arguments of xdr_array() are the XDR handle, a pointer to the array, a pointer to the size of the array, the maximum array size, the size of each array element, and a pointer to the XDR routine to translate each array element. If the size of the array is known in advance, use xdr_vector(), as shown in Example 4-5.


Example 4-5 xdr_vector Syntax Use

int intarr[SIZE];

bool_t
xdr_intarr(xdrsp, intarr)
	XDR *xdrsp;
	int intarr[];
{
	return (xdr_vector(xdrsp, intarr, SIZE,
				sizeof(int),
xdr_int));
}

XDR converts quantities to 4-byte multiples when serializing. For arrays of characters, each character occupies 32 bits. xdr_bytes() packs characters. It has four parameters similar to the first four parameters of xdr_array().

Null-terminated strings are translated by xdr_string(). It is like xdr_bytes() with no length parameter. On serializing it gets the string length from strlen(), and on deserializing it creates a null-terminated string.

Example 4-6 calls the built-in functions xdr_string() and xdr_reference(), which translates pointers to pass a string, and struct simple from the previous examples.


Example 4-6 xdr_reference Syntax Use

struct finalexample {
	char *string;
	struct simple *simplep;
} finalexample;

bool_t
xdr_finalexample(xdrsp, finalp)
	XDR *xdrsp;
	struct finalexample *finalp;
{
	if (!xdr_string(xdrsp, &finalp->string,
			MAXSTRLEN))
		return (FALSE);
	if (!xdr_reference( xdrsp, &finalp->simplep,
			sizeof(struct simple), xdr_simple))
		return (FALSE);
	return (TRUE);

}

Note that xdr_simple() could have been called here instead of xdr_reference().