Using RPCGEN to Generate Templates and a MAKEFILE
The rpcgen command has additional functionality to assist the developer of RPC applications. If the -a flag (see Figure 9.7) is passed to rpcgen , it will generate, in addition to the client and server stub files and header file, a set of template files for the client and server and a makefile for the entire application. Unlike the -C flag, which will cause rpcgen to overwrite preexisting stub files, the -a flag will cause rpcgen to halt with a warning message if the template files (with the default names ) are present in the current directory. Therefore, it is best to use the -a flag only when you are positive the protocol definition file is accurate; otherwise , you must manually remove or rename the previously generated template files.
For example, suppose we have a program called fact.c (Program 9.5) that requests an integer value and returns the factorial of that value if it is within the range of values that can be stored in a long integer; otherwise, it returns a value of 0.
Program 9.5 The original factorial program, fact.c .
File : fact.c /* A program to calculate Factorial numbers */ #include + int main( ){ long int f_numb, calc_fact(int); int number; printf("Factorial Calculator "); 10 printf("Enter a positive integer value "); scanf("%d", &number); if (number < 0) printf("Positive values only! "); else if ((f_numb = calc_fact(number)) > 0) + printf("%d! = %d ", number, f_numb); else printf("Sorry %d! is out of my range! ", number); return 0; } 20 /* Calculate the factorial number and return the result or return 0 if value is out of range. */ long int + calc_fact(int n){ long int total = 1, last = 0; int idx; for (idx = n; idx - 1; --idx) { total *= idx; 30 if (total <= last) /* Have we gone out of range? */ return (0); last = total; } return (total); + }
We would like to turn the factorial program into a clientserver application whereby the client could make a request for a factorial value from the remote factorial server. To accomplish this, we begin by writing the protocol definition file shown in Figure 9.19.
Figure 9.19 The protocol definition file for the factorial program.
File : fact.x /* The protocol definition file for the factorial program. The programmer generates this file. */ + program FACTORIAL { version ONE { long int CALC_FAC( int ) = 1; } = 1; } = 0x20000049;
We then use rpcgen with the -a and -C flags to generate the header file, the client and server stub files, the client and server template files, and the application Makefile . The details of and output from this process are shown in Figure 9.20.
Figure 9.20 Using rpcgen with the -a and -C flags.
linux$ ls fact.x linux$ rpcgen -a -C fact.x linux$ ls -x fact_client.c fact_clnt.c fact.h fact_server.c fact_svc.c fact.x Makefile.fact
As shown, passing rpcgen the protocol definition file with the -a and -C flags generates six files: the header file, fact.h , and the RPC stub files, fact_clnt.c and fact_svc.c , which are similar in content and nature to those in the previous example. The three new files created by rpcgen bear further investigation. The client template file is fact_client.c . Again, rpcgen has used the file name of the protocol definition file as the root for the file name and added the _client.c suffix. The contents of the fact_client.c file are shown in Figure 9.21.
Figure 9.21 The fact_client.c template client file generated by rpcgen .
File : fact_client.c /* This is sample code generated by rpcgen. These are only templates and you can use them as a guideline for developing your own functions. + */ #include "fact.h" void factorial_1(char *host) { CLIENT *clnt; 10 long *result_1; int calc_fac_1_arg; #ifndef DEBUG clnt = clnt_create (host, FACTORIAL, ONE, "udp"); + if (clnt == NULL) { clnt_pcreateerror (host); exit (1); } #endif /* DEBUG */ 20 result_1 = calc_fac_1(&calc_fac_1_arg, clnt); if (result_1 == (long *) NULL) { clnt_perror (clnt, "call failed"); } #ifndef DEBUG + clnt_destroy (clnt); #endif /* DEBUG */ } int main (int argc, char *argv[]) { 30 char *host; if (argc < 2) { printf ("usage: %s server_host ", argv[0]); exit (1); + } host = argv[1]; factorial_1 (host); exit (0); }
In the template file rpcgen has created a function called factorial_1 (lines 7 through 27). The function name is derived from the program name given in the protocol definition file with a suffix of _1 (the version number). As shown, the factorial_1 function is passed the host name. This function is used to make the RPC clnt_create call and the remote calc_fac_1 function call. Notice that variables for the correct argument type and function return type have been placed at the top of the factorial_1 function. By default, the transport protocol for the clnt_create call is specified as udp (versus tcp , which was used in the previous example). The call to the remote cal_fac_1 function is followed by a check of its return value. If the return value is NULL, indicating a failure, the library function clnt_perror (Table 9.6) is called to display an error message.
Table 9.6. Summary of the clnt_perror Library Call.
Include File(s) |
Manual Section |
3N |
||
Summary |
void clnt_perror(CLIENT *clnt, char *s ); |
|||
Return |
Success |
Failure |
Sets errno |
|
Print message to standard error indicating why the RPC call failed. |
The clnt_perror library call is passed the client handle from the clnt_create call and an informational message string. The clnt_perror message will have the informational message prefaced with an intervening colon .
A call to the library function clnt_destroy is also generated (Table 9.7).
The clnt_destroy function is used to return the resources allocated by the clnt_create function to the system. As would be expected, once a client RPC handle has been destroyed , it is undefined and can no longer be referenced.
To facilitate testing, rpcgen has also placed a series of preprocessor directives in the template file. However, it seems to overlook the fact that the call to clnt_perror requires the network library and thus may also need to be commented out when debugging the application. As in the previous example, if the -C option for rpcgen has been specified and a call to the remote factorial function ( calc_fac_1 ) is to be made in a debug setting, the function name should have the string _svc appended, and the clnt argument should be cast to the data type ( struct svc_req * ).
Table 9.7. Summary of the clnt_destroy Library Call.
Include File(s) |
Manual Section |
3N |
||
Summary |
void clnt_destroy( CLIENT *clnt ); |
|||
Return |
Success |
Failure |
Sets errno |
|
We can now edit the fact_client.c program and add the appropriate code from the function main in our initial fact.c example. The modified fact_client.c program is shown in Figure 9.22. Note the change in the call to the calc_fact function to the factorial_1 function.
Figure 9.22 The fact_client.c template file with modifications.
File : fact_client.c /* This is sample code generated by rpcgen. These are only templates and you can use them as a guideline for developing your own functions. + */ #include "fact.h" long int /* Returns a long int */ factorial_1( int calc_fac_1_arg, char *host) { CLIENT *clnt; 10 long *result_1; /* int calc_fac_1_arg;*/ #ifndef DEBUG clnt = clnt_create (host, FACTORIAL, ONE, "udp"); + if (clnt == NULL) { clnt_pcreateerror (host); exit (1); } #endif /* DEBUG */ 20 result_1 = calc_fac_1(&calc_fac_1_arg, clnt); if (result_1 == (long *) NULL) { clnt_perror (clnt, "call failed"); } #ifndef DEBUG + clnt_destroy (clnt); #endif /* DEBUG */ return *result_1; /* return value to main */ } int 30 main (int argc, char *argv[]) { char *host; long int f_numb; /* Own declarations */ int number; if (argc < 2) { + printf ("usage: %s server_host ", argv[0]); exit (1); } host = argv[1]; /* factorial_1 (host); */ 40 /* Replace canned call with code from previous main in program fact.c */ printf("Factorial Calculator "); printf("Enter a positive integer value "); + scanf("%d", &number); if (number < 0) printf("Positive values only! "); else if ((f_numb = factorial_1(number, host)) > 0) printf("%d! = %d ", number, f_numb); 50 else printf("Sorry %d! is out of my range! ", number); exit (0); }
In order, the modifications to the client program were as follows . First, the return type of the generated function ( factorial_1 ) is changed from void to a long int . Second, the factorial_1 argument list is adjusted to include the numeric value that is passed. The data type and other information for this argument was listed at the top of the function. Note that to prevent the overshadowing of the parameter, this previous declaration must be deleted or commented out (as done in line 11). Third, the return type for the factorial_1 function is added at the foot of the function. Fourth, in main the appropriate declarations are added (see lines 32 and 33). Fifth, and last, the bulk of the code from main in the fact.c is copied into the main of this program. When this is done, the canned call to factorial_1 must be removed (or commented out) and, most importantly, the name of the function to be invoked must be changed from its original calc_fact to factorial_1 .
Figure 9.23 Server template file fact_server.c generated by rpcgen .
File : fact_server.c /* This is sample code generated by rpcgen. These are only templates and you can use them as a guideline for developing your own functions. + */ #include "fact.h" long * calc_fac_1_svc(int *argp, struct svc_req *rqstp) { 10 static long result; /* * insert server code here */ + return &result; }
The server template file generated by rpcgen is shown in Figure 9.23.
As with the client template file, we now can modify the server template to incorporate the code for the remote procedure. The modified fact_server.c file is shown in Figure 9.24.
Figure 9.24 The fact_server.c template file with modifications.
File : fact_server.c /* This is sample code generated by rpcgen. These are only templates and you can use them as a guideline for developing your own functions. + */ #include "fact.h" long * calc_fac_1_svc(int *argp, struct svc_req *rqstp) { 10 static long result; /* * insert server code here */ long int total = 1, last = 0; + int idx; for (idx = *argp; idx - 1; idx) { total *= idx; if (total <= last) { /* Have we gone out of range? */ result = 0; 20 return (&result); } last = total; } result = total; + return &result; }
The changes for the server program are more straightforward than those for the client program. Essentially, the function code is pasted into the indicated location. The only coding adjustment occurs in line 17 where idx is initialized . As the argument passed to this function is as reference (versus a value) it must be dereferenced.
The Makefile generated by rpcgen is shown in Figure 9.25.
Figure 9.25 Makefile.fact , generated by rpcgen .
File : Makefile.fact # This is a template Makefile generated by rpcgen # Parameters + CLIENT = fact_client SERVER = fact_server SOURCES_CLNT.c = 10 SOURCES_CLNT.h = SOURCES_SVC.c = SOURCES_SVC.h = SOURCES.x = fact.x + TARGETS_SVC.c = fact_svc.c fact_server.c TARGETS_CLNT.c = fact_clnt.c fact_client.c TARGETS = fact.h fact_clnt.c fact_svc.c fact_client.c fact_server.c OBJECTS_CLNT = $(SOURCES_CLNT.c:%.c=%.o) $(TARGETS_CLNT.c:%.c=%.o) 20 OBJECTS_SVC = $(SOURCES_SVC.c:%.c=%.o) $(TARGETS_SVC.c:%.c=%.o) # Compiler flags CFLAGS += -g LDLIBS += -lnsl + RPCGENFLAGS = # Targets all : $(CLIENT) $(SERVER) 30 $(TARGETS) : $(SOURCES.x) rpcgen $(RPCGENFLAGS) $(SOURCES.x) $(OBJECTS_CLNT) : $(SOURCES_CLNT.c) $(SOURCES_CLNT.h) $(TARGETS_CLNT.c) + $(OBJECTS_SVC) : $(SOURCES_SVC.c) $(SOURCES_SVC.h) $(TARGETS_SVC.c) $(CLIENT) : $(OBJECTS_CLNT) $(LINK.c) -o $(CLIENT) $(OBJECTS_CLNT) $(LDLIBS) 40
While the makefile can be used pretty much as generated, you may want to modify some of the entries in the compiler flag section. For example, you may want to add the -C flag to RPCGENFLAGS, or indicate that the math library should be linked by adding -lm to LDLIBS. If a compiler other than the default compiler ( gcc on most systems) is to be used, you would add the notation in this section (e.g., CC = cc for Sun's C compiler or CC = CC for Sun's C++ compiler). The make utility will assume the Makefile it is to process is called makefile . As rpcgen creates a Makefile whose name is makefile with a period ( . ) root name of the protocol definition file ( fact ) appended, the user is left with two remedies. First, rename the generated Makefile to makefile by using the mv command, or second, use the -f flag for make . If the -f flag is used with make , then the name of the file for make to use should immediately follow the -f flag.
Figure 9.26 presents the sequence of events on a local system when the make utility with the -f flag is used to generate the factorial application.
Figure 9.26 Using the Makefile.fact file.
linux$ make -f Makefile.fact cc -g -c -o fact_clnt.o fact_clnt.c cc -g -c -o fact_client.o fact_client.c cc -g -o fact_client fact_clnt.o fact_client.o -lnsl cc -g -c -o fact_svc.o fact_svc.c cc -g -c -o fact_server.o fact_server.c cc -g -o fact_server fact_svc.o fact_server.o -lnsl
Figure 9.27 shows a sequence for running the factorial client-server application.
In the previous example, the factorial server program is invoked on the workstation called linux . The ps command verifies the presence of the fact_server process. The factorial client program is invoked and passed the name of the host that is running the factorial server process. The client process requests the user to input an integer value. The user enters the value 11. The client process makes a remote call to the server process, passing it the value 11. The server process responds by calculating the factorial value and returning it to the client. The client process displays the returned result. The client process is invoked a second time and passed a value of 15. The value 15! is beyond the storage range for an integer on the server. Thus, the server returns the value 0, indicating it was unable to complete the calculation. The client displays the corresponding error message. Next, the user has logged onto another workstation on the same network ( medusa ) and changes to the directory where the executables for the factorial application reside. The ps command is used to check if the factorial server process is present on this workstationit is not. The factorial client is invoked again and passed the name of the workstation running the server process ( linux ). The client program requests an integer value (entered as 12). This value is passed, via the RPC, to the server process on the workstation linux . The factorial value is calculated by the server process on linux and returned to the client process on medusa , which displays the results to the screen.
Figure 9.27 Running the factorial client-server application.
linux$ fact_server & <-- 1 [1] 24366 linux$ ps -ef grep fact gray 24366 24036 0 14:30 pts/2 00:00:00 fact_server gray 24368 24036 0 14:30 pts/2 00:00:00 grep gray linux$ fact_client linux <-- 2 Factorial Calculator Enter a positive integer value 11 11! = 39916800 linux$ fact_client linux Factorial Calculator Enter a positive integer value 15 Sorry 15! is out of my range! medusa$ ps -ef grep fact_server <-- 3 gray 28332 28192 0 15:17 pts/1 00:00:00 grep fact_server medusa$ fact_client linux <-- 4 Factorial Calculator Enter a positive integer value 12 12! = 479001600
(1) Put the server in the background on the host called linux .
(2) Run the client program and pass it the host name linux .
(3) On a different host ( medusa ) verify the server program is not running.
(4) Run the client program and pass it the host name linux .