Writing the RPC Client

Here's the source code for the client. It creates a three-element linked list, opens a connection to a machine specified on the command line, and asks the server to print the list:

list *mk_list(char *data, int key, color c)
{
    list *lst;

    lst = (list*)malloc(sizeof(list));
    if (lst == NULL)
        return NULL;
    lst->data = data;
    lst->key = key;
    lst->col = c;
    lst->next = NULL;
    return lst;
}

int main(int argc, char *argv[])
{
    list *l, *new;
    CLIENT *cl;
    int *result;

    if (argc next = l; l = new;
    new = mk_list("three", 3, ORANGE);
    new->next = l; l = new;

    cl = clnt_create(argv[1], PRINTER, PRINTER_V1, "tcp");
    if (cl == NULL) {
        printf("error: could not connect to server.\n");
        return 1;
    }
    result = print_list_1(l, cl);
    if (result == NULL) {
        printf("error: RPC failed!\n");
        return 1;
    }
    printf("client: server says it printed %d items.\n", *result);

    return 0;
}
Notice the RPC library call "clnt_create()", which opens a connection. As promised, you address the server by hostname, program ID, and version number. You can also specify "tcp" or "udp" transport modes. The RPC itself is in boldface. Note that it only differs from your typical function call in that we had to specify a connection as the second argument. Also, the return value is int*, where we would normally have expected int, but this is just that same XDR quirk again.

Putting it Together

As I said before, it is possible for the server and client to be the same program. For instance, a server might handle an incoming RPC by making an RPC of its own to yet another host. But if all the server does is sit in an idle loop listening for connections and requests, how would we ever get to issue any RPCs in the first place? You will get a detailed answer when I discuss asynchronous RPCs in a later article, but for now the short answer is "threads". Create one program with two threads: one to listen and one to talk. Realize that this does not necessarily mean just smooshing the server and client into one executable and running each in a separate thread; you might decide instead to divide the functionality into "input/output" and "compute".

Anyway, in this case I decided to separate the client and server in order to clarify the distinction between them. Nothing unusual is required to compile the code, just a straightforward Makefile with two targets:

.SUFFIXES:
.SUFFIXES: .c .o
CLNT = llist
SRVR = llist_svc
CFLAGS = -g -Wall

SRVR_OBJ = llist_svc_proc.o llist_xdr.o llist_svc.o
CLNT_OBJ = llist.o llist_xdr.o llist_clnt.o

.c.o:; gcc -c -o $@ $(CFLAGS) $<

default: $(CLNT) $(SRVR)

$(CLNT): $(CLNT_OBJ)
	gcc -o $(CLNT) $(CLNT_OBJ)

$(SRVR): $(SRVR_OBJ)
	gcc -o $(SRVR) $(SRVR_OBJ)
With the above Makefile (using suffix rules), re-running rpcgen will cause llist_svc.o, llist_clnt.o, and llist_xdr.o to be rebuilt, since the .c files get overwritten. If you wanted to, you could add a rule to re-run rpcgen and rebuild the objects automatically when llist.x changes, but the Makefile would be a little more complicated.

Try starting up the server, "llist_svc" in one terminal, and running "./llist localhost" in another. You should see the expected output in the server window. Have fun until next time, when we'll be implementing asynchronous RPCs on top of the standard package.

You can download the full source here: RPC Demo Source. Enjoy!