Remote Procedure Call远程过程调用
经典面试题:rpc和http的区别是什么?
http是一种应用层协议,但rpc不是协议。理论上来讲,也可以使用http实现rpc,但基于性能、安全性、拓展性等方面的考虑,一般会为rpc设计另外的协议,而在teamtalk中,使用了自定义的请求头字段配合protobuf实现了rpc。
源码剖析
我们以消息服务器msg_server举例,当用户上线下线状态改变时,要向login_server和route_server发送消息,更新用户状态,代码片段如下:
// 只有上下线通知才通知LoginServer
if (user_status == ::IM::BaseDefine::USER_STATUS_ONLINE) {
IM::Server::IMUserCntUpdate msg;
msg.set_user_action(USER_CNT_INC);
msg.set_user_id(pImUser->GetUserId());
CImPdu pdu;
pdu.SetPBMsg(&msg);
pdu.SetServiceId(SID_OTHER);
pdu.SetCommandId(CID_OTHER_USER_CNT_UPDATE);
send_to_all_login_server(&pdu);
...
}
在上面的代码中我们创建了一个msg实例,并设置了msg的一些数据,接下来又创建了pdu实例,将msg传入,最终通过一个send_to_all_login_server接口将pdu发送给login服务器。
其中msg的类声明如下:
class IMUserCntUpdate : public ::google::protobuf::MessageLite
可以看到,msg实际上就是把protobuf进行了包装。
接下来我们再看看CImPdu中有一个如下的结构体:
typedef struct {
uint32_t length; // the whole pdu length
uint16_t version; // pdu version number
uint16_t flag; // not used
uint16_t service_id; //
uint16_t command_id; //
uint16_t seq_num; // 包序号
uint16_t reversed; // 保留
} PduHeader_t;
现在我们就明白了pdu就是一个rpc的数据包,里面有两个部分:消息头,消息体,其中消息体就是前面传入的msg。
接下来我们看pdu是如何发送到login服务器的,也就是send_to_all_login_server的代码:
void send_to_all_login_server(CImPdu* pPdu)
{
CLoginServConn* pConn = NULL;
for (uint32_t i = 0; i < g_login_server_count; i++) {
pConn = (CLoginServConn*)g_login_server_list[i].serv_conn;
if (pConn && pConn->IsOpen()) {
pConn->SendPdu(pPdu);
}
}
}
只有十行代码,逻辑就是取出每一个login conn实例,调用SendPdu接口然后调用基类的send接口,再调用CBaseSocket类的send接口,将数据通过socket发送出去,其中login服务器的文件描述符就在loginconn中的m_handle字段中存储。
这一段是最重要的,描述了函数如何层层调用最后把数据发送出去的过程,因为时间关系代码我就懒得贴了...