RPC调用的一个例子

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数据的传输流程:
prc调用的一个例子 - xindufresne - xindufresne的技术博客
另外,上图还反映了2点:
1, in 在server是“只读”的,因为最后server并不把in返回给client,所以写也是白写
2, out在server中是“只写”的,因为client并没有把out的内容传给server。
上面是自己摸索的,如果有错还请不吝赐教

转载于:https://www.cnblogs.com/xindufresne/p/3409762.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,这里是一个完整的 SPDK 注册自己的 RPC 程序的例子: 首先,在 SPDK 中,要注册 RPC 程序,需要使用 `spdk_rpc_register()` 函数。该函数的参数为 `struct spdk_rpc_method` 结构体类型指针,该结构体包含了 RPC 方法的名称、处理函数等信息。 例如,我们要创建一个名为 `example_rpc` 的 RPC 方法,处理函数为 `example_rpc_handler`,则可以定义如下的 `struct spdk_rpc_method` 结构体: ```c static void example_rpc_handler(struct spdk_jsonrpc_request *request, const struct spdk_json_val *params) { // 处理 RPC 请求的代码 } static struct spdk_rpc_method example_rpc_method = { .name = "example_rpc", .method = example_rpc_handler, .min_args = 0, .max_args = 0, }; ``` 在上述代码中,我们定义了 `example_rpc_handler()` 函数来处理 RPC 请求,在该函数中可以编写具体的业务逻辑。同时,我们还定义了 `example_rpc_method` 结构体,其中包含了 RPC 方法的名称、处理函数、参数个数等信息。 接下来,我们需要在 SPDK 初始化时注册该 RPC 方法。在 SPDK 初始化时,会调用 `spdk_rpc_initialize()` 函数,该函数会依次调用所有已注册的 RPC 方法的初始化函数。 我们可以在 `spdk_rpc_initialize()` 函数中调用 `spdk_rpc_register()` 函数来注册我们的 `example_rpc_method`: ```c static void example_rpc_init(void) { spdk_rpc_register(&example_rpc_method); } static void spdk_app_start(void *arg1, void *arg2) { // 初始化 SPDK spdk_rpc_initialize(NULL); // 注册 example_rpc 方法 example_rpc_init(); // ... } ``` 在上述代码中,我们定义了一个 `example_rpc_init()` 函数,在该函数中调用 `spdk_rpc_register()` 函数来注册 `example_rpc_method`。接着,在 `spdk_app_start()` 函数中,我们先调用 `spdk_rpc_initialize()` 函数来初始化 RPC 系统,然后再调用 `example_rpc_init()` 函数来注册 `example_rpc_method`。 这样,当客户端发起名为 `example_rpc` 的 RPC 请求时,SPDK 就会调用 `example_rpc_handler()` 函数来处理该请求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值