|
|
Occasionally, it is useful to have a server become a client, and make an RPC call back to the client process. An example is remote debugging, where the client is a window system program, and the server is a debugger running on the remote machine. Most of the time, the user clicks a mouse button at the debugging window, which converts this to a debugger command, and then makes an RPC call to the server (where the debugger is actually running), telling it to execute that command. However, when the debugger hits a breakpoint, the roles are reversed, and the debugger wants to make an rpc call to the window program, so that it can inform the user that a breakpoint has been reached.
To do an RPC callback, a program number is needed to make the RPC call. Because this will be a dynamically generated program number, it should be in the transient range, 0x40000000 - 0x5fffffff. In the following example, the routine gettransient returns a valid program number in the transient range, and registers it with rpcbind. The call to rpcb_set is a test and set operation, in that it indivisibly tests whether a program number has already been registered, and if it has not, then reserves it.
#include <stdio.h> #include <rpc/rpc.h> #include <netconfig.h>The following program illustrates how to use the gettransient routine. The client makes an RPC call to the server, passing it a transient program number. Then the client waits around to receive a callback from the server at that program number. The server registers the program EXAMPLEPROG, so that it can receive the RPC call informing it of the callback program number. Then at some random time (on receiving an ALRM signal in this example), it sends a callback RPC call, using the program number it received earlier.gettransient(vers, nconf, address) int vers; struct netconfig *nconf; struct netbuf *address; { static int prog = 0x40000000;
while (! rpcb_set(prog++, vers, nconf, address)) continue; return (prog - 1); }
/* * client */ #include <stdio.h> #include <rpc/rpc.h> #include <netconfig.h> #include "example.h"This example shows how svc_tli_create can be used when it is necessary to explicitly choose the program number by calling rpcb_set until it succeeds. (Here it was not required that a service be registered on a given transport, and the example could simply have used a ``generic'' network type.) After creating the handle, svc_reg is called (with the last parameter given as NULL) to register the dispatch function with the dispatcher. Once the server side is ready, it then notifies the actual server of its dynamic program number with rpc_call. On success it then waits for requests from the remote server.main(argc, argv) int argc; char **argv; { SVCXPRT *xprt; struct netconfig *nconf; int prognum; enum clnt_stat stat;
if (argc != 3) { fprintf(stderr, "usage: clnt host transport\n"); exit (1); } nconf = getnetconfigent(argv[2]); if (nconf == NULL) { fprintf(stderr, "unknown transport\n"); exit (1); } xprt = svc_tli_create(RPC_ANYFD, nconf, (struct t_bind *)NULL, 0, 0); if (xprt == (SVCXPRT *)NULL) { fprintf(stderr, "could not create server handle\n"); exit (1); } prognum = gettransient(1, nconf, &xprt->xp_ltaddr); fprintf(stderr, "client gets prognum %d\n", prognum); if (svc_reg(xprt, prognum, 1, callback, NULL) == FALSE) { fprintf(stderr, "could not register service\n"); exit(1); } stat = rpc_call(argv[1], EXAMPLEPROG, EXAMPLEVERS, EXAMPLEPROC_CALLBACK, xdr_int, &prognum, xdr_void, NULL, NULL); if (stat != RPC_SUCCESS) { clnt_perrno(stat); exit(1); } svc_run(); exit(1); }
callback(rqstp, transp) register struct svc_req *rqstp; register SVCXPRT *transp; { switch (rqstp->rq_proc) { case 0: if (!svc_sendreply(transp, xdr_void, 0)) { fprintf(stderr, "err: exampleprog\n"); return (1); } return (0); case 1: if (!svc_getargs(transp, xdr_void, 0)) { svcerr_decode(transp); return (1); } fprintf(stderr, "client got callback\n"); if (!svc_sendreply(transp, xdr_void, 0)) { fprintf(stderr, "err: exampleprog"); return (1); } return (0); } return (2); }
In the following example, the server makes an RPC call to the client on an ALARM signal, but only if the client has passed the program number to the server. This server example illustrates the simplicity of the code when one is using rpc_reg.
/* * server */ #include <stdio.h> #include <rpc/rpc.h> #include <sys/signal.h> #include "example.h"char *getnewprog(); char *hostname; int docallback(); int pnum; /* program number for callback routine */
main () { if (argc != 2) { fprint(stderr, "usage: server hostname\n"); exit (1); } hostname = argv[1]; rpc_reg(EXAMPLEPROG, EXAMPLEVERS, EXAMPLEPROC_CALLBACK, getnewprog, xdr_int, xdr_void, NULL); signal(SIGALRM, docallback); alarm(10); svc_run(); fprintf(stderr, "Error: svc_run shouldn't return\n"); }
char * getnewprog(pnump) char *pnump; { pnum = *(int *)pnump; return NULL; }
docallback () { int ans;
if (pnum == 0) { alarm (10); return; } ans = rpc_call(hostname, pnum, 1, 1, xdr_void, 0, xdr_void, 0, NULL); if (ans != RPC_SUCCESS) { fprintf(stderr, "server: "); clnt_perrno(ans); } }