Sun RPC的使用

    在分布式系统中,远程过程调用(RPC)是非常重要的通信方式。下面对如何进行RPC程序的编写进行说明。

     Reference:http://www.krzyzanowski.org/rutgers/notes/pdf/ra-sunrpc.pdf      实现RPC需要写三个文件:IDL语言描述的数据定义,client端程序,server端程序。如图:

rpcbind 端口号每台机器都不一样 sunrpc端口_templates



    下面举例说明,首先给出本地运行的程序,得到当前时间,并转换为字符串,显示在屏幕上。

stand_alone.c:
 #include <stdio.h>
 long bin_date(void);
 char *str_date(long bintime); int main(int argc, char **argv)
 {
     long lresult; /* return from bin_date */
     char *sresult; /* return from str_date */
     if (argc != 1)
     {
         fprintf(stderr, "usage: %s\n", argv[0]);
         exit(1);
     }
     /* call the procedure bin_date */
     lresult = bin_date();
     printf("time is %ld\n", lresult);
     /* convert the result to a date string */
     sresult = str_date(lresult);
     printf("date is %s", sresult);
     exit(0);
 } /* bin_date returns the system time in binary format */
 long bin_date(void)
 {
     long timeval;
     long time(); /* Unix time function; returns time */
     timeval = time((long *)0);
     return timeval;
 } /* str_date converts a binary time into a date string */
 char *str_date(long bintime)
 {
     char *ptr;
     char *ctime(); /* Unix library function that does the work */
     ptr = ctime(&bintime);
     return ptr;
 }


    gcc编译此程序并运行:
time is 1270382997
date is Sun Apr 4 20:09:57 2010

    下面将此程序中的两个本地调用函数bin_date和str_date实现为RPC函数。
    首先进行date.x文件的编写,即使用IDL进行函数描述:
date.x:

program DATE_PROG
 {
                 version DATE_VERS
                 {
                                 long BIN_DATE(void) = 1;
                                 string STR_DATE(long) = 2;
                 } = 2;
 } = 0x123456;


    其中第7行的“2“是版本号,最后一行的数字是RPC程序ID,不能与系统中其他RPC程序有冲突。写完后使用rpcgen编译此文件(因为上面的version号为2,所以生成的函数都是小写函数名加上"-2-"):
    rpcgen -C date.x
    会得到三个文件:date_clnt.c,date.h,date_svc.c。date.h文件是RPC需要包含的头文件。
    下面开始编写server端程序:
    简单起见,使用rpcgen生成一个server端模板:
    rpcgen -C -Ss date.x >server.c
     生成后的server.c文件内容如下:

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 "date.h"
 long *
 bin_date_2_svc(void *argp, struct svc_req *rqstp)
 {
         static long result;         /*
          * insert server code here
          */         return &result;
 } char **
 str_date_2_svc(long *argp, struct svc_req *rqstp)
 {
         static char * result;         /*
          * insert server code here
          */         return &result;
 }


    可见我们只需要进行这两个函数内容的填写即可。因为RPC最终需要将执行结果返回给client端,所以这里的返回值都是指针形式。填写过后的server.c文件如下:

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 "date.h"
 long *
 bin_date_2_svc(void *argp, struct svc_req *rqstp)
 {
         static long result;
         /*
          * insert server code here
          */
     long time(); /* Unix time function; returns time */
     result = time((long *)0);
         return &result;
 } char **
 str_date_2_svc(long *argp, struct svc_req *rqstp)
 {
         static char * result;
         /*
          * insert server code here
          */
     char *ctime(); /* Unix library function that does the work */
     result = ctime(argp);
         return &result;
 }

    下面开始编写client端程序。client端程序需要包含<rpc/rpc.h>头文件,还需要创建一个CLIENT对象以与server端进行通信。在client端程序运行时加入-h参数指定server端的hostname,默认为localhost。一定要注意RPC调用是可能失败的,如server端没有启动或网络问题等。
client.c:

#include <rpc/rpc.h>
 #include "date.h" int main(int argc, char **argv)
 {
     extern char *optarg;
     extern int optind;
     char *server = "localhost";   /* default */
     int err = 0, c;
     while ((c = getopt(argc, argv, "h:")) != -1)
     {
         switch (c)
         {
             case 'h':
                 server = optarg;
                 break;
             case '?':
                 err = 1;
                 break;
         }
     }     /* exit if error or extra arguments */
     if (err || (optind < argc))
     {
         fprintf(stderr, "usage: %s [-h hostname]\n", argv[0]);
         exit(1);
     }     CLIENT *cl; /* rpc handle */
     cl = clnt_create(server, DATE_PROG, DATE_VERS, "udp");     long *lresult;
     if ((lresult = bin_date_2(NULL, cl)) == NULL)
     {
         clnt_perror(cl, server); /* failed! */
         exit(1);
     }
     printf("time on %s is %ld\n", server, *lresult);     char **sresult;
     if ((sresult = str_date_2(lresult, cl)) == NULL)
     {
         /* failed ! */
         clnt_perror(cl, server);
         exit(1);
     }
     printf("date is %s", *sresult);

     clnt_destroy(cl);
     return 0;
 }

     至此,RPC程序编写完成,编译过程如下:
     编译client端:gcc -o client client.c date_clnt.c -lnsl
     编译server端:gcc -o server -DRPC_SVC_FG server.c date_svc.c -lnsl
     此处加入RPC_SVC_FG编译参数是为了让server端在前台运行,默认是在后台运行。
     
     编译完毕之后,首先运行server端:
 ./server
     之后运行client端:
 ./client
     执行结果如下:
 time on localhost is 1270384696
 date is Sun Apr 4 20:38:16 2010

     如果在执行server端时出现错误:
 Cannot register service: RPC: Unable to receive; errno = Connection refused
     说明系统中没有运行portmap服务,可以通过/etc/init.d/portmap start运行。如果没有安装,debian系LINUX可以通过: sudo apt-get install portmap


   进行安装。如果没有portmap服务,运行client端可能会Segmentation Fault。