|
|
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 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"Of course, the service could have one procedure that takes the string and a boolean that specifies whether the procedure should respond.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); }
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"Because the server sends no message, the clients cannot be notified of any of the failures that may occur.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); }
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.