RPC状态机执行过程

RPC状态机执行过程

可能了解这个状态过程对以后工作没用,但还是想了解一下具体执行过程,也算是对自己以前一个工作的交代。
我们用的是SUNRPC,在内核中实现的RPC,代码在net/sunrpc/。

同步RPC和异步RPC

RPC调用有同步和异步两种。

/**
 * rpc_call_sync - Perform a synchronous RPC call
 * @clnt: pointer to RPC client
 * @msg: RPC call parameters
 * @flags: RPC call flags
 */
int rpc_call_sync(struct rpc_clnt *clnt, const struct rpc_message *msg, int flags);
/**
 * rpc_call_async - Perform an asynchronous RPC call
 * @clnt: pointer to RPC client
 * @msg: RPC call parameters
 * @flags: RPC call flags
 * @tk_ops: RPC call ops
 * @data: user call data
 */
int
rpc_call_async(struct rpc_clnt *clnt, const struct rpc_message *msg, int flags, const struct rpc_call_ops *tk_ops, void *data);

执行时都会调用rpc_run_task(&task_setup_data);

rpc_run_task

调用rpc_new_task分配一个新的RPC task,再调用rpc_execute执行RPC 任务,执行结果rpc_task返回。

rpc_new_task

/*
 * Create a new task for the specified client.
 */
struct rpc_task *rpc_new_task(const struct rpc_task_setup *setup_data)
{
    struct rpc_task *task = setup_data->task;
    unsigned short flags = 0;

    if (task == NULL) {
        task = rpc_alloc_task();
        if (task == NULL)
            goto out;
        flags = RPC_TASK_DYNAMIC;
    }

    rpc_init_task(task, setup_data);

    task->tk_flags |= flags;
    dprintk("RPC:       allocated task %p\n", task);
out:
    return task;
}

如果task是空,则调用rpc_alloc_task分配一个task,调用rpc_init_task用struct rpc_task_setup初始化rpc_task,设置task指向的内存空间为全0,会判断task->tk_ops->rpc_call_prepare != NULL,其实是判断task_setup_data->callback_ops回调函数是否有rpc_call_prepare,

const struct rpc_call_ops nfs4_entrycommit_ops = {
    .rpc_call_prepare = nfs4_entrycommit_prepare,
    .rpc_call_done = nfs4_entrycommit_done,
    .rpc_release = nfs4_entrycommit_release,
};

前面在初始化struct rpc_task_setup task_setup_data变量时,有过初始化,rpc_call_prepare非空,task->tk_action = rpc_prepare_task,

/*
 * Helper to call task->tk_ops->rpc_call_prepare
 */
void rpc_prepare_task(struct rpc_task *task)
{
    task->tk_ops->rpc_call_prepare(task, task->tk_calldata);
}

rpc_prepare_task通过函数指针调用函数nfs4_entrycommit_prepare执行,它会调用rpc_call_start(task),rpc_call_start实际执行task->tk_action = call_start,task->tk_action是函数指针,指向函数call_start,函数名是函数地址,rpc执行过程是状态机,但实际上没有改变状态,是通过回调函数实现的,task->tk_action指向不同的函数,下次就执行不同的函数,另外在struct rpc_task中还有void (tk_callback)(struct rpc_task ),我理解的是异步RPC时回调时使用的,稍后会有验证。

struct rpc_task {
/*
     * RPC call state
     */
    struct rpc_message  tk_msg;     /* RPC call info */

    /*
     * callback to be executed after waking up
     * action   next procedure for async tasks
     * tk_ops   caller callbacks
     */
    void            (*tk_callback)(struct rpc_task *);
    void            (*tk_action)(struct rpc_task *);
};

rpc_init_task结束后,rpc_new_task也就结束,至此初始化的工作已经完成。

_rpc_execute

rpc_new_task执行结束,开始执行rpc_execute。
rpc_execute调用__rpc_execute开始执行,函数__rpc_execute的注释是这样的,This is the RPC `scheduler’ (or rather, the finite state machine),这才是RPC中传说的有限状态机。
for不停循环,首先判断是否有pending callback,等待回调的函数,void (save_callback)(struct rpc_task ),这是在使用前需要先声明;再判断,这才是有限状态机的下一步。

if (!RPC_IS_QUEUED(task)) {
            if (task->tk_action == NULL)
                break;
            task->tk_action(task);
        }
#define RPC_IS_QUEUED(t)    test_bit(RPC_TASK_QUEUED, &(t)->tk_runstate)

RPC_IS_QUEUED是判断task的状态,rpc_execute在调用_rpc_execute前会调用rpc_set_active(task),设置rpc_task的状态,设置tk_runstate为RPC_TASK_ACTIVE,一共有3个状态。

#define RPC_TASK_RUNNING    0
#define RPC_TASK_QUEUED     1
#define RPC_TASK_ACTIVE     2

task->tk_action(task),函数指针开始调用call_start,call_start执行会修改task->tk_action,task->tk_action = call_reserve,call_start执行完后,再进入for循环,继续循环,只要task->tk_action非空,就会继续执行,如果是空,break跳出循环。
此处发现一写的很好的博客,参考会给出。
执行过程是:
->call_start
–>call_reserver:调用xprt_reserve,分配一个RPC 请求槽,就是struct rpc_xprt;
—>call_reserveresult
—->call_allocate
—–>call_bind
——>call_connect: 向server端发送前,先连接到server端
还是看图吧,跟着图走一遍流程就清楚了,pnfs的内核代码。
rpc状态转换图
看call_transmit,传输RPC请求,并等待返回。
调用rpc_xdr_encode(task),进行编码,RPC报文分为两部分:RPC报文头和净荷信息。RPC报文头通过rpc_encode_header()组装,净荷信息通过nfs3_xdr_enc_remove3args()组装。它再调用task->tk_msg.rpc_proc->p_encode,调用具体的编码函数进行编码,task->tk_msg是结构体struct rpc_message,task->tk_msg.rpc_proc是在初始化结构体rpc_message时初始化了。

struct rpc_message msg = {
        .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_ENTRYCOMMIT],
};

结构体数组的一个元素,看nfs4_procedures[]这个结构体数组又发现,proc是宏定义,p_encode被初始化为nfs4_xdr_enc_entrycommit,查看该函数的代码,将向server端提交的信息编码。
函数rpc_xdr_encode调用之前初始化的函数nfs4_xdr_enc_entrycommit,其实我是不太能理解这个调用,我理解的函数调用,即使是函数指针调用,也是需要传递参数的,以下的函数调用连传递参数都没有。
SX了,这仅仅是赋值,encode还是指针类型,真正调用时函数指针且有参数传递,在rpcauth_wrap_req中。

encode = task->tk_msg.rpc_proc->p_encode;
task->tk_status = rpcauth_wrap_req(task, encode, req, p,
            task->tk_msg.rpc_argp);

encode是函数指针传递,在函数rpcauth_wrap_req,调用 encode(rqstp, data, obj),执行,这个应该涉及函数指针的多种调用方式了。
函数call_transmit,编码结束后,调用xprt_transmit(task),该函数的注释是说发送一个RPC请求给一个端口。
参考[2]博客有如下说:
在实际传输之前要用XDR规范将数据进行编码,这是rpc的约定。看一下xprt_transmit就会发现,底层的rpc使用socket将数据传给服务器的,当然也可以用别的机制,比如任何底层链路协议,只要能进行网络传输的就可以。
我没看出来是socket传输的啊。
另外,请看以下call_status,我的理解是这里状态转变的地方,根据不同的状态采用不同的操作。
client端将RPC请求发送给server端,server端收到请求并处理,将处理结果返回给client端,client端收到后,还会执行到call_status,但是中间是如何执行到call_status的,暂不清楚,call_status会判断task的status,修改task->tk_action=call_decode,再回到_rpc_executefor循环,执行call_decode,调用rpcauth_unwrap_resp,它再调用具体的解码函数nfs4_xdr_dec_entrycommit进行解码。
看到这里,struct rpc_call_ops nfs4_entrycommit_ops,有3个函数,只有1个函数执行,那后两个函数呢?
在call_transmit,执行成功后,task->tk_action = rpc_exit_task,修改task->tk_action,执行rpc_exit_task,通过函数指针task->tk_ops->rpc_call_done调用nfs4_entrycommit_done,entrycommti是完成了,现在要释放占用的资源。
这个时候task->tk_action为空,跳出__rpc_execute的for不停循环,执行rpc_release_task,[rpc_release_task–>rpc_put_task–>rpc_free_task–>rpc_release_calldata],通过函数指针执行前面注册的release函数,nfs4_entrycommit_release。
一般会有3个函数
rpc_call_prepare():发起RPC请求报文前执行的函数,修改task->tk_action的指向;
rpc_call_done():处理完RPC应答报文后执行的函数;
rpc_release()是释放资源的函数,比如释放RPC请求过程中申请的内存,当RPC执行完毕或者失败时都会调用这个函数。

参考:
[1] http://m.blog.csdn.net/blog/ta_nk/7172927
[2] http://blog.csdn.net/dog250/article/details/5303423

### 回答1: 下面是一个使用 C 语言监听 RPC 服务程序状态是否崩溃或掉线的简单示例代码: ``` #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define PORT 50000 #define BUFSIZE 1024 int main(int argc, char* argv[]) { int sockfd; struct sockaddr_in server_addr; /* 创建套接字 */ sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { printf("Error creating socket: %s\n", strerror(errno)); exit(1); } /* 设置服务器地址 */ memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(PORT); server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); /* 连接服务器 */ if (connect(sockfd, (struct sockaddr*) &server_addr, sizeof(server_addr)) < 0) { printf("Error connecting to server: %s\n", strerror(errno)); exit(1); } char buffer[BUFSIZE]; int num_bytes_received; /* 不断接收服务器的数据 */ while (1) { num_bytes_received = recv(sockfd, buffer, BUFSIZE, 0); if (num_bytes_received < 0) { printf("Error receiving data: %s\n", strerror(errno)); close(sockfd); exit(1); } else if (num_bytes_received == 0) { printf("Server disconnected.\n"); close(sockfd); break; } else { buffer[num_bytes_received] = '\0'; printf("Received: %s\n", buffer); } } return 0; } ``` 这段代码创建了一个客户端,并连接到服务器 `127.0.0.1` 的 `50000` 端口。然后,它不断地从服务器接收数据。 ### 回答2: 要编写一个C程序来监听RPC服务程序的状态是否崩溃或掉线,我们可以使用Socket编程以及心跳机制来实现。 首先,创建一个C程序,其中包含一个主函数和一个子函数。在主函数中,我们需要创建一个Socket对象,并设置Socket的地址和端口号,以便与RPC服务程序建立连接。 在子函数中,我们可以使用心跳机制来监听RPC服务程序的状态。心跳机制是指定时发送一个特定的请求,用于检测远程主机是否正常工作。我们可以定时向RPC服务程序发送一个心跳请求,如果在规定的时间内未收到回复,则可以认为RPC服务程序已崩溃或掉线。 具体实现中,我们可以使用Socket的发送和接收函数来发送心跳请求并等待回复。如果在规定的时间内没有收到回复,则可以认为服务程序已经崩溃或掉线。此时,我们可以触发相应的操作,如发送警报通知管理员或进行自动恢复等。 在实际编写过程中,还需注意处理异常情况,如网络连接失败、Socket函数调用出错等。可以使用try-catch来捕获异常并进行适当的处理,如输出错误信息或进行重连等。 总之,以上是一个关于监听RPC服务程序状态的C程序的基本框架。具体实现中,还需要根据具体的需求和环境进行调整和完善。 ### 回答3: 要编写一个C程序来监听RPC服务程序的状态是否崩溃或者掉线,可以采用以下步骤: 1. 引入必要的头文件和库文件,如<stdio.h>和<rpc/rpc.h>。 2. 定义RPC服务的结构体和函数原型。根据实际的RPC服务程序定义相应的结构体和函数原型,并确保程序能够连接到RPC服务。 3. 创建一个死循环来实时监测RPC服务程序的状态。可以使用while循环,使程序不停地进行状态检测。 4. 在循环中使用RPC函数来检测服务状态。可以通过调用适当的RPC函数来检查服务程序是否崩溃或掉线。例如,可以使用rpc_call()函数来向RPC服务程序发送一个心跳信号,并等待响应。如果没有收到响应或者收到了错误响应,就说明服务崩溃或掉线。 5. 根据检测结果采取相应的操作。当程序发现RPC服务程序崩溃或掉线时,可以记录错误日志、发送警报或者采取其他适当的措施。 6. 添加适当的延时。为了避免程序过于频繁地检测,可以在循环中添加适当的延时,以降低资源消耗。 7. 编译并运行程序。将代码编译为可执行文件,并在服务器上运行该程序,即可开始监听RPC服务程序的状态。 总结:编写一个C程序来监听RPC服务程序状态是否崩溃或掉线,关键是通过循环实时检测服务状态,并根据检测结果采取相应的操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值