tars framework 源码解读(四) servant部分章节。服务端部分1。服务端各种关键类简洁
Servant类。
翻译成中文就是 仆人的意思。顾名思义,是真正干活的类
在我们实际上写tars代码时候,一般先定义好一个tars文件。比如tars源码的cpp/examples/CoroutineDemo/BServer.这个工程来说.有个BServant.tars文件。内容如下
module Test
{
interface BServant
{
int test();
int testCoroSerial(string sIn, out string sOut);
int testCoroParallel(string sIn, out string sOut);
};
};
对应有个BServantImp类。这个类继承于class BServantImp : public Test::BServant.
而BServant就是上面.tars文件中声明的类。如果撸过tars或者taf代码的话,那么应该都知道无论是make release或者是啥工具平台处理这个.tars文件,肯定会对应这个BServer.tars(或者jce)文件生成BServant.h和BServant.hpp文件.在这2个自动生成的文件中,会有对class BServant上面标注的3个函数的同步异步协程函数声明和基础实现。而BServant是继承于tars::Servant类的.
对于一次tars请求,服务端响应实现流程的概述
对应于 客户端流程部分 的《完整的tars调用流程详解》。服务端,是怎么实现tars请求过来后的响应的呢?
根据上面内容,可以知道我们写业务代码实现的Imp类最终是继承于Servant类的。
比如 管理控制接口类 class AdminRegistryImp: public AdminReg -> 而找.tars文件转成的.h文件中 class AdminReg : public tars::Servant。
而网络收发,那肯定是通过Epoll实现的,消息是怎么从epoll中收到包之后,调到我们的Imp类中对应的函数来的呢?另外,tars服务端总的又有多少个线程呢?
首先说说实现服务端的线程,tars有两类线程:
一种是网络线程:
通过读Application::initializeServer发现,线程数配置在/tars/application/server<netthread>中,默认是1个,代码保护是1->15个之间。我看了下tars默认给的模板conf文件,此值配成2.在创建
TC_EpollServer类时候将线程数传入,其内部会 new TC_EpollServer::NetThread(netthread)个真正处理网络请求的网络线程。
一种是业务线程,基类是 ServantHandle 。 class ServantHandle : public TC_EpollServer::Handle。
有个很重要的类,没法绕开。那就是BindAdapter。在每个.config.conf文件的<server>下面,必然有一个或者多个Adapter.比如tars.tarsnode.config.conf文件中:
<NodeAdapter>
endpoint=tcp -h localip.tars.com -p 19385 -t 60000
allow
maxconns=1024
threads=5
queuecap=10000
queuetimeout=4000
servant=tars.tarsnode.NodeObj
</NodeAdapter>
<ServerAdapter>
endpoint=tcp -h localip.tars.com -p 19386 -t 60000
allow
maxconns=1024
threads=5
queuecap=10000
queuetimeout=4000
servant=tars.tarsnode.ServerObj
</ServerAdapter>
对应的NodeServer.cpp文件的NodeServer::initialize()函数中,有如下语句:
string sNodeObj = ServerConfig::Application + “.” + ServerConfig::ServerName + “.NodeObj”;
string sServerObj = ServerConfig::Application + “.” + ServerConfig::ServerName + “.ServerObj”;
addServant<NodeImp>(sNodeObj);
addServant<ServerImp>(sServerObj);
这是啥玩意儿?Adapter这个单词,字面翻译是适配器。这个适配器是怎么干活的呢?
首先在 Application::bindAdapter()中,会轮询出配置中配的所有Adapter,设置好各种参数,有个重点,是与TC_EpollServer对象bind上(完全看不懂系列,此bind在当网络线程数大于1的时候,貌似只绑定第一个网络线程,其它网络线程都只是设置一个参数?啥意思?),这个bind最终会调用TC_EpollServer::NetThread这些网络收发线程,创建一个socket,兵根据Adapter中配置的<endpoint>调用tcp/ip的::bind()把指定的端口与ip 与此socket bind上,并且把Adapter的指针与此socket 生成k->v 缓存在线程的一个map中。那么很明显,只要此socket上来了消息,都可以找到此Adapter了。
上面部分做完 epoll与Adapter之间就搞上关系了。
另一个很重要的部分就是。在Application::main()中,会将这些Adapter遍历一遍,调用setHandle().这个函数后面流程简单来说,先会找下 Adapter对象对应的HandleGroupName的HandleGroup,如果没找到,则new一个HandleGroup,最终找到或者生成好HandleGroup后,将此adapter对象与此HandleGroup互相关联起来(HandleGroupName默认与AdapterName一样)。 重要的来了,在new HandleGroup的时候,会生成指定threads的HandleT。。这个东东在代码中是模板,实际实现的HandleT是ServantHandle。ServantHandle:public TC_EpollServer::Handle.这个东东,就是业务处理线程。。
所以对应的线程数有多少个,就能够清楚的知道了。
暂且不论收到网络包后消息是具体是怎么跑的。还有个重要的问题,就是一开头讲的Imp类,或者说Servant类,是怎么与上面啰嗦了一坨的各种类搭上关系的呢?怎么从ServerHandle线程调用到Servant类中来呢?
这个重点还是在两个步骤:
步骤1 在 addServant()执行时候..比如 addServant<NodeImp>(sNodeObj)..[很明显,NodeImp就是我们实现功能的Servant类,而sNodeObj是与配置中对应的ServantName] .这一句实际上会new一个ServantCreation<NodeImp>对象,并与 sNodeObj 建立 k->v,存在ServantHelperManager的map _servant_creator中.
步骤2 在每个 ServantHandle::initialize()时,会遍历它的HandleGroup上所有绑定的Adapter,并且调用ServantHelperManager::create(AdapterName)。这个函数会根据AdapterName查到对应的ServantName,有了ServantName,就可以从 步骤1 中的_servant_creator中查到对应的ServantCreation<NodeImp> 并执行->create(ServantName).这个函数才会创建真正的NodeImp这个Servant类指针,并与ServantName做k->v.保存在ServantHandle的_servants这个map中.并且在每个Servant类中也绑定上ServantHandle自己的this指针.另外执行此Imp->initialize(),做些初始化事宜。
所以,总的有多少个Servant类对象呢?可以说一个ServantHandle就有一组其绑定的Adapter所配的Servant对象。
当有消息过来时候,如果是tars协议消息,根据TarsCurrentPtr->getServantName(),找到servantName对应的Servant类,调用其dispatch(TarsCurrentPtr)。走到这个Servant类的ServantImp的业务逻辑处理函数中。这样整个消息环节就串起来了。
这样 Servant类就与整个消息流程搭上了..
ServantHelperManager类
通过ServantHelperManager->setAdapterServant(servantName, adapterName) 让servant名与adapter名做个key:value互映射.
在所有服务的进程,Application启动时候.会做两个与servant相关的操作:
1、在Application::initializeServer函数中,建立一个名字叫AdminObj的servant(类实现是AdminServant).
这个servant中实现了AdminF.tars的方法,实现了shutdown()和notify()。通过这个servant,就可以实现通知消息的处理(实际处理是走到NotifyObserver相关流程)。
并且ServantHelperManager->setAdapterServant(“AdminAdapter”, “AdminObj”).这两个字符串做好互映射。
另外会给此adapter设置一个叫“AdminAdapter”的handleGroup,并且设置此adapter的handleNum为1.
这两个值作用在下面部分说明.
2、在bindAdapter 绑定server配置的Adapter和对象的时候,遍历出“/tars/application/server”配置中全部的Adapter.并将下级配置中“/tars/application/server/” + adapterName + “<servant>”配置的servantName与adapterName做映射.
ServantHelperManager::getInstance()->setAdapterServant(adapterName, servantName)
另外会给每个adapter设置一个配置在“/tars/application/server/” + adapterName + “<handlegroup>”名字(如果未配此名字,默认用adapterName做handlegroup名)的handleGroup,并且设置此adapter的handleNum为“/tars/application/server/” + adapterName + “<threads>”(没配置则默认为0个).