Remote procedure calls are a well known method of transferring control from one process to another. The intent of RPC's is to make it appear to programmers that a local procedure call is taking place, thus hiding all the network code into its stub procedures. There are a number of steps involved in a remote procedure call. They are illustrated in Figure 8 and explained below [1].
For the grades program which will be developed, Sun RPCgen will be used. RPCgen is a compiler which takes a remote procedure interface, and generates the client and server stubs. Minimally RPCgen requires coding three files to implement a simple client server model. The client (or main function), The server (or remote procedures), and the RPC specification file. The client and server procedures must be written in 'C' and the RPC specification file must be written in Protocol Definition Language (PDL). A handout is provided with the full description of RPCgen PDL as given in [2]. All PDL files must end with the extension .x for the rpcgen compiler to recognize it. The rpcgen (given a file written in PDL as an argument) compiler will produce the following functions in 'C' which utilize Low Level RPC calls:
rpcgen pdl_file.x
from a command line will produce.
a) pdl_file.h - consists of common includes. This file must be included by the programmer in the client and server routines.
b) pdl_file_xdr.c - consists of filters used by both client and server routines.
c) pdl_file_clnt.c - consists of the client stub.
d) pdl_file_svc.c - consists of the server stub.
EX1 : Simple Example of a client choosing from two servers to 'manipulate' a structure.
/*
"program pdl.x"
Written in PDL, has 1 program section. It is the RPC
specification file.
*/
struct newgr{
char nm[30];
char inet_id[30];
int num;
};
typedef newgr *newgrptr;
program SERVER_1_PROG{ /*program section for server1*/
version SERVER_1_VERSION
{
newgr TEST_STRUCT1(newgr)=1; /*procedure 1*/
newgr TEST_STRUCT2(newgr)=2; /*procedure 2*/
/* other procedures used go here: with different procedure #'s*/
}=1; /*version number*/
}=0x31234567; /*program number*/
RPCgen generates the actual remote procedure names by converting the procedure names to lower case and appending an underscore and the version number to them.
/*
"program client.c"
Our client program is written in 'C Language'. The executable
file is designed to take the servers' address as its first and
second argument.
*/
#include <stdio.h>
#include <rpc/rpc.h> /* standard include file must be included */
/* in client program */
#include "pdl.h" /* generated by rpcgen and must be included */
/* in client program */
CLIENT *client_handle;
char *server1_id,*server2_id;
main(argc,argv)
int argc;
char *argv[];
{
newgr *ngptr;
newgrptr *ptr;
int serv;
server1_id=argv[1]; /*server1 is argument 1*/
server2_id=argv[2]; /* server2 is argument 2 */
if (argc!=3)
{perror("usage: clnt [server1.addr] [server2.addr]"); exit(0);}
printf("CHOOSE A SERVER 1 OR 2 \n");
scanf("%d",&serv); /* prompt user to choose a server */
switch (serv) /* according to the server chosen do */
{
case (1):{
if ((client_handle=clnt_create(server1_id,SERVER_1_PROG,
SERVER_1_VERSION,"tcp")) == NULL)
{
clnt_pcreateerror(argv[1]);
exit(0);
}
ngptr=(newgrptr)malloc(sizeof(newgr)); /*get memory*/
strcpy(ngptr->inet_id,server1_id);
/* call remote procedure */
ngptr=test_struct1_1(ngptr,client_handle);
printf("nm:%s inet_id:%s \n",ngptr->nm,ngptr->inet_id);
clnt_destroy(client_handle);
} break;
case (2):{
if ((client_handle=clnt_create(server2_id,SERVER_1_PROG,
SERVER_1_VERSION,"tcp")) == NULL)
{
clnt_pcreateerror(argv[2]);
exit(0);
}
ngptr=(newgrptr)malloc(sizeof(newgr)); /*get memory*/
strcpy(ngptr->inet_id,server2_id);
/* call remote procedure */
ngptr=test_struct2_1(ngptr,client_handle);
printf("nm:%s inet_id:%s \n",ngptr->nm,ngptr->inet_id);
clnt_destroy(client_handle);
} break;
} /*end switch*/
exit(0);
} /* end main */
All remote procedure calls return NULL upon failure.
/*
"program server.c"
Remote procedures called by the server stub. i.e our server
procedures.
*/
#include <string.h>
#include <rpc/rpc.h> /* must be included by server */
#include "pdl.h" /* must be included by server */
struct newgr* test_struct1_1(ng)/*remote procedure 1 for server*/
struct newgr* ng;
{
ng->nm[0]=NULL;
strcat(ng->nm,"running server 1");
printf("running server1\n");
return ng;
}
struct newgr* test_struct2_1(ng)/*remote procedure 2 for server*/
struct newgr* ng;
{
ng->nm[0]=NULL;
strcat(ng->nm,"running server 2");
printf("running server2\n");
return ng;
}
The remote procedures defined in the Program section of the pdl_file.x file (RPC specification file written in PDL), must be transformed to lower case and an underscore and version number must be attached at the end. In EX1 the Program sections of our pdl.x file define two procedures.
a) newgr TEST_STRUCT1(newgr)=1;/* procedure 1 in SERVER_1_PROG */ b) newgr TEST_STRUCT2(newgr)=2;/* procedure 2 in SERVER_1_PROG */The procedures have a version number of one (so an underscore and a number one must be appended to the lower case procedure name when defined or calling the procedures). They must be referenced by our client program as follows:
/*procedure 1*/ var=test_struct1_1(arg,client_handle); /*procedure 2*/ var=test_struct2_1(arg,client_handle);Where var is of appropriate return type in 'C' ('not' PDL) (note PDL defines some data types differently than 'C'). The appropriate client handle must be passed as the last parameter to any remote procedure. All remote procedures accept only two arguments. The first argument will be the 'message' sent, and the second the client handle. If more arguments must be sent through, they must be placed in a structure and the structure must be passed as the single argument.
/*procedure 1*/ struct newgr* test_struct1_1(ng) { ... }
/*procedure 2*/ struct newgr* test_struct2_1(ng) { ... }
Both the client and server programs must include the following:
#include <rpc/rpc.h> /* standard RPC include file */ #include "pdl_file.h" /* generated by pdl_file.x */
When coding the Program section of our pdl_file.x the program number of our defined section should be in the range 0x20000000 - 0x3fffffff . These are program numbers reserved by the operating system for user definitions.
clnt_create( )
Creates a CLIENT handle for the specified server host, program, and version numbers. Parameter host is the Interned address in dotted decimal notation. Parameters prognum and versnum are the Program and Version numbers specified in Specification File (.x file). Parameter protocol specifies the use of either tcp or udp communication protocols. clnt_create(3N) will return NULL upon failure.
CLIENT* clnt_create(host,prognum,versnum,protocol) char *host; u_long prognum, versnum; char* protocol;
clnt_destroy( )
Deallocates memory associated with private data structures for the named CLIENT handle.
clnt_destroy(clnt) CLIENT *clnt;
There are a few steps to actually running an RPC program. They are as follows.
1) Generate header (pdl_file.h), XDR filters (pdl_file_xdr.c), client stub (pdl_file_clnt.c), and server stub (pdl_file_svc.c) from our Specification file (pdl_file.x). from the command line type:
>rpcgen pdl_file.x2) Link the client code with the client stub and xdr filters:
>cc client_file.c pdl_file_clnt.c pdl_file_xdr.c -o client_exec3) Link the server code with the server stub and xdr filters:
>cc server_file.c pdl_file_svc.c pdl_file_xdr.c -o server_execEX2 : Compile and run the code described in EX1.
compilation:
>rpcgen pdl.x >cc client.c pdl_clnt.c pdl_xdr.c -o clnt >cc server.c pdl_svc.c pdl_xdr.c -o svcNOTE: This example assumes servers running in heron (server1), and condor (server2).
>clnt 129.108.42.32 129.108.42.34
[1] Stevens, W. R. Unix Network Programming, Prentice Hall Inc. 1990.
[2] Bloomer, J. Power Programming with RPC, O'Reilly & Associates, Inc. 1992.
[3] Comer, D.
Stevens, D. Internetworking with TCP/IP vol.iii, Prentice Hall Inc. 1993.
[4] Corbin, J. The Art of Distributed Applications, Springer-Verlag 1991.