[Return to Library] [Contents] [Previous Chapter] [Next Section] [Next Chapter] [Index] [Help]


2    Writing RPC Applications with the rpcgen Protocol Compiler

The rpcgen protocol compiler accepts a remote program interface definition written in RPC language, which is similar to C. It then produces C language output consisting of skeleton versions of the client routines, a server skeleton, XDR filter routines for both parameters and results, a header file that contains common definitions, and optionally, dispatch tables that the server uses to invoke routines that are based on authorization checks. See rpcgen(1) for more information.

The client skeleton interface to the RPC library hides the network from its callers, and the server skeleton hides the network from the server procedures invoked by remote clients. You compile and link output files from rpcgen as usual. The server code generated by rpcgen supports inetd. You can start the server via inetd or at the command line.

You can write server procedures in any language that has system calling conventions. To get an executable server program, link the server procedure with the server skeleton from rpcgen. To create an executable program for a remote program, write an ordinary main program that makes local procedure calls to the client skeletons, and link the program with the rpcgen skeletons. If necessary, the rpcgen options enable you to suppress skeleton generation and specify the transport to be used by the server skeleton.

The rpcgen protocol compiler helps to reduce development time in the following ways:

Refer to rpcgen(1) for more information about programming applications that use remote procedure calls or for writing XDR routines that convert procedure arguments and results into their network format (or vice versa). For a discussion of RPC programming without rpcgen, see Chapter 3.


[Return to Library] [Contents] [Previous Chapter] [Next Section] [Next Chapter] [Index] [Help]


2.1    Simple Example: Using rpcgen to Generate Client and Server RPC Code

This section shows how to convert a simple routine -- one that prints messages to the console of a single machine -- to an ONC RPC application that runs remotely over the network. To do this, the rpcgen protocol compiler is used to generate client and server RPC code. Example 2-1 shows the routine before conversion.

Example 2-1: Printing a Remote Message Without ONC RPC

/* 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 showing result. */ printmessage(msg) char *msg; { FILE *f;
 
f = fopen("/dev/console", "w"); if (f == NULL) { return (0); } fprintf(f, "%s\n", msg); fclose(f); return (1); }

Compile and run this program from a machine called cyrkle:

cyrkle%  cc printmsg.c -o printmsg
cyrkle%  printmsg "red rubber ball"

Message delivered!
cyrkle% 

If the printmessage procedure at the bottom of the printmsg.c program of Example 2-1 were converted into a remote procedure, you could call it from anywhere in the network, instead of only from the program where it is embedded. Before doing this, it is necessary to write a protocol specification in RPC language that describes the remote procedure, as shown in the next section.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.1.1    RPC Protocol Specification File Describing Remote Procedure

To create the specification file, you must know all the input and output parameter types. In Example 2-1, the printmessage procedure takes a string as input, and returns an integer as output. Example 2-2 is the RPC protocol specification file that describes the remote version of the printmessage procedure.

Example 2-2: RPC Protocol Specification File, Simple Example

/*
 * msg.x: Remote message printing protocol
 */

 
program MESSAGEPROG { version MESSAGEVERS { int PRINTMESSAGE(string) = 1; } = 1; } = 0x20000099;

Remote procedures are part of remote programs, so Example 2-2 actually declares a remote program containing a single procedure, PRINTMESSAGE. By convention, all RPC services provide for a NULL procedure (procedure 0), normally used for "pinging." (See ping(8).) The RPC protocol specification file in Example 2-2 declares the PRINTMESSAGE procedure to be in version 1 of the remote program. No NULL procedure (procedure 0) is necessary in the protocol definition because rpcgen generates it automatically and the user does not need it.

In RPC language, the convention (though not a requirement) is to make all declarations in uppercase characters. Notice that the argument type is string, not char *, because char * in C is ambiguous. Programmers usually intend it to mean a null-terminated string of characters, but it could also be a pointer to a single character or to an array of characters. In RPC language, a null-terminated string is unambiguously of type string.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.1.2    Implementing the Procedure Declared in the Protocol Specification

Example 2-3 defines the remote procedure declared in the RPC protocol specification file of the previous example.

Example 2-3: Remote Procedure Definition

/*
 * msg_proc.c: implementation of the remote procedure
 * "printmessage"
 */

 
#include <stdio.h> #include <rpc/rpc.h> /* always needed */ #include "msg.h" /* msg.h will be generated by rpcgen */
 
/* * Remote version of "printmessage" */ int * printmessage_1(msg) [1] char **msg; [2] { static int result; /* must be static! */ FILE *f;
 
f = fopen("/dev/console", "w"); if (f == NULL) { result = 0; return (&result); } fprintf(f, "%s\n", *msg); fclose(f); result = 1; return (&result); [3] }

In this example, the declaration of the remote procedure, printmessage_1, differs from that of the local procedure printmessage in three ways:

  1. It has _1 appended to its name. In general, all remote procedures called by rpcgen are named by the following rule: the name in the procedure definition (here, PRINTMESSAGE) is converted to all lowercase letters, and an underscore (_) and version number (here, 1) is appended to it. [Return to example]

  2. It takes a pointer to a string instead of a string itself. This is true of all remote procedures -- they always take pointers to their arguments rather than the arguments themselves; if there are no arguments, specify void. [Return to example]

  3. It returns a pointer to an integer instead of an integer itself. This is also characteristic of remote procedures -- they return pointers to their results. Therefore it is important to have the result declared as a static; if there are no arguments, specify void. [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.1.3    The Client Program That Calls the Remote Procedure

Example 2-4 declares the main client program, rprintmsg.c, that will call the remote procedure.

Example 2-4: Client Program that Calls the Remote Procedure

/*
 * rprintmsg.c: remote version of "printmsg.c"
 */

 
#include <stdio.h> #include <rpc/rpc.h> /* always needed */ #include "msg.h" /* msg.h will be generated by rpcgen */
 
main(argc, argv) int argc; char *argv[]; { CLIENT *cl; 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. We tell * the RPC package to use the TCP protocol when * contacting the server. */
 
cl = clnt_create(server, MESSAGEPROG, MESSAGEVERS, "tcp"); [1] [2] if (cl == NULL) {
 
/* * Couldn't establish connection with server. * Print error message and stop. */
 
clnt_pcreateerror(server); exit(1); }
 
/* * Call the remote procedure "printmessage" on the server */
 
result = printmessage_1(&message, cl); [3] if (result == NULL) { [4]
 
/* * An error occurred while calling the server. * Print error message and stop. */
 
clnt_perror(cl, server); exit(1); }
 
/* * Okay, we successfully called the remote procedure. */
 
if (*result == 0) { [4]
 
/* * Server was unable to print our message. * Print error message and stop. */
 
fprintf(stderr, "%s: %s couldn't print your message\n", argv[0], server); exit(1); }
 
/* * The message got printed on the server's console */
 
printf("Message delivered to %s!\n", server); exit(0); }

In this example, the following events occur:

  1. A client "handle" is created first, using the RPC library routine clnt_create(). This client handle will be passed to the skeleton routines that call the remote procedure. [Return to example]

  2. The last parameter to clnt_create is tcp, the transport on which you want to run your application. (Alternatively udp could have been used.) [Return to example]

  3. The remote procedure printmessage_1 is called exactly the same way as in msg_proc.c, except for the inserted client handle as the second argument. [Return to example]

  4. The remote procedure call can fail in two ways: the RPC mechanism itself can fail or there can be an error in the execution of the remote procedure. In the former case, the remote procedure, print_message_1, returns with a NULL. In the latter case, error reporting is application-dependent. In this example, the error is reported via *result. [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.1.4    Running rpcgen

Use the rpcgen protocol compiler on the RPC protocol specification file, msg.x, (from Example 2-2) to generate client and server RPC code automatically:

cyrkle%  rpcgen msg.x

Using rpcgen like this -- without options -- automatically creates the following files from the input file msg.x:

Note

The -T option of rpcgen creates an additional output file of index information for dispatching service routines.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.1.5    Compiling the Client and Server Programs

After the rpcgen protocol compilation, use two cc compilation statements to create a client program and a server program:


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.1.6    Copying the Server to a Remote Machine and Running It

Copy the server program msg_server to a remote machine called space in this example. Then, run it in background mode there -- indicated by &:

space%  msg_server &

Note

Servers generated by rpcgen can be invoked with port monitors like inetd, as well as from the command line, if they are invoked with the -I option.

From a local machine (earth) you can now print a message on the console of the remote machine space:

earth%  rprintmsg space "Hello out there..."

The message Hello out there... will appear on the console of the machine space. You can print a message on any console (including your own) with this program if you copy the server to that machine and run it.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.2    Advanced Example: Using rpcgen to Generate XDR Routines

Section 2.1 explains how to use rpcgen to generate client and server RPC code automatically to convert a simple procedure to one that can be run remotely over the network. The rpcgen protocol compiler can also generate the external data representation (XDR) routines that convert local data structures into network format (and vice versa).

The following sections present a more advanced example of a complete RPC service -- a remote directory listing service that uses rpcgen to generate both skeleton and XDR routines.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.2.1    The RPC Protocol Specification

As with the simple example, you must first create an RPC protocol specification file. This file, dir.x, is for a remote directory listing, shown in !A spec_file_adv_ex .

Example 2-5: RPC Protocol Specification File, Advanced Example

/*
 * dir.x: Remote directory listing protocol
 */

 
/* maximum length of a directory entry */ const MAXNAMELEN = 255;
 
/* a directory entry */ typedef string nametype<MAXNAMELEN>;
 
/* a link in the listing */ typedef struct namenode *namelist;
 
/* * 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;

Note

You can define types (like readdir_res in Example 2-5) by using the struct, union, and enum keywords, but do not use these keywords in later variable declarations of those types. For example, if you define union foo, you must declare it later by using foo, not union foo. The rpcgen protocol compiler compiles RPC unions into C structures, so it is an error to declare them later by using the union keyword.

Running rpcgen on dir.x creates four output files:

The first three files have already been described. The fourth file, dir_xdr.c, contains the XDR routines that convert the declared data types into XDR format (and vice versa). For each data type used in the .x file, rpcgen assumes that the RPC/XDR library contains a routine with the name of that data type prefixed by xdr_, for example, xdr_int. If the data type was defined in the .x file, then rpcgen generates the required XDR routine. If there are no such data types, then the file (for example, dir_xdr.c) will not be generated. If the data types were used but not defined, then the user has to provide that XDR routine. This enables you to create your own customized XDR routines.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.2.2    Implementing the Procedure Declared in the Protocol Specification

Example 2-6 consists of the dir_proc.c file that implements the remote READDIR procedure from the previous RPC protocol specification file.

Example 2-6: Remote Procedure Implementation

/*
 * dir_proc.c: remote readdir implementation
 */
#include <rpc/rpc.h>            /* Always needed */
#include <sys/dir.h>
#include "dir.h"                /* Created by rpcgen */

 
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); }


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.2.3    The Client Program that Calls the Remote Procedure

Example 2-7 shows the client side program, rls.c, that calls the remote server procedure.

Example 2-7: Client Program that Calls the Server

/*
 * 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); }
 
server = argv[1]; dir = argv[2];
 
/* * Create client "handle" used for calling DIRPROG on * the server designated on the command line. Use * the tcp protocol when contacting the server. */ cl = clnt_create(server, DIRPROG, DIRVERS, "tcp"); if (cl == NULL) { /* * Couldn't establish connection with server. * Print error message and stop. */ clnt_pcreateerror(server); exit(1); }
 
/* * Call the remote procedure readdir on the server */ result = readdir_1(&dir, cl); if (result == NULL) { /* * An RPC error occurred while calling the server. * Print error message and stop. */ 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 stop. */ 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); }


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.2.4    Running rpcgen

As with the simple example, you must run the rpcgen protocol compiler on the RPC protocol specification file dir.x, to create a header file, dir.h, an output file of client skeletons, dir_clnt.c, and a server program, dir_svc.c. For this advanced example, rpcgen also generates the file of XDR routines, dir_xdr.c:

earth%  rpcgen dir.x


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.2.5    Compiling the File of XDR Routines

The next step is to compile the file of XDR routines, dir_xdr.c, with the following cc -c command:

earth%  cc -c dir_xdr.c

Here, the -c option is used to suppress the loading phase of the compilation and to produce an object file, even if only one program is compiled.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.2.6    Compiling the Client Side Program with rpcgen Output

Next, the remote directory listing client program, rls.c, is compiled with the file of client skeletons, dir_clnt.c, and the object file of the previous compilation, dir_xdr.o. The -o option places the executable output of the compilation into the file rls:

earth%  cc rls.c dir_clnt.c dir_xdr.o -o rls


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.2.7    Compiling the Server Side Program with rpcgen Output

The following statement compiles three files together: the server program from the original rpcgen compilation, dir_svc.c; the remote READDIR implementation program, dir_proc.c; and the object file, dir_xdr.o, produced by the recent cc compilation of the file of XDR routines. Through the -o option, the executable output is placed in the file dir_svc:

earth%  cc dir_svc.c dir_proc.c dir_xdr.o -o dir_svc


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.2.8    Running the Remote Directory Program

To run the remote directory program, use the new dir_svc and rls commands. The following statement runs the dir_svc command in background mode on the local machine earth:

earth%  dir_svc &

The rls command can then be used from the remote machine space to provide a directory listing on the machine where dir_svc is running in background mode. The command and output (a directory listing of /usr/pub on machine earth) is shown here:

space%  rls earth /usr/pub

.
..
ascii
eqnchar
kbd
marg8
tabclr
tabs
tabs4

Note

Client code generated by rpcgen does not release the memory allocated for the results of the RPC call. You can call xdr_free to deallocate the memory when no longer needed. This is similar to calling free, except that you must also pass the XDR routine for the result. For example, after printing the directory listing in the previous example, you could call xdr_free as follows:

xdr_free(xdr_readdir_res, result);


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.3    Debugging Applications

It is difficult to debug distributed applications that have separate client and server processes. To simplify this, you can test the client program and the server procedure as a single program by linking them with each other rather than with the client and server skeletons. To do this, you must first remove calls to client creation RPC library routines (for example, clnt_create). To create the single debuggable file rls, use the -o option when you compile rls.c, dir_clnt.c, dir_proc.c, and dir_xdr.c together as follows:

cc rls.c dir_clnt.c dir_proc.c dir_xdr.c -o rls

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 skeleton produced by rpcgen and the server procedures can be linked to the server skeleton produced by rpcgen.

There are two kinds of errors possible in an RPC call:


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.4    The C-Preprocessor

The C-preprocessor, cpp, runs on all input files before they are compiled, so all the preprocessor directives are legal within an .x file. Five macro identifiers may have been defined, depending upon which output file is being generated. The following table lists these macros:
Identifier Usage
RPC_HDR For header-file output
RPC_XDR For XDR routine output
RPC_SVC For server-skeleton output
RPC_CLNT For client-skeleton output
RPC_TBL For index-table output

Also, rpcgen does some additional preprocessing of the input file. Any line that begins with a percent sign (%) passes directly into the output file, without any interpretation. Example 2-8 demonstrates this processing feature.

Example 2-8: Using the Percent Sign to Bypass Interpretation of a Line

/*
 * time.x: Remote time protocol
 */
program TIMEPROG {
                version TIMEVERS {
                                unsigned int TIMEGET(void) = 1;
                } = 1;
} = 44;

 
#ifdef RPC_SVC %int * %timeget_1() %{ % static int thetime; % % thetime = time(0); % return (&thetime); %} #endif

Using the percent sign feature does not guarantee that rpcgen will place the output where you intend. If you have problems of this type, do not use this feature.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.5    rpcgen Programming

The following sections contain additional rpcgen programming information about network types, defining symbols, inetd support, and dispatch tables.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.5.1    Network Types

By default, rpcgen generates server code for both UDP and TCP transports. The -s flag creates a server that responds to requests on the specified transport. The following example creates a UDP server:

rpcgen -s udp proto.x


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.5.2    User-Provided Define Statements

The rpcgen protocol compiler provides a way to define symbols and assign values to them. These defined symbols are passed on to the C preprocessor when it is invoked. This facility is useful when, for example, invoking debugging code that is enabled only when the DEBUG symbol is defined. For example:

rpcgen -DDEBUG proto.x


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.5.3    inetd Support

The rpcgen protocol compiler can create RPC servers that can be invoked by inetd when a request for that service is received.

rpcgen -I proto.x

The server code in proto_svc.c supports inetd. For more information on setting up the entry for RPC services in /etc/inetd.conf, see Section 3.3.6.

In many applications, it is useful for services to wait after responding to a request, on the chance that another will soon follow. However, if there is no call within a certain time (by default, 120 seconds), the server exits and the port monitor continues to monitor requests for its services. You can use the -K option to change the default waiting time. In the following example, the server waits only 20 seconds before exiting:

rpcgen -I -K 20 proto.x

If you want the server to exit immediately, use -K 0; if you want the server to wait forever (a normal server situation), use -K -1.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.5.4    Dispatch Tables

Dispatch tables are often useful. For example, the server dispatch routine may need to check authorization and then invoke the service routine, or a client library may need to control all details of storage management and XDR data conversion. The following rpcgen command generates RPC dispatch tables for each program defined in the protocol description file, proto.x, and places them in the file proto_tbl.i (the suffix .i indicates index):

rpcgen -T proto.x

Each entry in the table is a struct rpcgen_table defined in the header file, proto.h, as follows:

struct rpcgen_table {
        char        *(*proc)();
        xdrproc_t   xdr_arg;
        unsigned    len_arg;
        xdrproc_t   xdr_res;
        unsigned    len_res;
};

In this proto.h definition, proc is a pointer to the service routine, xdr_arg is a pointer to the input (argument) xdr_routine, len_arg is the length in bytes of the input argument, xdr_res is a pointer to the output (result) xdr_routine, and len_res is the length in bytes of the output result.

The table dirprog_1_table is indexed by procedure number. The variable dirprog_1_nproc contains the number of entries in the table. The find_proc routine in Example 2-9 shows how to locate a procedure in the dispatch tables.

Example 2-9: Locating a Procedure in a Dispatch Table

struct rpcgen_table *
find_proc(proc)
        long    proc;
{
        if (proc >= dirprog_1_nproc)

 
/* error */ else return (&dirprog_1_table[proc]); }

Each entry in the dispatch table contains a pointer to the corresponding service routine. However, the service routine is not defined in the client code. To avoid generating unresolved external references, and to require only one source file for the dispatch table, the actual service routine initializer is RPCGEN_ACTION(proc_ver). With this, you can include the same dispatch table in both the client and the server. Use the following define statement when compiling the client:

#define RPCGEN_ACTION(routine)  0

Next, use the following define statement when compiling the server:

#define RPCGEN_ACTION(routine)  routine


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.6    Client Programming

The following sections contain client programming information about default timeouts and client authentication.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.6.1    Timeout Changes

RPC sets a default timeout of 25 seconds for RPC calls when clnt_create is used. RPC waits for 25 seconds to get the results from the server. If it does not, then this usually means one of the following conditions exists:

In such cases, the function returns NULL; you can print the error with clnt_perrno.

Sometimes you may need to change the timeout value to accommodate the application or because the server is too slow or far away. Change the timeout by using clnt_control. The code segment in Example 2-10 demonstrates the use of clnt_control.

Example 2-10: Using the clnt_control Routine

struct timeval tv;
CLIENT *cl;

 
cl = clnt_create("somehost", SOMEPROG, SOMEVERS, "tcp"); if (cl == NULL) { exit(1); } tv.tv_sec = 60; /* change timeout to 1 minute */ tv.tv_usec = 0; /* this should always be set */ clnt_control(cl, CLSET_TIMEOUT, &tv);


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.6.2    Client Authentication

By default, client creation routines do not handle client authentication. Sometimes, you may want the client to authenticate itself to the server. This is easy to do, as shown in the following coding:

CLIENT *cl;

 
cl = client_create("somehost", SOMEPROG, SOMEVERS, "udp"); if (cl != NULL) { /* To set UNIX style authentication */ cl->cl_auth = authunix_create_default(); }

For more information on authentication, see Section 3.3.5.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.7    Server Programming

The following sections contain server programming information about system broadcasts and passing data to server procedures.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.7.1    Handling Broadcasts

Sometimes, clients broadcast to determine whether a particular server exists on the network, or to determine all the servers for a particular program and version number. You make these calls with clnt_broadcast (for which there is no rpcgen support). Refer to Section 3.3.2.

When a procedure is known to be called via broadcast RPC, it is best for the server not to reply unless it can provide useful information to the client. Otherwise, the network can be overloaded with useless replies. To prevent the server from replying, a remote procedure can return NULL as its result; the server code generated by rpcgen can detect this and prevent a reply.

The following example shows a procedure that replies only if it acts as an NFS server:

void *
reply_if_nfsserver()
{
        char notnull;   /* just here so we can use its address */

 
if (access("/etc/exports", F_OK) < 0) { return (NULL); /* prevent RPC from replying */ } /* * return non-null pointer so RPC will send out a reply */ return ((void *)&notnull); }

If a procedure returns type void *, it must return a non-NULL pointer if it wants RPC to reply for it.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.7.2    Passing Data to Server Procedures

Server procedures often need to know more about an RPC call than just its arguments. For example, getting authentication information is useful to procedures that want to implement some level of security. This information is supplied to the server procedure as a second argument. (For details see the structure of svc_req in Section 3.3.5.2.) The following example shows the use of svc_req, where the previous printmessage_1 procedure is rewritten to allow only root users to print a message to the console:

int *
printmessage_1(msg, rqstp)
        char **msg;
        struct svc_req  *rqstp;
{
        static int result;      /* Must be static */
        FILE *f;
        struct authunix_parms *aup;

 
aup = (struct authunix_parms *)rqstp->rq_clntcred; if (aup->aup_uid != 0) { result = 0; return (&result); }
 
/* * Same code as before. */ }


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.8    RPC and XDR Languages

RPC language is an extension of XDR language, through the addition of the program and version types. The XDR language is similar to C. For a complete description of the XDR language syntax, see the External Data Representation Standard: Protocol Specification RFC 1014. For a description of the RPC extensions to the XDR language, see the Remote Procedure Calls: Protocol Specification RFC 1050.

The following sections describe the syntax of the RPC and XDR languages, with examples and descriptions of how the various RPC and XDR type definitions are compiled into C type definitions in the output header file.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.8.1    Definitions

An RPC language file consists of a series of definitions:

        definition-list:
                definition ";"
                definition ";" definition-list

RPC recognizes the following definition types:

        definition:
                enum-definition
                typedef-definition
                const-definition
                declaration-definition
                struct-definition
                union-definition
                program-definition


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.8.2    Enumerations

XDR enumerations have the same syntax as C enumerations:

        enum-definition:
                "enum" enum-ident "{"
                        enum-value-list
                "}"

 
enum-value-list: enum-value enum-value "," enum-value-list
 
enum-value: enum-value-ident enum-value-ident "=" value

A comparison of the next two examples show how close XDR language is to C by showing an XDR language enum (Example 2-11), followed by the C language enum that results after compilation (Example 2-12).

Example 2-11: XDR enum Before Compilation

        enum colortype {
                RED = 0,
                GREEN = 1,
                BLUE = 2
        };

Example 2-12: C enum Resulting from Compiling XDR enum

        enum colortype {
                RED = 0,
                GREEN = 1,
                BLUE = 2,
        };
        typedef enum colortype colortype;


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.8.3    Typedefs

XDR typedefs have the same syntax as C typedefs:

        typedef-definition:
                "typedef" declaration

The following example in XDR defines a fname_type that declares file name strings with a maximum length of 255 characters:

typedef string fname_type<255>;

The following example shows the corresponding C definition for this:

typedef char *fname_type;


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.8.4    Constants

XDR constants are used wherever an integer constant is used (for example, in array size specifications), as shown by the following syntax:

        const-definition:
                "const" const-ident "=" integer

The following XDR example defines a constant DOZEN equal to 12:

        const DOZEN = 12;

The following example shows the corresponding C definition for this:

        #define DOZEN 12


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.8.5    Declarations

XDR provides only four kinds of declarations, shown by the following syntax:

        declaration:
                simple-declaration        [1]
                fixed-array-declaration       [2]
                variable-array-declaration        [3]
                pointer-declaration           [4]

The syntax for each, followed by examples, is listed here:

  1. Simple declarations

            simple-declaration:
                    type-ident variable-ident
    

    For example, colortype color in XDR, is the same in C: colortype color. [Return to example]

  2. Fixed-length array declarations

            fixed-array-declaration:
                    type-ident variable-ident "[" value "]"
    

    For example, colortype palette[8] in XDR, is the same in C: colortype palette[8]. [Return to example]

  3. Variable-length array declarations

    These have no explicit syntax in C, so XDR creates its own by using angle brackets, as in the following syntax:

    variable-array-declaration:
            type-ident variable-ident "<" value ">"
            type-ident variable-ident "<" ">"
    

    The maximum size is specified between the angle brackets; it may be omitted, indicating that the array can be of any size, as shown in the following example:

            int heights<12>;        /* at most 12 items */
            int widths<>;           /* any number of items */
    

    Variable-length arrays have no explicit syntax in C, so each of their declarations is compiled into a struct. For example, the heights declaration is compiled into the following struct:

        struct {
            u_int heights_len; /* number of items in array */
            int *heights_val;  /* pointer to array */
        } heights;
    

    Here, the _len component stores the number of items in the array and the _val component stores the pointer to the array. The first part of each of these component names is the same as the name of the declared XDR variable. [Return to example]

  4. Pointer declarations

    These are the same in XDR as in C. You cannot send pointers over the network, but you can use XDR pointers to send recursive data types, such as lists and trees. In XDR language, this type is called optional-data, not pointer, as in the following syntax:

    optional-data:
            type-ident "*"variable-ident
    

    An example of this (the same in both XDR and C) follows:

            listitem *next;
    

    [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.8.6    Structures

XDR declares a struct almost exactly like its C counterpart. The XDR syntax is the following:

        struct-definition:
                "struct" struct-ident "{"
                        declaration-list
                "}"

 
declaration-list: declaration ";" declaration ";" declaration-list

The following example shows an XDR structure to a two-dimensional coordinate, followed by the C structure into which it is compiled in the output header file:

        struct coord {
                int x;
                int y;
        };


The following example shows the C structure that results from compiling the previous XDR structure:

        struct coord {
                int x;
                int y;
        };
        typedef struct coord coord;

Here, the output is identical to the input, except for the added typedef at the end of the output. This enables the use of coord instead of struct coord in declarations.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.8.7    Unions

XDR unions are discriminated unions, and are different from C unions. They are more analogous to Pascal variant records than to C unions. The syntax is shown here:

        union-definition:
                "union" union-ident "switch" ("simple declaration") "{"
                        case-list
                "}"

 
case-list: "case" value ":" declaration ";" "case" value ":" declaration ";" case-list "default" ":" declaration ";"

The following is an example of a type that might be returned as the result of a read data. If there is no error, it returns a block of data; otherwise, it returns nothing:

        union read_result switch (int errno) {
        case 0:
                opaque data[1024];
        default:
                void;
        };

This coding is compiled into the following:

        struct read_result {
                int errno;
                union {
                        char data[1024];
                } read_result_u;
        };
        typedef struct read_result read_result;

Notice that the union component of the output struct has the same name as the structure type name, except for the suffix, _u.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


2.8.8    Programs

You declare RPC programs using the following syntax:

program-definition:
        "program" program-ident "{"
                version-list
        "}" "=" value

 
version-list: version ";" version ";" version-list
 
version: "version" version-ident "{" procedure-list "}" "=" value
 
procedure-list: procedure ";" procedure ";" procedure-list
 
procedure: type-ident procedure-ident "("type-ident")" "=" value

Example 2-13 shows a program of time protocol.

Example 2-13: RPC Program Illustrating Time Protocol

/*
 * time.x: Get or set the time.  Time is represented as number
 * of seconds since 0:00, January 1, 1970.
 */
program TIMEPROG {
        version TIMEVERS {
                unsigned int TIMEGET(void) = 1;
                void TIMESET(unsigned) = 2;
        } = 1;
} = 44;

This file compiles into the following #define statements in the output header file:

#define TIMEPROG 44
#define TIMEVERS 1
#define TIMEGET 1
#define TIMESET 2


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Chapter] [Index] [Help]


2.8.9    Special Cases

The following list describes exceptions to the syntax rules described in the previous sections: