2021SC@SDUSC
回到CallMethod函数。
接下来将设置各种成员,如超时时间,response等,因为demo中场景没有设置loadbalancer,所以是SingleServer
通过_serialize_request,这里会把request序列化到controller的request_buf,原因是为了重试,一个RPC过程中只会调用一次。然后启动定时任务处理超时。
接下来就是真正的连接和发送了
然后看下controller的IssueRPC。
首先通过current_id()得到该次请求的CallId,如上所述,第一次请求版本号为1 + 0 + 1 = 2
然后通过server_id获取到Socket保存在tmp_sock中、
因为当前使用的是单连接,所以直接将sending_sock指向tmp_sock,然后调用协议的pack_request,本例中协议为baidu_std,将_request_buf,correlation_id等打包到packet中,每次请求前都会调用,包括重试,其中attachment不会经过序列化,而是直接原始的二进制数据。
然后调用Write方法将数据写入Socket。
brpc在多线程向fd写数据时实现了wait-free,这部分逻辑已经抽出单独的ExecutionQueue,之前的文章已经分析过,这里再简单看下。
先新建一个WriteRequest,WriteRequest就是ExecutionQueue中的节点结构,req中的data设置为本次请求要发送的数据data,然后指向UNCONNECTED,然后执行StartWrite
首先通过exchange原子修改当前队列头节点_write_head为新建的req,并返回旧的头节点prev_head,如果prev_head不为null,那么说明当前有bthread在向fd中写数据,因为这个bthread写完队列中所有的WriteRequest,因此直接将req链入队列即可。如果prev_head是null,那么当前线程/bthread就会去写数据。
然后开始调用ConnectIfNot
到现在为止,client端其实还没有向server端发起连接,因此fd为-1,于是调用Connect建立连接。
为了性能的考虑,brpc使用的是异步连接,然后复用EventDispatcher的逻辑来epoll通知连接事件的完成。具体的,首先新建一个socket,然后设置为non_blocking,然后调用connect连接server。
新建一个EpollOutRequest req,设置req的fd和data,然后新建一个Socket connect_id,新建这个Socket的目的就是上文提到的复用EventDispatcher逻辑(这两个Socket的"回调函数"不一样)。新建的Socket的user为这个req。
然后向EventDispatcher中注册epoll out事件。
回到StartWrite的1529行,此时返回的是1,表示正在连接,因此直接返回到IssueRPC执行bthread_id_unlock。
该函数是对ID对象的释放,首先获取到ID对象,此时ID的butex要么是locked,要么是contended,首先要将butex置为first_ver,如果当前状态为contended,那么说明有其他线程/bthread被butex_wait挂起在该butex上,因此使用butex_wake唤醒他,继续竞争ID对象。
此时IssueRPC执行结束了,假设此时完成了对server端的连接,那么将会产生epollout事件,如下:
直接调用HandleEpollOut,data.u64在上文的Connect中被设置为了新建的Socket connect_id。