|
|
Assume an application that runs on a single machine. Suppose we want to convert it to run over the network. Here we will show such a conversion by way of a simple example program that prints a message to the console. The source file for the original program might look like this:
/* printmsg.c: print a message on the console */For local use on a single machine, this program could be compiled and executed as follows:#include <stdio.h>
main(argc, argv) int argc; char *argv[]; { char *message;
if (argc != 2) { fprintf(stderr, "usage: %s <message>\n", argv[0]); exit(1); } message = argv[1];
if (!printmessage(message)) { fprintf(stderr, "%s: couldn't print your message\n", argv[0]); exit(1); } printf("Message delivered!\n"); exit(0); } /* Print a message to the console. */ /* Return a boolean indicating whether the message was actually printed. */
printmessage(msg) char *msg; { FILE *f;
f = fopen("/dev/console", "w"); if (f == NULL) { return (0); } fprintf(f, "%s\n", msg); fclose(f); return (1); }
This should return the response ``Message delivered!''.
If the printmessage function were turned into a remote procedure, it could be called from anywhere in the network. It is not difficult to make a procedure remote.
In general, it is necessary to figure out what the types are for all procedure inputs and outputs. Here, the procedure printmessage takes a string as input, and returns an integer as output. Knowing this, we can write a protocol specification in RPC language that describes the remote version of printmessage. The RPC language source code for such a specification might look like this:
/* msg.x: Remote message printing protocol */Remote procedures are always declared as being part of remote programs. The above is actually a declaration for an entire remote program, one that contains the single procedure PRINTMESSAGE.program MESSAGEPROG { version MESSAGEVERS { int PRINTMESSAGE(string) = 1; } = 1; } = 0x20000001;
In this example, the PRINTMESSAGE procedure is declared to be procedure 1, in version 1 of the remote program whose number is 0x20000001. (Refer to ``Program number assignment'' for guidance on choice of program numbers.)
By convention, all RPC services provide for a procedure 0; a call to a remote program's procedure 0 should do nothing (a ``no-op'') except succeed. To ping means to call procedure 0 of a remote program. Pinging is used to verify the existence and accessibility of a remote program.
Using rpcgen, no null procedure (procedure 0) need be written because rpcgen generates it automatically.
Notice that the program and procedure names are declared with all capital letters. This is not required, but is a good convention to follow.
Notice also that the argument type is string and not char * as it would be in C. This is because a char * in C is ambiguous. Programmers usually intend it to mean a null-terminated string of characters, but it could also represent a pointer to a single character or a pointer to an array of characters. In RPC language, a null-terminated string is unambiguously called a string.
There are two more things to write:
/* * msg_proc.c: implementation of the remote procedure "printmessage" */Notice that the declaration of the remote procedure printmessage_1 differs from that of the local procedure printmessage in three ways:#include <stdio.h> #include <rpc/rpc.h> /* always needed */ #include "msg.h" /* msg.h will be generated by rpcgen */
/* * Remote version of "printmessage" */ int * printmessage_1(msg) char **msg; { static int result; /* must be static! */ FILE *f;
f = fopen("/dev/console", "w"); if (f == NULL) { result = 0; return (&result); } fprintf(f, "%s\n", *msg); fclose(f); result = 1; return (&result); }
In the code generated by rpcgen, the result address is converted to XDR format after the remote procedure returns. If the result were declared local to the remote procedure, references to its address would be invalid after the remote procedure returned. So the result must be declared ``static'' when rpcgen is used.
The last thing to do is declare the main client program that will call the remote procedure. This is one possibility:
/* * rprintmsg.c: remote version of "printmsg.c" */ #include <stdio.h> #include <rpc/rpc.h> /* always needed */ #include "msg.h" /* msg.h will be generated by rpcgen */There are four things to note here:main(argc, argv) int argc; char *argv[]; { CLIENT *cl; int *result; char *server; char *message;
if (argc != 3) { fprintf(stderr, "usage: %s host message\n", argv[0]); exit(1); }
/* * Save values of command line arguments */ server = argv[1]; message = argv[2];
/* * Create client "handle" used for calling MESSAGEPROG on the * server designated on the command line. */ cl = clnt_create(server, MESSAGEPROG, MESSAGEVERS, "visible"); if (cl == NULL) { /* * Couldn't establish connection with server. * Print error message and die. */ clnt_pcreateerror(server); exit(1); } /* * Call the remote procedure "printmessage" on the server */ result = printmessage_1(&message, cl); if (result == NULL) { /* * An error occurred while calling the server. * Print error message and die. */ clnt_perror(cl, server); exit(1); }
/* * Okay, we successfully called the remote procedure. */ if (*result == 0) { /* * Server was unable to print our message. * Print error message and die. */ fprintf(stderr, "%s: %s couldn't print your message\n", argv[0], server); exit(1); }
/* * The message got printed on the server's console */ printf("Message delivered to %s!\n", server); exit(0); }
Two programs are compiled here: the client program rprintmsg and the server program msg_server. Before doing this, rpcgen was used to fill in the missing pieces.
This is what rpcgen (called without any flags) did with the input file msg.x:
Once created, the server should be copied to a remote machine and run. (If the machines are homogeneous, the server can be copied as a binary. Otherwise, the source files will need to be copied to and compiled on the remote machine.) For this example, the remote machine is called remote and the local machine is called local. The server is started from the shell on the remote system:
remote$ msg_server
Thereafter, a user on
local
can print a message on the console of system
remote
as follows:
rprintmsg remote "Hello there."
Using rprintmsg, a user can print a message on any system console (including the local console) if the server msg_server is running on the target system.