Introduction to programming with Sun/ONC RPC: step 6

Step 1 Step 2 Step 3 Step 4 Step 5 Step 6

Step 6. Cleanup

Your program is now working. All it needs now is some cleaning up. This is a crucial step for any non-throw-away code. You want to make sure the program is readable and flows well. It shouldn't have the appearance that you just hacked some code from a template just to get it working. The overall style should be consistent between your code and the rpcgen-generated code. For example:

  • I do not add a space before the opening parenthesis on a function call (e.g. "clnt_destroy(clnt)" instead of "clnt_destroy (clnt)", so I removed them here.
  • A name such as add_1_arg is not only unclear but rather long. It's as bad as naming the first parameter to function f as f_arg. I'm just going to call it v (for value; the code is so short that the meaning is clear and doesn't need the verbosity that a more global variable would call for).
  • It's important that you exit the program or otherwise ensure that you do not make any more RPC calls if you received an error from an RPC call (a return of 0).
  • I like having my main function at the top, so I moved it up and declared the function prototypes above it.
  • I moved the call to clnt_destroy to the main function. This allows one to call add multiple times without having it destroy the client handle after the first call.
  • I created a function called rpc_setup to create the client handle. It's only a few calls but it makes the main function cleaner to follow.
  • I added #include <stdio.h> at the top of the file to avoid errors of an undefined stdio.h (some systems gripe more than others).
  • I added #include <stdlib.h> at the top of the file to keep the compiler on my mac from giving warnings about the implicit definition of exit. You may not need this on other systems.
  • I removed any comments about this being template code.

The #ifdef DEBUG statements should be removed and the code can be made easier to read if the main function is moved to the top. Variables can be renamed more sensibly (add_1_arg is a bit crude). Auto-generated comments should be removed.

If we really intended to use the add remote procedure call repeatedly we would not want to create the handle each time, so it would be a good idea to create the RPC handle just once.

Here is the final client code:

/* RPC example: add two numbers */

#include "add.h"

CLIENT *rpc_setup(char *host);
void add(CLIENT *clnt, int a, int b);

int
main(int argc, char *argv[])
{
	CLIENT *clnt;  /* client handle to server */
	char *host;    /* host */
	int a, b;

	if (argc != 4) {
		printf("usage: %s server_host num1 num2\n", argv[0]);
		exit(1);
	}
	host = argv[1];
	if ((a = atoi(argv[2])) == 0 && *argv[2] != '0') {
		fprintf(stderr, "invalid value: %s\n", argv[2]);
		exit(1);
	}
	if ((b = atoi(argv[3])) == 0 && *argv[3] != '0') {
		fprintf(stderr, "invalid value: %s\n", argv[3]);
		exit(1);
	}
	if ((clnt = rpc_setup(host)) == 0)
		exit(1);	/* cannot connect */
	add(clnt, a, b);
	clnt_destroy(clnt);
	exit(0);
}

CLIENT *
rpc_setup(char *host)
{
	CLIENT *clnt = clnt_create(host, ADD_PROG, ADD_VERS, "udp");
	if (clnt == NULL) {
		clnt_pcreateerror(host);
		return 0;
	}
	return clnt;
}

void
add(CLIENT *clnt, int a, int b)
{
	int  *result;
	intpair v;	/* parameter for add */

	v.a = a;
	v.b = b;
	result = add_1(&v, clnt);
	if (result == 0) {
		clnt_perror(clnt, "call failed");
	} else {
		printf("%d\n", *result);
	}
}

And the server (with one line of diagnostics being printed) is:

/* * RPC server code for the remote add function */ #include "add.h" int * add_1_svc(intpair *argp, struct svc_req *rqstp) { static int result; result = argp->a + argp->b; printf("add(%d, %d) = %d\n", argp->a, argp->b, result); return &result; }

Even for a trivially simple program like this there are several ways to format the output. I opt for a single number on one line:

167

the number on a single line rather than something like:

result is 167

or

RPC program 123+44 = 167

Question: Why?

One last thing

Before we're completely done with this, let's create a new makefile. Unfortunately the automatically-generated one is neither easy to read nor reliable in its list of dependencies. We'll write a very explicit one. This is how our makefile looks now:

CC = gcc CFLAGS = -g -DRPC_SVC_FG RPCGEN_FLAG = -C all: add_client add_server # the executables: add_client and add_server add_client: add_client.o add_clnt.o add_xdr.o $(CC) -o add_client add_client.o add_clnt.o add_xdr.o -lnsl add_server: add_server.o add_svc.o add_xdr.o $(CC) -o add_server add_server.o add_svc.o add_xdr.o -lnsl # object files for the executables add_server.o: add_server.c add.h $(CC) $(CFLAGS) -c add_server.c add_client.o: add_client.c add.h $(CC) $(CFLAGS) -c add_client.c # compile files generated by rpcgen add_svc.o: add_svc.c add.h $(CC) $(CFLAGS) -c add_svc.c add_clnt.o: add_clnt.c add.h $(CC) $(CFLAGS) -c add_clnt.c add_xdr.o: add_xdr.c add.h $(CC) $(CFLAGS) -c add_xdr.c # add.x produces add.h, add_clnt.c, add_svc.c, and add_xdr.c # make sure we regenerate them if our interface (add.x) changes add_clnt.c add_svc.c add_xdr.c add.h: add.x rpcgen $(RPCGEN_FLAG) add.x clean: rm -f add_client add_client.o add_server add_server.o add_clnt.* add_svc.* add.h

Back to step 5

*****

Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh

Duis ultrices dolor sed erat.

Praesent ultrices, turpis vel feugiat tristique, mauris ligula pellentesque urna, quis interdum est enim mollis sem.

Ellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Praesent orci leo, suscipit vel, pretium eu, viverra at, eros. Morbi cursus.