RPC学习 teamtalk是如何实现服务间通信的|青训营

108 阅读2分钟

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字段中存储。
这一段是最重要的,描述了函数如何层层调用最后把数据发送出去的过程,因为时间关系代码我就懒得贴了...