Programming with Remote Procedure Calls (RPC)

The expert level

At the expert level, network selection is done exactly as at the intermediate level. The only difference here is in the level of control that the application has over the details of the transport's configuration. Control at this level is much greater. These examples illustrate that control, which is exercised using the clnt_tli_create and svc_tli_create routines.

Expert level: client side

This is the client side of some code that implements a version of clntudp_create (the client-side creation routine for the UDP transport) in terms of clnt_tli_create. The example shows how to do network selection based on the family of the transport one wishes to use.

clnt_tli_create is normally used to create a client handle when:

   #include <stdio.h>
   #include <rpc/rpc.h>
   #include <netconfig.h>
   #include <netinet/in.h>
    * In an earlier implementation of RPC, the only transports supported
    * were TCP/IP and UDP/IP.  Here they are shown based on the Berkeley
    * socket, but implemented on the top of XTI/Streams.
   clntudp_create(raddr, prog, vers, wait, sockp)
   	struct sockaddr_in *raddr;	/* Remote address */
   	u_long prog;		/* Program number */
   	u_long vers;		/* Version number */
   	struct timeval wait;	/* Time to wait */
   	int *sockp;		/* fd pointer */
   	CLIENT *cl;		/* Client handle */
   	int madefd = FALSE;	/* Is fd opened here */
   	int fd = *sockp;		/* fd */
   	struct t_bind *tbind;	/* bind address */
   	struct netconfig *nconf;	/* netconfig structure */
   	void *handlep;

if ((handlep = setnetconfig()) == 0) { /* No transports available */ rpc_createerr.cf_stat = RPC_UNKNOWNPROTO; return ((CLIENT *)NULL); } /* * Try all the transports till it gets one which is * connectionless, family is INET and name is UDP */ while (nconf = getnetconfig(handlep)) { if ((nconf->nc_semantics == NC_TPI_CLTS) && (strcmp(nconf->nc_protofmly, NC_INET) == 0) && (strcmp(nconf->nc_proto, NC_UDP) == 0)) break; } if (nconf == NULL) { rpc_createerr.cf_stat = RPC_UNKNOWNPROTO; goto err; } if (fd == RPC_ANYSOCK) { fd = t_open(nconf->nc_device, O_RDWR, NULL); if (fd == -1) { rpc_createerr.cf_stat = RPC_SYSTEMERROR; goto err; } madefd = TRUE; /* The fd was opened here */ } if (raddr->sin_port == 0) { /* remote addr unknown */ ushort_t sport;

/* * rpcb_getport() is a user provided routine * which will call rpcb_getaddr and translate * the netbuf address to port number. */ sport = rpcb_getport(raddr, prog, vers, nconf); if (sport == 0) { rpc_createerr.cf_stat = RPC_PROGUNAVAIL; goto err; } raddr->sin_port = sport; }

/* Transform sockaddr_in to netbuf */ tbind = (struct t_bind *)t_alloc(fd, T_BIND, T_ADDR); if (tbind == NULL) { rpc_createerr.cf_stat = RPC_SYSTEMERROR; goto err; } (void) memcpy(tbind->addr.buf, (char *)raddr, (int)tbind->addr.maxlen); tbind->addr.len = tbind->addr.maxlen;

/* Bind endpoint to a reserved address */ (void) bind_resv(fd); cl = clnt_tli_create(fd, nconf, &(tbind->addr), prog, vers, 8800, 8800); (void) endnetconfig(handlep); /* Close the netconfig file */ (void) t_free((char *)tbind, T_BIND); if (cl) { *sockp = fd; if (madefd == TRUE) { /* fd should be closed while destroying the handle */ (void) CLNT_CONTROL(cl, CLSET_FD_CLOSE, NULL); } /* Set the retry time */ (void) CLNT_CONTROL(cl, CLSET_RETRY_TIMEOUT, &wait); return (cl); }

err: if (madefd == TRUE) (void) t_close(fd); (void) endnetconfig(handlep); return ((CLIENT *)NULL); }

The network selection is done using the library functions setnetconfig, getnetconfig, and endnetconfig. (Note that endnetconfig is not called until after the call to clnt_tli_create, near the end of the example.)

clntudp_create can be passed an open fd, but if not (fd == RPC_ANYSOCK), it will open its own using the netconfig structure for UDP.

If the remote address is not known, (raddr->sin_port == 0), then it is obtained from the remote rpcbind. Note the call to bind_resv, which is a user-supplied function that serves to bind a transport endpoint to a reserved address. This call is necessary because there is no notion of a reserved address in RPC under XTI, as there is in both TCP and UDP. The implementation of this routine is of no interest here, because it is entirely transport specific. What is of interest is the scaffolding necessary to call it.

After the client handle has been created, the programmer can suitably customize it using calls to clnt_control. Here, the RPC library closes the file descriptor while destroying the handle (as it usually does with a call to clnt_destroy when it opens the fd itself) and sets the retry timeout period.

Expert level: server side

Below is the corresponding server code. It implements svcudp_create in terms of svc_tli_create, and calls the user provided bind_resv to bind the transport endpoint to a reserved address.

svc_tli_create is normally used when the application needs a fine degree of control, and especially if it is necessary to:

The fd argument may be unbound when passed in. If it is, then it is bound to a given address, and the address is stored in a handle. If the bind address is set to NULL, and if the fd is initially unbound, it will be bound to any suitable address.

NOTE: It is the responsibility of the programmer to use rpcb_set to register the service with rpcbind.

   #include <stdio.h>
   #include <rpc/rpc.h>
   #include <netconfig.h>
   #include <netinet/in.h>

/* * On the server side */ SVCXPRT * svcudp_create(fd) register int fd; { struct netconfig *nconf; SVCXPRT *svc; int madefd = FALSE; int port; void *handlep;

if ((handlep = setnetconfig()) == 0) { /* No transports available */ nc_perror("server"); return ((SVCXPRT *)NULL); } /* * Try all the transports till it gets one which is * a connection less, family is INET and name is UDP */ while (nconf = getnetconfig(handlep)) { if ((nconf->nc_semantics == NC_TPI_CLTS) && (strcmp(nconf->nc_protofmly, NC_INET) == 0) && (strcmp(nconf->nc_proto, NC_UDP) == 0)) break; } if (nconf == NULL) { endnetconfig(handlep); fprintf(stderr, "UDP transport not available\n"); return ((SVCXPRT *)NULL); } if (fd == RPC_ANYFD) { fd = t_open(nconf->nc_device, O_RDWR, NULL); if (fd == -1) { (void) endnetconfig(); (void) fprintf(stderr, "svcudp_create: could not open connection\n"); return ((SVCXPRT *)NULL); } madefd = TRUE; }

/* * Bind Endpoint to a reserved address */ port = bind_resv(fd); svc = svc_tli_create(fd, nconf, (struct t_bind *)NULL, 8800, 8800); (void) endnetconfig(handlep); if (svc == (SVCXPRT *)NULL) { if (madefd) (void) t_close(fd); return ((SVCXPRT *)NULL); } if (port == -1) /* Specifically set xp_port now */ svc->xp_port = ((struct sockaddr_in *)svc->xp_ltaddr.buf)->sin_port; else svc->xp_port = port; return (svc); }

The network selection here is done in a similar way as in clntudp_create.

svcudp_create is set up to receive an open fd, but if it does not, it will open one itself using the selected netconfig structure.

bind_resv is a user-provided function that binds the fd to a reserved port if the caller is an RPC administrator.

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