Friday, April 24, 2009

Distributed Systems Programming with rpcgen

rpcgen utility enables developers to write distributed applications.

It generates stubs for client and server side automatically. Programmer is required to write 3 additional programs to use rpcgen tool.

You need to have rpcgen utility installed on your machine to run the example. Ubuntu synaptic easily installs it for you.

1-) Write the prog.x file which contains definition for required routines. You can give any name for this *.x file ending with .x extension.You need to declare remote procedures in a remote program.

/*  prog.x
                                                                
 * Define your procedure here
 
 * function_1 dir_list_1(string) returns the output to the client 
 * (takes one string argument)

 * function_2 execute_uptime_1() returns the output to the client 
 * (no argument)
*/    
program RPC_PROG {                                               
    version RPC_VERS {                                         
    string DIR_LIST(string) = 1; /* procedure number = 1 */
    string EXECUTE_UPTIME(void) = 2; /* procedure number = 2 */
    } = 1; /* version number = 1 */
} = 0x12345678; /* program number = 0x12345678 */


2-) Write remote procedure implementation in a *.c file.

This file contains implementation of procedures declared in prog.x file.

/*  server_source.c   */  
#include <time.h>
#include <rpc/rpc.h> /* standard RPC include file */
#include "prog.h" /* this file is generated by rpcgen */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define READ_MAX_SIZE 512

/*Return the listing of all files in a directory function1*/
char** dir_list_1_svc(char** location, struct svc_req* arg2)
{
 FILE* fpipe;
 char* command="ls -l ";
 char* commandAndDirName;
 static char* readPipeInto;
 readPipeInto = (char*)malloc((READ_MAX_SIZE + 1)*sizeof(char));
 memset(readPipeInto, 0, READ_MAX_SIZE + 1);
 commandAndDirName=(char*)malloc((strlen(command)+strlen(*location)+1)*sizeof(char));
 strcpy(commandAndDirName, command);
 strcat(commandAndDirName, *location);
 //execute command ls -l
 if ( !(fpipe = (FILE*)popen(commandAndDirName,"r")) )
 {
    perror("Cant open pipe!");
    exit(1);
 }
 //store result in readPipeInto
 fread((char*)readPipeInto, READ_MAX_SIZE, 1, fpipe); 
 pclose(fpipe); 
 free(commandAndDirName);
 //return output to the client
   return (char**)&readPipeInto;
}
/* Return the result of command uptime function2*/
char** execute_uptime_1_svc(void* arg1,struct svc_req* arg2)
{
 FILE* fpipe;
 char* command="uptime";
 static char* readPipeInto;
 readPipeInto = (char*)malloc((READ_MAX_SIZE + 1)*sizeof(char));
 memset(readPipeInto, 0, READ_MAX_SIZE + 1);
 //execute command uptime
 if ( !(fpipe = (FILE*)popen(command,"r")) )
 { 
    perror("Cant open pipe!");
    exit(1);
 }
 //store result in readPipeInto
 fread((char*)readPipeInto, READ_MAX_SIZE, 1, fpipe); 
 pclose(fpipe);
 //return output to the client
 return (char**)&readPipeInto;
}

1st procedure at server program
" char** dir_list_1_svc(char** location, struct svc_req* arg2) " 


corresponds to " string DIR_LIST(string) " procedure declaration in prog.x file.

DIR_LIST procedure takes a string directory location such as "/home" from client program as an input parameter and appends this parameter to "ls -l" program at server procedure.


So, output of "ls -l /home" is returned back to the client from the server.

For return values of procedures, one more level of indirection is added to each procedure signature and parameters at server side.


For the 1st procedure at client program "string DIR_LIST(string) ", return value is changed from (char*) to (char**) .

2nd procedure at server program
" char** execute_uptime_1_svc(void* arg1,struct svc_req* arg2) "
corresponds to " string EXECUTE_UPTIME(void) " procedure declaration in prog.x file.

EXECUTE_UPTIME(void) procedure does not take any parameter from the client and executes the external program "uptime". 


Result of execution is sent back to the client.

At both of the above procedure implementations, it is common that programs (ls -l and uptime) are executed at server side and the result is written into a pipe. 


Then command output is read from that pipe and returned back to the client side.

3-) Write the main client program that calls the remote procedures from server.




/* client_source.c file */

#include <stdio.h>
#include <stdlib.h>                                       
#include <rpc/rpc.h> /* standard RPC include file */     
#include "prog.h" /* this file is generated by rpcgen */ 
                    
main(int argc, char *argv[])                               
{ 
   CLIENT *cl; /* RPC handle */

   char *server;
   /* return value from dir_list_1() function1*/
   char** resultStringFromServerDirList;
   /* return value from execute_uptime_1 function2*/ 
   char** resultStringFromServerUptime; 
   if (argc != 2) {
       fprintf(stderr, "usage: %s hostname\n", argv[0]);
       exit(1);
   }

   server = argv[1];
   /* Create client handle */
   if ((cl = clnt_create(server, RPC_PROG, RPC_VERS, "udp")) == NULL) {
       /* can't establish connection with server */
       clnt_pcreateerror(server);
       exit(2);
   }
   char* directory = "/home";

   /* call remote procedure dir_list */
   resultStringFromServerDirList = dir_list_1((char**)&directory, cl);
   if (resultStringFromServerDirList == NULL) {
      clnt_perror(cl, server);
      exit(3);
   }
   printf("\n\n");
   printf("Function1 Result of Directory Listing is: \n\n");
   printf("%s",*resultStringFromServerDirList);
   printf("\n\n");

   /* call remote procedure execute_uptime */
   resultStringFromServerUptime = execute_uptime_1(NULL, cl);
   if (resultStringFromServerUptime == NULL) {
      clnt_perror(cl, server);
      exit(4);
   }
   printf("Function2 Result of Command uptime is: \n\n");
   printf("%s",*resultStringFromServerUptime);
   printf("\n\n");
   
   clnt_destroy(cl); 
   exit(0);
}

After writing those 3 files, you can use rpcgen tool to generate related client and server programs.


Put "client_source.c" "prog.x" and "server_source.c" files into the same folder. 

Then by using cc -o from the command line create client and server executables separately.


Sample Run:
------------------------------------------------------------
Prepare server and run from the console: Open your console to run following commands.

user@machine:~/Desktop/user$ rpcgen prog.x
user@machine:~/Desktop/user$ cc -o client client_source.c prog_clnt.c 
user@machine:~/Desktop/user$ cc -o server server_source.c prog_svc.c 
user@machine:~/Desktop/user$ ./server 


"rpcgen prog.x" command creates prog_clnt.c(client stub) prog_svc.c(server stub) and prog.h header file. 


At the end of successful compilation client and server executables are located in the same directory. Then open a new terminal tab and Run client:

user@machine:~/Desktop/user$ ./client localhost


Server returns the output of executed programs to the client.

user@machine:~/Desktop/user$ ./client localhost


Function1 Result of Directory Listing is: 

total 4
drwxr-xr-x 52 user user 4096 2009-04-25 01:04 user


Function2 Result of Command uptime is: 

 01:05:49 up  4:08,  3 users,  load average: 0.17, 0.36, 0.25


user@machine:~/Desktop/user$ 

1 comment:

  1. when i did ./server

    i got the error :

    Cannot register service: RPC: Unable to receive; errno = Connection refused
    unable to register (RPC_PROG, RPC_VERS, udp)

    could you please help me resolve this

    ReplyDelete