在分布式系统中,远程过程调用(RPC)是非常重要的通信方式。下面对如何进行RPC程序的编写进行说明。
Reference:http://www.krzyzanowski.org/rutgers/notes/pdf/ra-sunrpc.pdf
实现RPC需要写三个文件:IDL语言描述的数据定义,client端程序,server端程序。如图:
下面举例说明,首先给出本地运行的程序,得到当前时间,并转换为字符串,显示在屏幕上。
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。