The Lowest Layer of RPC from the Client Side

This section describes the lowest layer of RPC from the client side.

A programmer using the callrpc routine has control over neither the RPC delivery mechanism nor the socket used to transport the data. However, the lowest layer of RPC allows the user to adjust these parameters. The following code can be used to request the nusers service:

#include <stdio.h>
#include <rpc/rpc.h>
#include <utmp.h>
#include <rpcsvc/rusers.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netdb.h>

main(argc, argv)
    int argc;
    char **argv;
{
    struct hostent *hp;
    struct timeval pertry_timeout, total_timeout;
    struct sockaddr_in server_addr;
    int sock = RPC_ANYSOCK;
    register CLIENT *client;
    enum clnt_stat clnt_stat;
    unsigned long nusers;

    if (argc != 2) {
        fprintf(stderr, "usage: nusers hostname\n");
        exit(-1);
    }
    if ((hp = gethostbyname(argv[1])) == NULL) {
        fprintf(stderr, "can't get addr for %s\n",argv[1]);
        exit(-1);
    }
    pertry_timeout.tv_sec = 3;
    pertry_timeout.tv_usec = 0;
    bcopy(hp->h_addr, (caddr_t)&server_addr.sin_addr,
        hp->h_length);
    server_addr.sin_family = AF_INET;
    server_addr.sin_port =  0;
    if ((client = clntudp_create(&server_addr, RUSERSPROG,
     RUSERSVERS, pertry_timeout, &sock)) == NULL) {
        clnt_pcreateerror("clntudp_create");
        exit(-1);
    }
    total_timeout.tv_sec = 20;
    total_timeout.tv_usec = 0;

         
clnt_stat = clnt_call(client, RUSERSPROC_NUM, xdr_void,
        0, xdr_u_long, &nusers, total_timeout);
    if (clnt_stat != RPC_SUCCESS) {
        clnt_perror(client, "rpc");
        exit(-1);
    }
    clnt_destroy(client);
    close(sock);
    exit(0);
}

The low-level version of the callrpc routine is the clnt_call macro, which takes a CLIENT pointer rather than a host name. The parameters to the clnt_call macro are a CLIENT pointer, the procedure number, the XDR routine for serializing the argument, a pointer to the argument, the XDR routine for deserializing the return value, a pointer to where the return value is to be placed, and the total time in seconds to wait for a reply. Thus, the number of tries is the time out divided by the clntudp_create time out.

The CLIENT pointer is encoded with the transport mechanism. The callrpc routine uses UDP, thus it calls the clntudp_create routine to get a CLIENT pointer. To get Transmission Control Protocol (TCP), the programmer can call the clnttcp_create routine.

The parameters to the clntudp_create routine are the server address, the program number, the version number, a time-out value (between tries), and a pointer to a socket.

The clnt_destroy call always deallocates the space associated with the client handle. If the RPC library opened the socket associated with the client handle, the clnt_destroy macro closes it. If the socket was opened by the programmer, it stays open. In cases where there are multiple client handles using the same socket, it is possible to destroy one handle without closing the socket that other handles are using.

The stream connection is made when the call to the clntudp_create macro is replaced by a call to the clnttcp_create routine.


clnttcp_create(&server_addr, prognum, versnum, &sock,
               inputsize, outputsize);
In this example, no time-out argument exists. Instead, the send and receive buffer sizes must be specified. When the clnttcp_create call is made, a TCP connection is established. All remote procedure calls using the client handle use the TCP connection. The server side of a remote procedure call using TCP is similar, except that the svcudp_create routine is replaced by the svctcp_create routine, as follows:

transp = svctcp_create(RPC_ANYSOCK, 0, 0);

The last two arguments to the svctcp_create routine are send and receive sizes, respectively. If 0 is specified for either of these, the system chooses a reasonable default.