【TARS】RegistryServer网络模块及业务处理模块分析

目录

 

0.复习

1.初始化

1.1 先看看Application中哪些代码与网络模块的初始化有关

1.2 初始化服务Application::initializeServer()

1.2.1 tars::TC_EpollServer::_epoller的初始化

1.2.2 tars::TC_EpollServer::NetThread::_epoller的初始化

1.2.3  对AdminAdapter进行处理

1.3 Application::bindAdapter初始化

1.3.1 建立bindAdapter和_epollServer的绑定关系

1.3.2 TC_EpollServer::BindAdapter::BindAdapter的具体实现

1.3.3  _epollServer->bind(bindAdapter);

1.3.4 TC_EpollServer::bind的具体实现

2.服务端工作

2.1 接受客户端连接

2.1.1 执行epoll_ctl(_iEpollfd,EPOLL_CTL_ADD,fd,&ev)

2.1.2 三个线程的epoll_wait()实现

2.1.3 accept实现【所有Adapter的监听都是在主线程完成】

2.2 接受RPC请求

2.2.1 接受入口

2.2.2 获取激活了的连接Connection并进行简单的检查工作

2.2.3 读事件入口

2.2.4 以recvTcp()来说明

2.3 处理RPC请求

2.3.1 获取请求数据构造请求上下文

2.3.2 处理请求

      2.3.2.1  入口

      2.3.2.2 根据协议来选择相应函数处理 

      2.3.2.3 介绍TARS协议的请求的相关处理-以TARS协议讲解

2.3.3 将响应数据包push到线程安全队列中并通知网络线程-以TARS协议讲解

2.3.4 发送RPC响应

3.服务端工作总结


0.复习

第一步.创建socket

socket(iDomain, iSocketType, 0);// 创建socket ,_notify.init(&_epoller)中

第二步.绑定套接字与IP,端口

int bind(int sockfd,  const struct sockaddr, socklen_t addrlen); // 绑定套接字与IP和端口 

_epollServer->bind(lsPtr)/_epollServer->bind(bindAdapter) ,

中途调用bind(ep, s, lsPtr->isManualListen()),

最终调用的是s.bind(ep.getHost(), ep.getPort())/ s.bind(ep.getHost().c_str()); TC_Socket::bind(const string &sServerAddr, int port)

第三步.listen ,监听

第四步 accept ,接收到链接之后会返回新的fd  看看一哥写的accept

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockft-创建socket返回的那个fd,参数 sockfd 是一个由 socket(2) 创建的套接口,通过 bind绑定到一个本地地址,并且在调用 listen(2)之后正处于监听之中

参数 addr 是指向一个 sockaddr结构的指针。这结构体被填充为一个端套接口,又被称为通信层。返回的地址结体 addr的额外的格式可以通过套接口地址族(参看 socket(2)和各自的协议手册页)来确定。

当 addr 是 NULL 时,没有内容被填充,此时 addrlen不被使用,同时也可以是 NULL。

参数 addrlen 是一个“值-返回”型参数,调用者必须把它初始化为 addr指向的结构的大小(字节数),返回时,它指出端地址的实际大小。

如果提供的缓冲区太小,返回的地址将被截断,此时,addrlen 将返回一个比传入更大的值。

 一个链接

epoll的高效封装

TC_EpollServer:: processPipe()的逻辑的链接

Epoll相关操作

关键的接口:

epoll_create

epoll_wait

epoll_ctl

ssize_t nready,efd,res;

struct epoll_event tep,ep[OPEN_MAX];

efd = epoll_create(10); // 创建epoll模型,efd指向红黑树根节点

nready = epoll_wait(efd,ep,OPEN_MAX,-1); /*epoll为阻塞监听事件,ep为struct epoll_event类型数组,OPEN_MAX为数组容量,-1表示永久阻塞*/

nready = epoll_wait(efd,ep,OPEN_MAX,1000);/*监听红黑树efd,将满足的时间的文件描述符添加到ep数组中,一秒没有事件满足,返回 0*/

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 

This  system  call  performs control operations on the epoll(7) instance referred to by the file descriptor epfd.  It requests that the operation op be performed

 for the target file descriptor, fd.

这个系统调用展示了由文件描述符epfd指向的epoll实例的操作.它要求对指向的文件描述符fd做op的操作.

op的取值范围:

(1) EPOLL_CTL_ADD - Register the target file descriptor fd on the epoll instance referred to by the file descriptor epfd and associate the event event with the internal  file  linked to fd.
(2) EPOLL_CTL_MOD - Change the event event associated with the target file descriptor fd.
(3) EPOLL_CTL_DEL-Remove (deregister) the target file descriptor fd from the epoll instance referred to by epfd.  The event is ignored and can be NULL (but see BUGS below).
EPOLLIN , EPOLLOUT , EPOLLPRI, EPOLLERR 和 EPOLLHUP事件

好文章链接

Tars服务网络模型总结

我的系列文章:

【TARS】初识TARS

【TARS】源码方式部署TARS

【TARS】Centos下用Docker部署TARS

【TARS】用TarsCpp-创建第一个服务

【TARS】TARS学习文章链接(感谢哈)及自己的链接

【TARS】基于TARS的调试

【TARS】TARS协议的编解码

【TARS】TARS架构支持的协议汇总

【TARS】TARS-CPP客户端学习一

【TARS】TARS-CPP客户端学习二

【TARS】我的测试环境的常用路径

【TARS】TARS-CPP服务器端数据结构总结

【TARS】扩容与负载均衡

【TARS】开发测试及运维记录

【TARS】网关TarsGateway

【TARS】压测工具TarsBenchmark

【TARS】分布式存储系统DCache

【TARS】远程调用方式

【TARS】TarsCpp-Http服务示例

【TARS】PUSH功能

【TARS】日志服务

【TARS】鉴权功能

【TARS】Mysql操作

【TARS】TARS中的nodejs

【TARS】深入理解RegistryServer

1.初始化

1.1 先看看Application中哪些代码与网络模块的初始化有关

void Application::main(int argc, char *argv[])
{
   ......
        initializeServer(); // 初始化服务
   ......
        vector<TC_EpollServer::BindAdapterPtr> adapters;
        //绑定对象和端口
        bindAdapter(adapters);
	......
        ;
}

void Application::waitForShutdown()
{
    ...
     _epollServer->waitForShutdown();
    ......
}

void TC_EpollServer::waitForShutdown()
{
   ...
   _epollServer->createEpoll();
   ...
}

1.2 初始化服务Application::initializeServer()

void Application::initializeServer()
{
...
    // 第2步 初始化TC_EpollServer,设置网络线程数(注意此时线程并没有启动)
    _epollServer = new TC_EpollServer(ServerConfig::NetThread);

   ...
    // 第14步 初始化管理对象
    if(!ServerConfig::Local.empty())
    {

      ....
      // AdminAdapter与Application::_epollServer之间建立关系
      TC_EpollServer::BindAdapterPtr lsPtr = new TC_EpollServer::BindAdapter(_epollServer.get());
      ...
      _epollServer->bind(lsPtr); // 创建套接字
      ...
}

1.2.1 tars::TC_EpollServer::_epoller的初始化

TC_EpollServer::TC_EpollServer(unsigned int iNetThreadNum)
: _netThreadNum(iNetThreadNum)
, _bTerminate(false)
, _handleStarted(false)
, _pLocalLogger(NULL)
, _acceptFunc(NULL)
{
    ...
    //创建epoll
    _epoller.create(10240);
    // 问题 : 为什么 tc_epoller.cpp 中的 TC_Epoller::NotifyInfo::init(TC_Epoller *ep)  用的是 UDP协议呢? _notify.createSocket(SOCK_DGRAM, AF_INET);
    _notify.init(&_epoller); // 这一步创建类socket,看下面的源码
    _notify.add(_notify.notifyFd());
   
    for (size_t i = 0; i < _netThreadNum; ++i)
    {
        TC_EpollServer::NetThread* netThreads = new TC_EpollServer::NetThread(this, i);// 实现在本文件的 line 1479 ,按这里写的如果 _netThreadNum 是2 的话,就会有两颗 epoll 树
        _netThreads.push_back(netThreads);
    }
}



void TC_Epoller::NotifyInfo::init(TC_Epoller *ep)
{
    _ep = ep;
	_notify.createSocket(SOCK_DGRAM, AF_INET);
}


void TC_Epoller::NotifyInfo::add(uint64_t data)
{
    _data = data;
    _ep->add(_notify.getfd(), data, EPOLLIN | EPOLLOUT);
}

1.2.2 tars::TC_EpollServer::NetThread::_epoller的初始化

TC_EpollServer::NetThread::NetThread(TC_EpollServer *epollServer, int threadIndex)
: _epollServer(epollServer)
, _threadIndex(threadIndex)
, _bTerminate(false)
, _list(this)
, _bEmptyConnAttackCheck(false)
, _iEmptyCheckTimeout(MIN_EMPTY_CONN_TIMEOUT)
, _nUdpRecvBufferSize(DEFAULT_RECV_BUFFERSIZE)
{
    _epoller.create(10240);// 相当于C语言中做了epoll_create(intsize),创建epoll句柄

    _notify.init(&_epoller); // 实际上这步做了创建socket的工作,核心步骤是 
    TC_Socket::createSocket中的socket(iDomain, iSocketType, 0);
    _notify.add(_notify.notifyFd());
}

1.2.3  对AdminAdapter进行处理

 在Application::initializeServer()步骤中,关注两步,

(1)建立AdminAdapter与Application::_epollServer之间的链接关系;

(2) Application::_epollServer创建了套接字.

代码阅读搜索:

 搜索 TC_EpollServer::BindAdapterPtr lsPtr = new TC_EpollServer::BindAdapter(_epollServer.get());

_epollServer->bind(lsPtr);【TC_EpollServer::bind】--在这一步完成了创建套接字的过程. 【TC_EpollServer::BindAdapterPtr lsPtr】

1.3 Application::bindAdapter初始化

1.3.1 建立bindAdapter和_epollServer的绑定关系

关键步骤 TC_EpollServer::BindAdapterPtr bindAdapter = new TC_EpollServer::BindAdapter(_epollServer.get());

void Application::bindAdapter(vector<TC_EpollServer::BindAdapterPtr>& adapters)
{
 ...
 vector<string> adapterName;
 ...

 if (_conf.getDomainVector("/tars/application/server", adapterName))
 {
   ...
   TC_EpollServer::BindAdapterPtr bindAdapter = new TC_EpollServer::BindAdapter(_epollServer.get());
   ...
  }
}

1.3.2 TC_EpollServer::BindAdapter::BindAdapter的具体实现

达到将bindAdapte对象的内部成员变量_pEpollServer指向tars::Application::_epollServer的目的.

搜索: TC_EpollServer::BindAdapterPtr bindAdapter = new TC_EpollServer::BindAdapter(_epollServer.get()) 快速找到源码.

TC_EpollServer::BindAdapter::BindAdapter(TC_EpollServer *pEpollServer)
    : _pReportQueue(NULL)
    , _pReportConRate(NULL)
    , _pReportTimeoutNum(NULL)
    , _pEpollServer(pEpollServer)
    ,  _pf(echo_protocol)
    , _hf(echo_header_filter)
    , _name("")
    , _iMaxConns(DEFAULT_MAX_CONN)
    , _iCurConns(0)
    , _iHandleNum(0)
    , _eOrder(ALLOW_DENY)
    , _iQueueCapacity(DEFAULT_QUEUE_CAP)
    , _iQueueTimeout(DEFAULT_QUEUE_TIMEOUT)
    , _iHeaderLen(0)
    , _iHeartBeatTime(0)
    , _protocolName("tars")
{
}

1.3.3  _epollServer->bind(bindAdapter);

【TC_EpollServer::BindAdapterPtr bindAdapter】

此处的调用与1.3中的调用都是调用的TC_EpollServer::bind

每一个Adapter都根据自己的Endpoint去进行监听.

比如说我们RegistryServer中有三个Adapter,那么就会有在此监听三个端口,分别是

(1)AdminAdapter根据对应IP:PORT等信息进行监听;

(2)tars.tarsregistry.QueryObjAdapter根据对应IP:PORT等信息进行监听;

(3)tars.tarsregistry.RegistryObjAdapter根据对应IP:PORT等信息进行监听.

如图所示,有三个端口开始监听.

图中问题:我的虚拟机的IP是192.168.118.138,同一个程序监听了192.168.118.138:17890和127.0.0.1:17890,这两个不会端口冲突吗?

答案:不会冲突,看链接给出的解释:链接

1.3.4 TC_EpollServer::bind的具体实现

int TC_EpollServer::bind(BindAdapterPtr &lsPtr)
{
    auto it = _listeners.begin(); // unordered_map<int, BindAdapterPtr>    _listeners;

    while (it != _listeners.end())
    {
        if (it->second->getName() == lsPtr->getName())
        {
            throw TC_Exception("bind name '" + lsPtr->getName() + "' conflicts.");
        }
        ++it;
    }

    const TC_Endpoint &ep = lsPtr->getEndpoint();

    TC_Socket &s = lsPtr->getSocket();

    bind(ep, s, lsPtr->isManualListen());

   /*
    SOCKET_TYPE getfd() const { return _sock; } 这个 _sock 哪里来的? 
    在initializeServer()中的第二步骤初始化TC_EpollServer中的_notify.init(&_epoll)中 _notify.createSocket 实现

   */
    _listeners[s.getfd()] = lsPtr; // 实现_listeners:unordered_map<int, BindAdapterPtr>的键值对的插入,键--fd,值--对应的BindAdapter

    _bindAdapters.push_back(lsPtr);

    return s.getfd();
}

2.服务端工作

2.1 接受客户端连接

TARS基金会的相关介绍链接-老版本

2.1.1 执行epoll_ctl(_iEpollfd,EPOLL_CTL_ADD,fd,&ev)

讨论服务器接受请求,经分析,accept的工作全部在从主线程中完成.

主线程中的执行的是_epoller.add(it->first, it->first, EPOLLIN);【TC_Epoller::add(SOCKET_TYPE fd, uint64_t data, int32_t event);tars::TC_EpollServer::_epoller】

网络线程中执行的是 _epoller.add(cPtr->getfd(), cPtr->getId(), EPOLLIN | EPOLLOUT);【int TC_Epoller::add(SOCKET_TYPE fd, uint64_t data, int32_t event);tars::TC_EpollServer::NetThread::_epoller】

网络线程中_epoller.add的第三个参数EPOLLIN | EPOLLOUT与主线程中的EPOLLIN其实在这里是没有区别的,都会被装化成原生的EPOLL_CTL_ADD.

那么从epoll_wait()返回的时候,epoll_event中的联合体epoll_data将会是 cPtr->getId()-是创建socket的时候的返回的fd.

2.1.2 三个线程的epoll_wait()实现

【6.3.3】两个网络线程:int iEvNum = _epoller.wait(1000);

【6.3.4】主线程:           int iEvNum = _epoller.wait(300);

// 步骤【6.3.3】网络线程
void TC_EpollServer::NetThread::run()
{
    _threadId = std::this_thread::get_id();
    ...    
    while(!_bTerminate)//循环监听网路连接请求
    {
        ...  
        // tars::TC_EpollServer::NetThread::_epoller
        int iEvNum = _epoller.wait(1000);   // 执行 epoll_wait
        ...
        for (int i = 0; i < iEvNum; ++i){
            try{
                const epoll_event &ev = _epoller.get(i);
                uint32_t fd = TC_Epoller::getU32(ev, false); //获取事件的fd, 用低32位存储要监听的fd,高32位存储监听类型     
                // (uint32_t)_notify.notifyFd()的值是创建socket时候返回的fd
                if (fd == (uint32_t)_notify.notifyFd()){
                    processPipe();//处理管道消息
                }
                else{ // 管道信息和网络请求有什么区别?
                    processNet(ev);
                }
            }
            catch (exception &ex){
                error("run exception:" + string(ex.what()));
            }
        }
    }
}
//【6.3.4 】 主线程的处理逻辑循环 
    while (!_bTerminate)
    {
        // 4.1 等待事件的到来,此处调用了epoll_wait,epoll_wait(_iEpollfd, _pevs, _max_connections, millsecond)
        // tars::TC_EpollServer::_epoller
        int iEvNum = _epoller.wait(300);  // 主线程进行_epoller.wait,把等待得到的事件分发给NetThread线程去处理
        ....
        for (int i = 0; i < iEvNum; ++i)// 4.3 进入循环
        {
            try{
                const epoll_event &ev = _epoller.get(i);
                uint32_t fd = TC_Epoller::getU32(ev, false); // 注意!!!获取低位的数据
                auto it = _listeners.find(fd);
                if (it != _listeners.end()){                 
                    if (TC_Epoller::readEvent(ev)){//监听端口有请求,TC_Epoller::readEvent点进去看看
                        bool ret;
                        do{                    
                            // 4.4 当监听端口有请求的时候的accept逻辑,linux下的重要逻辑
                            ret = accept(fd, it->second->_ep.isIPv6() ? AF_INET6 : AF_INET); 
                        } while (ret);
                    }
                }
            }
        }
    }

EINTR错误的链接

while后面直接加分号和不加分号的区别回顾

2.1.3 accept实现【所有Adapter的监听都是在主线程完成】

根据上面的分析,我们整个程序当中只有主线会进行监听,然后将监听到的链接创建出来,然后按照哈希的方式放入到网络线程中.

具体步骤的代码实现搜索源码【6.3.4.3.2.2.1】或直接看本文的2.1.2节中第二段代码中的逻辑,本节节选是accept的关键逻辑.

auto it = _listeners.find(fd);
if (it != _listeners.end())
{ 
   if (TC_Epoller::readEvent(ev))
   {
      bool ret;
      do{                                         
         ret = accept(fd, it->second->_ep.isIPv6() ? AF_INET6 : AF_INET); 
       } while (ret);
    } 
}

当监听端口有读事件时,启动tars::TC_EpollServer::accept(int fd, int domain = 2)函数,此处主线程根据6.3.4.3.2的_listeners开始监听所有adapters上的所有客户端的连接.

通过if (it != _listeners.end()){...}这个循环体实现.

 

2.2 接受RPC请求

2.2.1 接受入口

TARS基金会对于老版本(1.*版本)的论述:

讨论服务器接收RPC请求,同样从网络线程的NetThread::run()开始分析,上面是进入switch中的case 
ET_LISTEN分支来接受客户端的连接,那么现在就是进入case ET_NET分支了,为什么是case ET_NET分支呢?
因为上面提到,将客户端socket的fd加入TC_Epoller来监听其读写,采用的是_epoller.add(cPtr->getfd(), 
cPtr->getId(), EPOLLIN | EPOLLOUT),传递给函数的第二个参数是32位的整形cPtr->getId(),而函数的
第二个参数要求必须是64位的整型,因此,这个参数将会是高32位是0,低32位是cPtr->getId()的64位整型。
而第二个参数的作用是当该注册的事件引起epoll_wait()退出的时候,会作为激活事件epoll_event 结构体中
的64位联合体epoll_data_t data返回给用户. 看下面NetThread::run()代码:

try
{
    const epoll_event &ev = _epoller.get(i);
    uint32_t h = ev.data.u64 >> 32;
 
    switch(h)
    {
    case ET_LISTEN:
        ……
        break;
    case ET_CLOSE:
        //关闭请求
        break;
    case ET_NOTIFY:
        //发送通知
        ......
        break;
     case ET_NET:
        //网络请求
        processNet(ev);
        break;
      default:
         assert(true);
      }
}

代码中的h是64位联合体epoll_data_t data的高32位,经过上面分析,客户端socket若因为接收到数据而
引起epoll_wait()退出的话,epoll_data_t data的高32位是0,低32位是cPtr->getId(),因此h将会是
0。而ET_NET就是0,因此客户端socket有数据来到的话,会执行case ET_NET分支。下面看看执行case 
ET_NET分支的函数流程图。

较新版本中(2.4.14),判断分支的方法已经发生了变化,新版本直接根据fd的值进行判断是否进入processNet(ev);进行逻辑处理:

// 步骤【6.3.3】网络线程
void TC_EpollServer::NetThread::run()
{
    _threadId = std::this_thread::get_id();
    ...    
    while(!_bTerminate)//循环监听网路连接请求
    {
        ...  
        // tars::TC_EpollServer::NetThread::_epoller
        int iEvNum = _epoller.wait(1000);   // 执行 epoll_wait
        ...
        for (int i = 0; i < iEvNum; ++i){
            try{
                const epoll_event &ev = _epoller.get(i);
                uint32_t fd = TC_Epoller::getU32(ev, false); //获取事件的fd, 用低32位存储要监听的fd,高32位存储监听类型     
                // (uint32_t)_notify.notifyFd()的值是创建socket时候返回的fd
                if (fd == (uint32_t)_notify.notifyFd()){
                    processPipe();//处理管道消息
                }
                else{ // 管道信息和网络请求有什么区别?
                    processNet(ev);
                }
            }
            catch (exception &ex){
                error("run exception:" + string(ex.what()));
            }
        }
    }
}

2.2.2 获取激活了的连接Connection并进行简单的检查工作

void TC_EpollServer::NetThread::processNet(const epoll_event &ev)
{
    uint32_t uid = TC_Epoller::getU32(ev, false);
    Connection *cPtr = getConnectionPtr(uid);
    // 如果链接是空的,那么打印日志并返回
    if(!cPtr)
    {
        debug("TC_EpollServer::NetThread::processNet connection[" + TC_Common::tostr(uid) + "] not exists.");
        return;
    }
    // 如果事件是错误事件,ev.events为EPOLLERR||EPOLLHUP
    if (TC_Epoller::errorEvent(ev))
    {
        delConnection(cPtr, true, EM_SERVER_CLOSE);
        return;
    }
    ....
}

2.2.3 读事件入口

void TC_EpollServer::NetThread::processNet(const epoll_event &ev)
{ 
...
// 如果事件是读事件,ev.events为EPOLLIN
    if (TC_Epoller::readEvent(ev)) 
    {
        int ret = cPtr->recv();// 重点关注 TC_EpollServer::Connection::recv()
        if (ret < 0)
        {
            delConnection(cPtr, true, EM_CLIENT_CLOSE);
            return;
        }
    }
...
}

int TC_EpollServer::Connection::recv()
{
   // TC_EpollServer::Connection::recvTcp() ,TC_EpollServer::Connection::recvUdp()    
   return isTcp() ? recvTcp() : recvUdp();
}

2.2.4 以recvTcp()来说明

recvTcp()中调用了_sock.recv((void *)buffer, BUFFER_SIZE)【这里调用了标准库中的recv函数】

rbuf->addBuffer(buffer, iBytesReceived)

parseProtocol(*rbuf);

recvTcp()中还调用了parseProtocol

parseProtocol中调用了insertRecvQueue

int TC_EpollServer::Connection::parseProtocol(TC_NetWorkBuffer &rbuf)中调用了TC_EpollServer::Connection::insertRecvQueue(const shared_ptr<TC_EpollServer::RecvContext> &recv),

TC_EpollServer::Connection::insertRecvQueue(const shared_ptr<TC_EpollServer::RecvContext> &recv)中又调用了TC_EpollServer::BindAdapter::insertRecvQueue(const shared_ptr<RecvContext> &recv)

至此_threadDataQueue[idx]->_rbuffer.push_back(recv);实现了向接受队列中插入数据.

 TC_ThreadLock::Lock lock(_threadDataQueue[idx]->_monitor);

 _threadDataQueue[idx]->_monitor.notify();

并且开始通知对应的线程队列醒过来.到此,业务模块终于要开始登场,处理RPC请求了.

下面是TC_EpollServer::BindAdapter::insertRecvQueue的源码:

void TC_EpollServer::BindAdapter::insertRecvQueue(const shared_ptr<RecvContext> &recv)
{
    _iRecvBufferSize++;

    size_t idx = 0;

    if(isQueueMode()) {
        //相同连接过来的进入同一个buffer, 被Handle的同一个线程处理
        idx = recv->fd() % _iHandleNum + 1;
    }
    _threadDataQueue[idx]->_rbuffer.push_back(recv);

    //通知对应的线程队列醒过来
    TC_ThreadLock::Lock lock(_threadDataQueue[idx]->_monitor);
    _threadDataQueue[idx]->_monitor.notify();

}

2.3 处理RPC请求

处理线程开始启动去处理RPC请求,调用的是ServantHandle::run(),首先来看一下ServantHandle这个类中 提供了哪些方法.链接,搜索【ServantHandle】.

重点分为三步:

(1)构造请求上下文;【6.3.1.2.1.3.5.6 ServantHandle::handle  createCurrent(data)】

(2)调用用户实现的方法处理请求;【6.3.1.2.1.3.5.6.3.1.7】

(3)将响应数据包push到线程安全队列中并通知网络线程【6.3.1.2.1.3.5.6.3.1.7.4】.

第二步中,对于用户实现的处理请求数据的方法,非tars协议需要重写Servant类中的虚函数doRequest,对于tars协议需要重写onDispatch方法.

2.3.1 获取请求数据构造请求上下文

当业务线程从条件变量上被唤醒之后,从其负责的BindAdapter中获取请求数据:

     popRecvQueue(data)【6.3.1.2.1.3.5】-->>>_bindAdapter->waitForRecvQueue(_handleIndex, recv),

在 TC_EpollServer::BindAdapter::waitForRecvQueue(uint32_t handleIndex, shared_ptr<RecvContext> &data)中,

将从线程安全队列 tars::TC_EpollServer::BindAdapter::DataQueue::_rbuffer中获取数据:

bool TC_EpollServer::BindAdapter::waitForRecvQueue(uint32_t handleIndex, shared_ptr<RecvContext> &data)
{
    bool bRet = getRecvQueue(handleIndex).pop_front(data);

    if (!bRet)
    {
        return bRet;
    }

    --_iRecvBufferSize;

    return bRet;
}

.... 

CurrentPtr ServantHandle::createCurrent(const shared_ptr<TC_EpollServer::RecvContext> &data)
{
    CurrentPtr current = new Current(this);
    try
    {
        current->initialize(data);
    }
    ...
    return current;
}

 

在ServantHandle::createCurrent()中,先new出Current实例,然后调用其initialize()方法,在Current::initialize中将RPC请求包的内容放进请求上下文CurrentPtr current中,后续只需关注这个请求上下文即可.

【另外可以稍微关注一下,若采用TARS协议会使用TarsCurrent::initialize(const string &sRecvBuffer)将请求包的内容放进请求上下文中,否则直接采用memcpy()系统调用来拷贝内容】--这句话引自【TARS基金会】

中对较早版本的论述,对现在版本好像并不适用.
 

2.3.2 处理请求

      2.3.2.1  入口

我的本地代码中的相关注释索引:
前置入口:handle(data);
步骤:6.3.1.2.1.3.5.6
入口:handleTarsProtocol(current)
步骤:6.3.1.2.1.3.5.6.3.1

      2.3.2.2 根据协议来选择相应函数处理 

// 6.3.1.2.1.3.5.6
void ServantHandle::handle(const shared_ptr<TC_EpollServer::RecvContext> &data)
{
    // 6.3.1.2.1.3.5.6.1 构造请求上下文current
    CurrentPtr current = createCurrent(data);

    // 6.3.1.2.1.3.5.6.2  判断current是否为NULL,如果为NULL,则返回
	if (!current) return;
    
    // 6.3.1.2.1.3.5.6.3 获取current的协议是否为TASR协议,如果是TARS协议
    if (current->getBindAdapter()->isTarsProtocol())
    {
        // 6.3.1.2.1.3.5.6.3.1 当current的协议为TASR协议时,调用handleTarsProtocol(current)
        handleTarsProtocol(current);
    }
    else
    {
        // 6.3.1.2.1.3.5.6.3.2 当current的协议不为TASR协议时,调用handleNoTarsProtocol(current)
        handleNoTarsProtocol(current);
    }
}

      2.3.2.3 介绍TARS协议的请求的相关处理-以TARS协议讲解

进入函数中,会先对请求上下文进行预处理,例如set调用合法性检查,染色处理等。随后,就依据上下文中的服务名来获取服务对象:

map<string, ServantPtr>::iterator sit = _servants.find(current->getServantName()),_servants在【业务模块的初始化 6.3.1.1.3】,

其key是服务ID(或者叫服务名),value是用户实现的服务XXXServantImp实例指针。

// 6.3.1.2.1.3.5.6.3.1 当current的协议为TASR协议时,调用handleTarsProtocol(current)
void ServantHandle::handleTarsProtocol(const CurrentPtr &current)
{

    // 6.3.1.2.1.3.5.6.3.1.1 打印tars日志
    TLOGTARS("[ServantHandle::handleTarsProtocol current:"
                << current->getIp() << "|"
                << current->getPort() << "|"
                << current->getMessageType() << "|"
                << current->getServantName() << "|"
                << current->getFuncName() << "|"
                << current->getRequestId() << "|"
                << TC_Common::tostr(current->getRequestStatus()) << "]"<<endl);


     // 6.3.1.2.1.3.5.6.3.1.2 检查set调用合法性
    //检查set调用合法性
    if(!checkValidSetInvoke(current))
    {
        return;
    }
    
    // 6.3.1.2.1.3.5.6.3.1.3 处理染色消息
    //处理染色消息
    string dyeingKey = "";
    TarsDyeingSwitch dyeSwitch;
    if (processDye(current, dyeingKey))
    {
        dyeSwitch.enableDyeing(dyeingKey);
    }


    // 6.3.1.2.1.3.5.6.3.1.4 处理cookie
    //处理cookie
    map<string, string> cookie;
    CookieOp cookieOp;
    if (processCookie(current, cookie))
    {
        cookieOp.setCookie(cookie);
        current->setCookie(cookie);
    }


    // 6.3.1.2.1.3.5.6.3.1.5 调用链打开的情况下,处理tracking信息
#ifdef TARS_OPENTRACKING
    //处理tracking信息
    processTracking(current);
#endif
    auto sit = _servants.find(current->getServantName());

    if (sit == _servants.end())
    {
        current->sendResponse(TARSSERVERNOSERVANTERR); // 没有找到相关服务
#ifdef TARS_OPENTRACKING
        finishTracking(TARSSERVERNOSERVANTERR, current);
#endif
        return;
    }


    // 6.3.1.2.1.3.5.6.3.1.6 定义返回码ret,异常信息接收变量sResultDesc,响应包变量ResponsePacket
    int ret = TARSSERVERUNKNOWNERR;

    string sResultDesc = "";

	ResponsePacket response;
//    vector<char> buffer;
    try
    {
        //业务逻辑处理
        // VIP , 当current->getFuncName() != "tars_ping"的时候,在tars协议中调用了onDispatch,在非tars协议中调用了doRequest函数
        // 6.3.1.2.1.3.5.6.3.1.7 业务处理
        ret = sit->second->dispatch(current, response.sBuffer);
    }
    // 6.3.1.2.1.3.5.6.3.1.8 解码异常的捕获
    catch(TarsDecodeException &ex)
    {
        TLOGERROR("[ServantHandle::handleTarsProtocol " << ex.what() << "]" << endl);

        ret = TARSSERVERDECODEERR;

        sResultDesc = ex.what();
    }
    // 6.3.1.2.1.3.5.6.3.1.9 编码异常的捕获
    catch(TarsEncodeException &ex)
    {
        TLOGERROR("[ServantHandle::handleTarsProtocol " << ex.what() << "]" << endl);

        ret = TARSSERVERENCODEERR;

        sResultDesc = ex.what();
    }
    // 6.3.1.2.1.3.5.6.3.1.10 异常捕获 
    catch(exception &ex)
    {
        TLOGERROR("[ServantHandle::handleTarsProtocol " << ex.what() << "]" << endl);

        ret = TARSSERVERUNKNOWNERR;

        sResultDesc = ex.what();
    }
    // 6.3.1.2.1.3.5.6.3.1.11 未知异常捕获 
    catch(...)
    {
        TLOGERROR("[ServantHandle::handleTarsProtocol unknown error]" << endl);

        ret = TARSSERVERUNKNOWNERR;

        sResultDesc = "handleTarsProtocol unknown exception error";
    }

    //单向调用或者业务不需要同步返回
    // 6.3.1.2.1.3.5.6.3.1.12 判断是否函数返回时发送响应包给客户端
    if (current->isResponse())
    {
        // 6.3.1.2.1.3.5.6.3.1.12.1 VIP 函数返回时需要发送响应包给客户端的情况下,调用sendResponse
        current->sendResponse(ret, response, Current::TARS_STATUS(), sResultDesc);
    }
// 6.3.1.2.1.3.5.6.3.1.13 调用链打开的情况下,本函数结束调用链调用
#ifdef TARS_OPENTRACKING
    finishTracking(ret, current);
#endif
}

随后就可以利用XXXServantImp实例指针来执行RPC请求了:ret = sit->second->dispatch(current, buffer),在Servant:: dispatch()

(如图所示因为XXXServantImp是继承自XXXServant,而XXXServant继承自Servant,所以实际是执行Servant的方法)中,使用

不同的协议会有不同的处理方式,这里只介绍TARS协议的,调用了XXXServant::onDispatch(tars::TarsCurrentPtr _current, vector

&_sResponseBuffer)方法,即上面贴的代码中的步骤【6.3.1.2.1.3.5.6.3.1.7】.

// 6.3.1.2.1.3.5.6.3.1.7
// 6.3.1.2.1.3.5.6.3.2.6
int Servant::dispatch(CurrentPtr current, vector<char> &buffer)
{
    // 6.3.1.2.1.3.5.6.3.1.7.1 定义并初始化函数返回值ret
    int ret = TARSSERVERUNKNOWNERR;

    // 6.3.1.2.1.3.5.6.3.1.7.2  如果上下文调用的是函数是tars_ping,则返回成功TARSSERVERSUCCESS(0)
    if (current->getFuncName() == "tars_ping")
    {
        TLOGTARS("[Servant::dispatch] tars_ping ok from [" << current->getIp() << ":" << current->getPort() << "]" << endl);

        ret = TARSSERVERSUCCESS;
    }
    // 6.3.1.2.1.3.5.6.3.1.7.3  如果上下文调用的不是tars_ping且不是tars协议,返回调用doRequest【普通协议的请求】的返回值
    else if (!current->getBindAdapter()->isTarsProtocol())
    {
        TC_LockT<TC_ThreadRecMutex> lock(*this);

        ret = doRequest(current, buffer);
    }
    // 6.3.1.2.1.3.5.6.3.1.7.4  如果上下文调用的不是tars_ping且是tars协议,返回调用onDispatch【分发并处理请求】的返回值
    else
    {
        TC_LockT<TC_ThreadRecMutex> lock(*this);

        ret = onDispatch(current, buffer);
    }
    return ret;
}

XXXServant类就是执行Tars2Cpp的时候生成的,会依据用户定义的tars文件来生成相应的纯虚函数,以及onDispatch()方法,该方法的动作有:

1.找出在本服务类中与请求数据相对应的函数;
2.解码请求数据中的函数参数;
3.执行XXXServantImp类中用户定义的相应RPC方法;
4.编码函数执行后的结果;
5.return tars::TARSSERVERSUCCESS。
上述步骤是按照默认的服务端自动回复的思路去阐述,在实际中,用户可以关闭自动回复功能(如:current->setResponse(false)),并自行发送回复(如:servant->async_response_XXXAsync(current, ret, rStr)).

到此,服务端已经执行了RPC方法.

补充,Servant,XXXServant及XXXServantImp之间的关系如下图:


2.3.3 将响应数据包push到线程安全队列中并通知网络线程-以TARS协议讲解

处理完RPC请求,执行完RPC方法之后,需要将结果(下面代码中的buffer)回送给客户端:

6.3.1.2.1.3.5.6.3.1.12.1【TARS协议】
6.3.1.2.1.3.5.6.3.2.8.1 【非TARS协议】


Handle业务线程的启动”中被赋予内容,其key是服务ID(或者叫服务名),value是用户实现的服务
XXXServantImp实例指针.

随后就可以利用XXXServantImp实例指针来执行RPC请求了:ret = sit->second->dispatch(current, 
buffer),在Servant:: dispatch()中,
因为XXXServantImp是继承自XXXServant,而XXXServant继承自Servant,所以实际是执行Servant的方法)
中,使用不同的协议会
有不同的处理方式,本节只介绍TARS协议的,调用了XXXServant::onDispatch(tars::TarsCurrentPtr 
_current, vector &_sResponseBuffer)方法:
void ServantHandle::handleTarsProtocol(const TarsCurrentPtr &current)
{
    // 1-对请求上下文current进行预处理
    // 2-寻找合适的服务servant
    // 3-业务逻辑处理
    // 回送响应,本节分析
    if (current->isResponse()){
        current->sendResponse(ret, buffer, TarsCurrent::TARS_STATUS(), sResultDesc);
    }
}
由于业务与网络是独立开来的,网络线程收到请求包之后利用条件变量来通知业务线程,而业务线程才有什么方
式来通知网络线程呢?由前面可知,网络线程是阻塞在epoll中的,因此需要利用epoll来通知网络线程.
在ServantHandle::handleTarsProtocol()中,最后的一步就是回送响应包.数据包的回送经历的步骤是:编码
响应信息——找出与接收请求信息的网络线程,因为我们需要通知他来干活——将响应包放进该网络线程的发送队列
——利用epoll的特性唤醒网络线程,我们重点看看NetThread::send():
void TC_EpollServer::NetThread::send(const shared_ptr<SendContext> &data)
{
    if(_threadId == std::this_thread::get_id()) {
        //发送包线程和网络线程是同一个线程,直接发送即可
        Connection *cPtr = getConnectionPtr(data->uid());
        if(cPtr)
        {
            cPtr->send(data);
        }
    }
    else {
        //发送包线程和网络线程不是同一个线程, 需要先放队列, 再唤醒网络线程去发送
        _sbuffer.push_back(data);
        //通知epoll响应, 有数据要发送
        if (!_notifySignal) {
            _notifySignal = true;
            _notify.notify();
        }
    }
}


// 不同线程的时候用这个方法来通知网络线程
void TC_Epoller::NotifyInfo::notify()
{
    // 通知epoll响应, 有数据要发送
    _ep->mod(_notify.getfd(), _data, EPOLLIN | EPOLLOUT);
}

到此,服务器中的业务模块已经完成他的使命,后续将响应数据发给客户端是网络模块的工作了.

2.3.4 发送RPC响应

再回看【2.2.1】节中的代码再结合【2.3.3】中的TC_Epoller::NotifyInfo::notify,在TC_Epoller::NotifyInfo::notify中通知另外线程的时候,用的_notify.getfd(),

似乎就知道了为什么在【2.2.1】节中的代码中fd == (uint32_t)_notify.notifyFd()就可以判断这时候要处理的是管道信息,其实要发送网络线程的发送队列中的数据.

// 步骤【6.3.3】网络线程
void TC_EpollServer::NetThread::run()
{
    _threadId = std::this_thread::get_id();
    ...    
    while(!_bTerminate)//循环监听网路连接请求
    {
        ...  
        // tars::TC_EpollServer::NetThread::_epoller
        int iEvNum = _epoller.wait(1000);   // 执行 epoll_wait
        ...
        for (int i = 0; i < iEvNum; ++i){
            try{
                const epoll_event &ev = _epoller.get(i);
                uint32_t fd = TC_Epoller::getU32(ev, false); //获取事件的fd, 用低32位存储要监听的fd,高32位存储监听类型     
                // (uint32_t)_notify.notifyFd()的值是创建socket时候返回的fd
                if (fd == (uint32_t)_notify.notifyFd()){
                    processPipe();//处理管道消息
                }
                else{ // 管道信息和网络请求有什么区别?
                    processNet(ev);
                }
            }
            catch (exception &ex){
                error("run exception:" + string(ex.what()));
            }
        }
    }
}

在NetThread::processPipe()中,先从线程安全队列中取响应信息包:_sBufQueue.dequeue(sendp, false),这里与“2.3.3处理RPC请求”的第3小点“将响应数据包push到线程安全队列中并通知网络线程”遥相呼应.

然后从响应信息中取得与请求信息相对应的那个Connection的uid,利用uid获取Connection:Connection *cPtr = getConnectionPtr(sendp->uid)。由于Connection是聚合了TC_Socket的,后续通过Connection

将响应数据回送给客户端,最终是调用【6.3.3.3.5.3.3】TC_EpollServer::Connection::sendBuffer中的_sock.send来实现将响应数据发送给客户端的,具体流程如下图所示:

3.服务端工作总结

旧版本中的总结(取自TARS基金会的服务端介绍的文章):

图(2-30)服务端工作图

基于新版本2.4.14,这里我也学习TARS基金会那里写的用图解总结一下RegistryServer服务端的工作过程:

4.GDB调试观察

对于AdminAdapter

pvector g_app._epollServer->_bindAdapters

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值