Programming with Remote Procedure Calls (RPC)


The RPC architecture is designed so that clients send a call message, and wait for servers to reply that the call succeeded. This implies that clients do not compute while servers are processing a call. This is inefficient if the client does not want or need an acknowledgement for every message sent. It is possible for clients to continue computing while waiting for a response, using RPC batch facilities.

RPC messages can be placed in a ``pipeline'' of calls to a desired server; this is called batching. Batching assumes that:

Because the server does not respond to every call, the client can generate new calls in parallel with the server executing previous calls. Furthermore, the TCP/IP implementation can buffer up many call messages, and send them to the server in one write(2) system call. This overlapped execution greatly decreases the interprocess communication overhead of the client and server processes, and the total elapsed time of a series of calls.

Because the batched calls are buffered, the client should eventually do a nonbatched call to flush the pipeline.

An example of batching follows. Assume a string rendering service (like a window system) has two similar calls: one renders a string and returns void results, while the other renders a string and remains silent. The service (using a reliable byte stream transport) may look like:

   #include <stdio.h>
   #include <rpc/rpc.h>
   #include "windows.h"

void windowdispatch();

main () { int num;

num = svc_create(windowdispatch, WINDOWPROG, WINDOWVERS, "tcp"); if (num == 0){ fprintf(stderr, "can't create an RPC server\n"); exit(1); } svc_run(); /* Never returns */ fprintf(stderr, "should never reach this point\n"); }

void windowdispatch(rqstp, transp) struct svc_req *rqstp; SVCXPRT *transp; { char *s = NULL;

switch (rqstp->rq_proc) { case NULLPROC: if (!svc_sendreply(transp, xdr_void, 0)) fprintf(stderr, "can't reply to RPC call\n"); return; case RENDERSTRING: if (!svc_getargs(transp, xdr_wrapstring, &s)) { fprintf(stderr, "can't decode arguments\n"); /* * Tell caller an error occurred */ svcerr_decode(transp); break; } /* * Code here to render the string ``s'' */ if (!svc_sendreply(transp, xdr_void, NULL)) fprintf(stderr, "can't reply to RPC call\n"); break; case RENDERSTRING_BATCHED: if (!svc_getargs(transp, xdr_wrapstring, &s)) { fprintf(stderr, "can't decode arguments\n"); /* * We are silent in the face of protocol errors */ break; } /* * Code here to render string s, but send no reply! */ break; default: svcerr_noproc(transp); return; } /* * Now free string allocated while decoding arguments */ svc_freeargs(transp, xdr_wrapstring, &s); }

Of course, the service could have one procedure that takes the string and a boolean that specifies whether the procedure should respond.

To take advantage of batching (using the code above), the client must make RPC calls on a reliable byte stream transport. The calls must have the following attributes:

This is an example of a client that uses batching to render a bunch of strings; the batching is flushed when the client gets a null string (EOF):

   #include <stdio.h>
   #include <rpc/rpc.h>
   #include "windows.h"

main(argc, argv) int argc; char **argv; { struct timeval total_timeout; register CLIENT *client; enum clnt_stat clnt_stat; char buf[1000], *s = buf;

if ((client = clnt_create(argv[1], WINDOWPROG, WINDOWVERS, "tcp")) == NULL) { clnt_pcreateerror("clnt_create"); exit(1); } total_timeout.tv_sec = 0; total_timeout.tv_usec = 0; while (scanf("%s", s) != EOF) { clnt_call(client, RENDERSTRING_BATCHED, xdr_wrapstring, &s, xdr_void, NULL, total_timeout); }

/* Now flush the pipeline */

total_timeout.tv_sec = 20; clnt_stat = clnt_call(client, NULLPROC, xdr_void, NULL, xdr_void, NULL, total_timeout); if (clnt_stat != RPC_SUCCESS) { clnt_perror(client, "rpc"); exit(1); } clnt_destroy(client); exit(0); }

Because the server sends no message, the clients cannot be notified of any of the failures that may occur.

Batching performance

The example of batching was completed to render all the lines in a 2000 line file. The rendering service did nothing but throw the lines away.

The example was run in four configurations, with the following results:

Configuration RPC Time
Machine to itself Regular 50 seconds
Machine to itself Batched 16 seconds
Machine to another Regular 52 seconds
Machine to another Batched 10 seconds

Running fscanf on the same file only requires six seconds. These timings show the advantage of protocols that allow for overlapped execution.

© 2005 The SCO Group, Inc. All rights reserved.
SCO OpenServer Release 6.0.0 -- 02 June 2005