Occasionally, it is useful to have a server become a client and make a Remote Procedure Call (RPC) back to the process client. For example, with remote debugging, the client is a window system program and the server is a debugger running on the remote machine. Usually, the user clicks a mouse button at the debugging window. This step invokes a debugger command that makes a remote procedure call to the server (where the debugger is actually running), telling it to execute that command. When the debugger hits a breakpoint, however, the roles are reversed. The debugger then makes a remote procedure call to the window program to inform the user that a breakpoint has been reached.
An RPC callback requires a program number to make the remote procedure call on. Since this will be a dynamically generated program number, it should be in the transient range, 0x40000000 to 0x5fffffff. The gettransient routine returns a valid program number in the transient range, and registers it with the port mapper. This routine only talks to the port mapper running on the same machine as the gettransient routine itself. The call to the pmap_set routine is a test-and-set operation. That is, it indivisibly tests whether a program number has already been registered, and reserves the number if it has not. On return, the sockp argument contains a socket that can be used as the argument to an svcudp_create or svctcp_create routine.
#include <stdio.h> #include <rpc/rpc.h> #include <sys/socket.h> gettransient(proto, vers, sockp) int proto, vers, *sockp; { static int prognum = 0x40000000; int s, len, socktype; struct sockaddr_in addr; switch(proto) { case IPPROTO_UDP: socktype = SOCK_DGRAM; break; case IPPROTO_TCP: socktype = SOCK_STREAM; break; default: fprintf(stderr, "unknown protocol type\n"); return 0; } if (*sockp == RPC_ANYSOCK) { if ((s = socket(AF_INET, socktype, 0)) < 0) { perror("socket"); return (0); } *sockp = s; } else s = *sockp; addr.sin_addr.s_addr = 0; addr.sin_family = AF_INET; addr.sin_port = 0; len = sizeof(addr); /* * may be already bound, so don't check for error */ bind(s, &addr, len); if (getsockname(s, &addr, &len)< 0) { perror("getsockname"); return (0); } while (!pmap_set(prognum++, vers, proto, ntohs(addr.sin_port))) continue; return (prognum-1); }
Note: The call to the ntohs subroutine ensures that the port number in addr.sin_port, which is in network byte order, is passed in host byte order. The pmap_set subroutine expects host byte order.
The following programs illustrate how to use the gettransient routine. The client makes a remote procedure 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 EXAMPLEPROG program so that it can receive the remote procedure call informing it of the callback program number. Then, at some randomly selected time (on receiving a SIGALRM signal in this example), the server sends a callback remote procedure call, using the program number it received earlier.
/* * client */ #include <stdio.h> #include <rpc/rpc.h>
int callback(); char hostname[256];
main() {
int x, ans, s; SVCXPRT *xprt; gethostname(hostname, sizeof(hostname)); s = RPC_ANYSOCK; x = gettransient(IPPROTO_UDP, 1, &s); fprintf(stderr, "client gets prognum %d\n", x); if ((xprt = svcudp_create(s)) == NULL) { fprintf(stderr, "rpc_server: svcudp_create\n"); exit(1); } /* protocol is 0 - gettransient does registering */ (void)svc_register(xprt, x, 1, callback, 0); ans = callrpc(hostname, EXAMPLEPROG, EXAMPLEVERS, EXAMPLEPROC_CALLBACK, xdr_int, &x, xdr_void, 0); if ((enum clnt_stat) ans != RPC_SUCCESS) { fprintf(stderr, "call: "); clnt_perrno(ans); fprintf(stderr, "\n"); } svc_run(); fprintf(stderr, "Error: svc_run shouldn't return\n");
}
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); } }
} /* * server */ #include <stdio.h> #include <rpc/rpc.h> #include <sys/signal.h>
char *getnewprog(); char hostname[256]; int docallback(); int pnum; /* program number for callback routine */
main()
{ gethostname(hostname, sizeof(hostname)); registerrpc(EXAMPLEPROG, EXAMPLEVERS, EXAMPLEPROC_CALLBACK, getnewprog, xdr_int, xdr_void); fprintf(stderr, "server going into svc_run\n"); 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;
ans = callrpc(hostname, pnum, 1, 1, xdr_void, 0, xdr_void, 0); if (ans != 0) { fprintf(stderr, "server: "); clnt_perrno(ans); fprintf(stderr, "\n"); } }