鹅厂开源框架tars之网络层实现

   简介:https://github.com/Tencent/Tars

一、客户端同步调用远程服务部分:

使用过tars的人应该都能感受到RPC功能的方便性,tars调用远程服务的方法如下:

m_pRoleServantPrx = Application::getCommunicator()->stringToProxy<RoleServantPrx>(GAMECONFIG->sRoleServerObj);

这样就可以获取远程服务的rpc接口了,基本上是我用过的最方便的rpc框架么有之一了

具体框架源码做了什么事情,通过下图然后后续展开来分析

如上图,可以看到首先调用application的获取Communicator通讯器(Communicator 通信器,用于创建和维护客户端proxy),本文主要描述整个核心的网络流程,对辅助类后续会有文章展开分析

直接进入Communicator的stringToProxy成员函数,该函数主要功能如下:

ServantProxy * pServantProxy = getServantProxy(objectName,setName);

根据传入的objname例如例子中的sRoleServerObj(HERMAN.GateServer.GateServantObj@tcp -h 101.37.70.*** -t 60000 -p 5052),获取一个Servant的代理类proxy(Servant的含义是:tars每一个应用下面可以有多个服务,一个服务下面可以有多个servant),表现形式就是一个servant负责处理一个ip和端口的服务,因此servant的官方文档描述如下:

1:远程对象的本地代理
 2:同名servant在一个通信器中最多只有一个实例

要注意的一点就是在获取servant代理对象之前,客户端通讯器先调用了Communicator::initialize();

initialize干了这些事情:先new了一个_servantProxyFactory工厂类,然后根据服务配置的netthread网络线程数量,启动对应数量的CommunicatorEpoll(CommunicatorEpoll:用于处理epoll事件的线程类,线程定期run处理epoll的网络事件:handle((FDInfo*)data, ev.events)),CommunicatorEpoll构造的时候:注册了EPOLLIN事件,同时new了ObjectProxyFactory.

每个objectname在进程空间唯一有一个objectproxy
管理收发的消息队列

后续CommunicatorEpoll的run函数监听到EPOLLIN函数则处理来自网络接口的输入:handleInputImp处理收到的网络包,调用

遍历包链表,调用pTransceiver->getAdapterProxy()->finishInvoke(*it)函数,调用adapter代理类的成员函数:AdapterProxy::finishInvoke(ReqMessage * msg)如果是同步调用则唤醒ServantProxy线程。

至此,stringToProxy干的主要事情已经描述完毕,小结:就是启动epoll线程监听输入输出,处理输入并且根据同步或者异步方式进行不同的处理。那么还有问题,就是得到了远程服务的代理类之后,数据是怎么发出去的?

调用getRoleServantPrx()->reportRoleInfo(data);reportRoleInfo是远程服务的rpc接口名字。

这里看下一个最简单的例子:接口定义如下:

 interface GateServant
{
    int doRequest(string sRequest, out string sResponse);
};
 

可以看下文件自动生成的同步调用源码:

tars::Int32 doRequest(const std::string & sRequest,std::string &sResponse,const map<string, string> &context = TARS_CONTEXT(),map<string, string> * pResponseCo
ntext = NULL)
        {
            tars::TarsOutputStream<tars::BufferWriter> _os;
            _os.write(sRequest, 1);
            _os.write(sResponse, 2);
            tars::ResponsePacket rep;
            std::map<string, string> _mStatus;
            tars_invoke(tars::TARSNORMAL,"doRequest", _os.getByteBuffer(), context, _mStatus, rep);
            if(pResponseContext)
            {
                *pResponseContext = rep.context;
            }

            tars::TarsInputStream<tars::BufferReader> _is;
            _is.setBuffer(rep.sBuffer);
            tars::Int32 _ret;
            _is.read(_ret, 0, true);
            _is.read(sResponse, 2, true);
            return _ret;
        }  

如上可以看到这里调用了tars_invoke接口,回到框架源码,可以看到ServantProxy的成员函数taf_invoke实现如下:

先new一个消息类:ReqMessagePtr req = new ReqMessage(_comm);然后把rpc调用的参数等上下文参数进行填充。

然后调用ServantProxy::invoke(ReqMessagePtr& req)注意如果是同步调用,这里new了一个ReqMonitor,用于同步调用时的条件变量,阻塞wait指定时间,等待对端返回。接着调用_objectProxy->invoke(req);

//选择一个远程服务的Adapter来调用
    AdapterProxy * pAdapterProxy = NULL;
    bool bFirst = _pEndpointManger->selectAdapterProxy(msg,pAdapterProxy);

调用 pAdapterProxy->invoke(msg);这里判断发送队列如果为空则直接_pTrans->sendRequest发送数据,否则调用_pTimeoutQueue->push把发送数据接入发送队列,至此调用远程接口的数据则发送到远程服务了。根据文章前部分的描述,等待远程服务返回数据的时候,CommunicatorEpoll线程则会接收到epollin事件,然后启动接收数据处理函数。完成一个rpc调用的流程,当然这里面还包括了超时处理例如远程服务超载等细节,本文只理清核心流程,细节这里不做分析。另外CommunicatorEpoll类调用handleOutputImp的时候会调用

doRequest();函数把//取adapter里面积攒的数据
    _pAdapterProxy->doInvoke();如下图

void CommunicatorEpoll::handleOutputImp(Transceiver * pTransceiver)

{
    //检查连接是否有错误
    if(pTransceiver->isConnecting())
    {
        pTransceiver->setConnected();
    }
    pTransceiver->doRequest();
}

二、客户端异步调用远程服务部分:

void async_doRequest(GateServantPrxCallbackPtr callback,const std::string &sRequest,const map<string, string>& context = TARS_CONTEXT())
        {
            tars::TarsOutputStream<tars::BufferWriter> _os;
            _os.write(sRequest, 1);
            std::map<string, string> _mStatus;
            tars_invoke_async(tars::TARSNORMAL,"doRequest", _os.getByteBuffer(), context, _mStatus, callback);
        }

异步调用最明显的不同在于多了一个callback,ServantProxyCallback异步回调对象的基类,其他发送部分类似于同步调用的流程,通过servantprxoy的invoke再调用objectprxoy的invoke写入发送缓冲队列。同样等待CommunicatorEpoll线程处理EPOLLIN事件,AdapterProxy::finishInvoke 异步调用。

//如果是本线程的回调,直接本线程处理:ReqMessagePtr msgPtr = msg;msg->callback->onDispatch(msgPtr);

//异步回调,放入回调处理线程中
                _pObjectProxy->getCommunicatorEpoll()->pushAsyncThreadQueue(msg);

异步线程AsyncProcThread::run()处理异步请求回来的响应包:调用msg->callback->onDispatch(msgPtr);

jce自动生成的代码:调用了callback_doRequest如下图从而实现回包自动回调

virtual int onDispatch(tars::ReqMessagePtr msg)
        {   
            static ::std::string __GateServant_all[]=
            {   
                "doRequest",
                "test"
            };  
            pair<string*, string*> r = equal_range(__GateServant_all, __GateServant_all+2, string(msg->request.sFuncName));
            if(r.first == r.second) return tars::TARSSERVERNOFUNCERR;
            switch(r.first - __GateServant_all)
            {   
                case 0:
                {   
                    if (msg->response.iRet != tars::TARSSERVERSUCCESS)
                    {   
                        callback_doRequest_exception(msg->response.iRet);

                        return msg->response.iRet;
                    }   
                    tars::TarsInputStream<tars::BufferReader> _is;

                    _is.setBuffer(msg->response.sBuffer);
                    tars::Int32 _ret;
                    _is.read(_ret, 0, true);

                    std::string sResponse;
                    _is.read(sResponse, 2, true);
                    CallbackThreadData * pCbtd = CallbackThreadData::getData();
                    assert(pCbtd != NULL);

                    pCbtd->setResponseContext(msg->response.context);

                    callback_doRequest(_ret, sResponse);

                    pCbtd->delResponseContext();

                    return tars::TARSSERVERSUCCESS;

                }   

三、服务器启动监听部分:

      initializeServer //初始化Server部分 new出来TC_EpollServer 用于

*  注册协议解析器
 * 注册逻辑处理器
 * 注册管理端口处理器

并且设置TC_EpollServer 类的_netThreadNum网络线程个数:用于处理循环监听网路连接请求。如下:

_netThreads在初始化阶段是不启动的,只是先设置了参数,一般等到业务服务调用:waitForShutdown则启动网络线程,具体做了什么事情如下:

void TC_EpollServer::NetThread::run()

const epoll_event &ev = _epoller.get(i);

case ET_LISTEN://监听端口有请求

case ET_NET:
  //网络请求
  processNet(ev);       函数读取网络数据 这里根据ev.data.u32的获取连接:Connection *cPtr = getConnectionPtr(uid);

然后调用NetThread::recvBuffer将数据缓存到_recvbuffer然后调用NetThread::Connection::parseProtocol

再降数据压力入适配器的recv_queue      _rbuffer;接收队列(后面会用业务线程处理接收队列)

创建好TC_EpollServer 接下来

//绑定适配器对象和端口(支持多个)
 bindAdapter(adapters);该函数读取配置文件中配置的适配器创建TC_EpollServer::BindAdapterPtr,例如下图,配置了一个msdk适配器,endpoint字段配置了该适配器监听的IP和端口,protocol指定了是否使用taf协议,threads设置了处理收发包的网络线程数目

注意如果使用的taf协议则使用taf默认的协议处理函数,这里协议函数使用了loki库的封装机制,实现回调函数AppProtocol::parse的绑定,如果是非taf的协议,注意调用setProtocol设置自己的协议处理回调函数

if (bindAdapter->isTafProtocol())
            {
                bindAdapter->setProtocol(AppProtocol::parse);
            }

接下来:调用_epollServer->bind(bindAdapter);调用NetThread::bind根据tcp或者udp创建socket;

如果是tcp还会设置tcp参数:

s.setKeepAlive();  //心跳
s.setTcpNoDelay(); //禁用了Nagle算法
//不要设置close wait否则http服务回包主动关闭连接会有问题
s.setNoCloseWait();

//设置HandleGroup分组,启动线程

传入上图适配器配置文件设置的字段参数 threads代表线程个数
         * 初始化处理线程,线程将会启动
         */
        template<typename T> void setHandle()
        {
            _pEpollServer->setHandleGroup<T>(_handleGroupName, _iHandleNum, this);
        }

 //启动业务处理线程
        _epollServer->startHandle();上一步设置的hds[i]->start();

    _epollServer->createEpoll(); TC_Epoller是epoller操作类如果是TCP类,启动侦听端口:_epoller.add(it->first, H64(ET_LISTEN) | it->first, EPOLLIN);

    TC_EpollServer::Handle::run() 开始处理上面提到的netthread接收到的缓存队列

 调用handleImp函数,这里遍历adapters所有适配器,adapter->waitForRecvQueue等待接收队列,如果队列中数据包超时了则调用handleTimeout,否则调用handle(stRecvData);进行消息包处理。

调用 ServantHandle::handle(const TC_EpollServer::tagRecvData &stRecvData)根据是否taf协议分别调用:handleTafProtocol(current);和handleNoTafProtocol(current);

如果是taf协议则继续调用sit->second->dispatch

这里要结合下jce定义rpc接口文件的生成代码,例如:

编译后自动生成代码 rpc调用 addVipGrowth接口

           

         

猜你喜欢

转载自blog.csdn.net/cyblueboy83/article/details/82312733