rpc函数参数及返回值的传递跟普通本地环境下的函数调用还是有很大区别的。本文着重讨论多线程(rpcgen -M)环境下缓冲区(字符串)是怎么传递的。
下面的例子test中client传“hello“给server,server统计接受的client请求数,并返回“hello“+请求数给client。显然,client与server之间传递的消息都是缓冲区(字符串),而不是基本的定长的数据类型。
这是test.x
/* rpcgen -M test.x */
const MAXLEN = 255; typedef string filename<MAXLEN>; program test{ version test_ver{ filename func(filename) = 1; } = 1; } = 177;
|
这是client.c
#include "test.h" #define host "localhost" /* gcc -o client test_clnt.c client.c test_xdr.c * /
int main() { CLIENT *cl; enum clnt_stat res; filename in = "hello"; filename out;
out = (filename) malloc(sizeof(MAXLEN)); printf("out = [%p]\n", out); cl = clnt_create (host, test, test_ver, "tcp"); //这里的test,test_ver是使用rpcgen生成的test.h文件里包含的,可以查看test为177,test_ver为1,也是test.x中定义的。host是机器的IP地址 if (cl == NULL) { clnt_pcreateerror (host); return 1; } res = func_1(&in, &out, cl); //这里的func_1()函数是test_clnt.c文件里的函数,调用的时候可以直接传递到server端。 if (res != RPC_SUCCESS){ clnt_perror(cl, host); return 1; } printf("receive : [%p]%s\n", out, out); clnt_destroy(cl); return 0; }
|
这是server.c
#include "test.h" /* gcc -o server test_svc.c server.c test_xdr.c */ bool_t func_1_svc(filename *in, filename *out, struct svc_req *s) //这里的func_1_svc()其实就是服务器端的调用func代码,课client端的func_1()以及.x文件中的fucn是一个函数,只是一种机制 { //具体不是很清楚,中间PRC屏蔽了很多细节的东西。但是使用的时候,需要把这些代码放到自己的工程里面。可以修改名字, printf("receive : %s\n", *in); //但是,必须知道调用关系。 filename out1; static int i; out1 = (filename) malloc (sizeof(MAXLEN)); printf("out1 = %p\n", out1); sprintf(out1, "%s%d\n", *in, i++); *out = out1; printf("return out : %s\n", *out); return TRUE; }
int test_1_freeresult (SVCXPRT *transp, xdrproc_t xdr_result, caddr_t result) { xdr_free(xdr_result, result); }
|
client端运行结果:
$ ./client out = [0x602010] receive : [0x602010]hello0 $ ./client out = [0x602010] receive : [0x602010]hello1 $ ./client out = [0x602010] receive : [0x602010]hello2
|
server端运行结果:
$ ./server receive : hello out1 = 0x609b00 return out : hello0 receive : hello out1 = 0x609b00 return out : hello1 receive : hello out1 = 0x609b00 return out : hello2
|
从以上结果看出,client端和server端都接收到正确的数据了。
1, client端:从out输出的地址可以看出,out(其实是char*类型)指向的地址并没有被改变,也就是说调用
func_1并没有为out重新分配空间。
因此可以推断,当
“ func_1(&in, &out, cl);”
返回时,rpc已经把返回的字符串拷贝到out指向的地址空间了,而不是重新分配空间,再把out指向该空间。因此这里就有一个问题要注意:
client端指向期待server返回的地址空间要在调用前分配好(out指向的空间要事先被分配好),不然就会出现“Segmentation fault”错误(读者不妨自己验证,只需把 out = (filename) malloc(sizeof(MAXLEN));这句去掉就行了。 |
2, server端:out的空间是不是也要事先分配好。也就是说,是不是一定要像上面server.c中先通过out1分配空间,再把out指向out1呢?试试再说。把server.c改成如下,client.c不变:
bool_t func_1_svc(filename *in, filename *out, struct svc_req *s) { printf("receive : %s\n", *in); static int i; sprintf(*out, "%s%d\n", *in, i++); printf("return out : [%p]%s\n", *out, *out); return TRUE; }
int test_1_freeresult (SVCXPRT *transp, xdrproc_t xdr_result, caddr_t result) { xdr_free(xdr_result, result); }
|
结果却在server端显示:
*receive : hello return out : [0x607930]hello0 *** glibc detected *** ./server: double free or corruption (out): 0x0000000000607930 *** ======= Backtrace: ========= /lib/libc.so.6[0x2ad05656faad] /lib/libc.so.6(cfree+0x76)[0x2ad056571796] /lib/libc.so.6(xdr_string+0xce)[0x2ad0565e864e] ./server[0x400e22] /lib/libc.so.6(xdr_free+0x15)[0x2ad0565e7e45] ./server[0x400dfb] ./server[0x400bd1] /lib/libc.so.6(svc_getreq_common+0x1de)[0x2ad0565e5e5e] /lib/libc.so.6(svc_getreq_poll+0xaa)[0x2ad0565e620a] /lib/libc.so.6(svc_run+0xa9)[0x2ad0565e68c9] ./server[0x400d21] /lib/libc.so.6(__libc_start_main+0xf4)[0x2ad056520b74] ./server[0x400a09] ======= Memory map: ========
[...]
|
上面输出显示,在
func_1_svc返回之后出现了
“double free or corruption” out的错误,也就是说out指向的空间被double free了,在
Backtrace里面还看到了
熟悉的xdr_free(这是需要自己写的,在
test_1_freeresult中,
负责释放server端程序内存的函数),于是不难想到这可能是xdr_free试图free掉out的空间,但是out的空间根本就没有被分配过(或者说已经被释放过了),所以出现了double free的错误。于是便不难想到,把xdr_free去掉是否就可以了?再试试,把server.c中的
xdr_free(xdr_result, result);这句去掉,结果:
client端输出:
$ ./client out = [0x602010] receive : [0x602010] $ ./client out = [0x602010] receive : [0x602010] $ ./client out = [0x602010] receive : [0x602010]
|
可以看到client根本没有收到server端传来的数据
server端的输出:
$ ./server receive : hello return out : [0x607930]hello0 receive : hello return out : [0x607930]hello1 receive : hello return out : [0x607930]hello2
|
server端是正确的。
这说明,server端在执行
func_1_svc之后
并没有把
out指向的内存传输给client端——这应该是svc_sendreply函数做的事情,在rpcgen生成的test_svc.c中可以找到。
所以结论是:server端对要返回给client的内存空间也要自己事先分配好(rpcgen没有帮你做),之后该区间要通过
test_1_freeresult来释放。
下面的图反映了rpc数据的传输流程:
另外,上图还反映了2点:
1, in 在server是“只读”的,因为最后server并不把in返回给client,所以写也是白写
2, out在server中是“只写”的,因为client并没有把out的内容传给server。
上面是自己摸索的,如果有错还请不吝赐教