说完Tars-C++ 揭秘篇:TC_Buffer的妙用后,我们继续将关注点放在与收发相关的链接管理上。
当::accept返回值不小于0时,这个返回值便是链接的操作符,当我们想设计一个高效、可靠的网络框架时,在链接方面至少需要考虑下面这些问题:
- 空间上怎样管理链接数量
- 时间上怎样检查链接是否超时,怎样定时刷新过期时间,何时断开连接。
12.1 空间管理
- 每一个服务端口是一个BindAdapter,如图中2000端口和2001端口。Tars允许监听多个服务端口。每个BindAdapter都定义了链接到本端口的最大链接数_iMaxConns和当前链接数 _iCurConns
- 当一个客户端成功链接到一个BindAdapter端口时,会生成一个Connection类,Connection的当前数量就由_iCurConns表示,Connection的总数限制由_iMaxConns表示
- 看下如何利用上面两个变量控制Connection的数量。第一种情形:客户端发起链接,服务端accept成功后,会先检查当前链接数是否大于总链接数,即_iCurConns + 1 > _iMaxConns,如果大于就close掉本端口,小于才继续创建Connection;第二种情形:当成功创建Connection后,会将_iCurConns加一(为了提高效率,这里的_iCurConns是一个原子变量);第三种情形:当客户端主动断开链接或者服务端发现链接超时,会删除掉这个Connection,同时将_iCurConns数目减一
12.2 时间管理
- Tars设计了两个比较巧妙的数据结构对链接的超时时间进行存储。一个是蓝色框中multimap类型的_tl,一个是粉红框中pair类型的数组_vConn
- 当一个新的Connection成功建立时候,我们会给这个Connection分配一个uid编号,然后给这个uid设定一个过期时间,组成一个key value(key为过期时间,value为uid)insert到_tl中。同时将insert返回的迭代器与Connection组成一个pair放到以uid为下标的_vConn数组中。
- 简单看下这样设计的好处。(1)当我们想查找过期的Connection时,只需要按照顺序遍历_tl(multmap的key是有序的),就能得到过期时间对应的所有uid,通过uid可以轻松定位到_vConnuid中的Connection;(2)当我们想更新一个Connection的超时时间时,通过_vConnuid中指向_tl的迭代器,可以很轻松的更新_tl中这个uid的超时时间。这两种情形其实就是下面要说的怎样判断链接超时和怎样刷新超时时间。
12.2.1 如何判断Connection超时
当一个Connection成功建立后,如果一定时间内都没有发送和接收数据,这个Connection就可以被认定为超时链接,可以从_vConn何_tl中剔除,以节省资源。
- Tars中对超时链接判断放在了TC_EpollServer::NetThread::run中的while循环里。
- 如上图所示,当前时间为11:20:03,则_tl里11:20:00和11:20:02对应的uid都过期了。
- 按顺序先取出11:20:00对应的uid,根据uid获取_vConnuid
- 因为该链接已经失效,所以我们需要把链接操作符从epoll中删掉,并关闭这个链接操作符。这些信息都可以从_vConnuid.first(即Connection类)中得到
- 进而再删除tl中11:20:00对应的uid,这时可以看到_vConnuid.second的便利性了。直接进行这样的操作即可:_tl.erase(_vConnuid.second)。
- 最后将_vConnuid中的数据清理即可。
12.2.2 刷新超时时间
如果一个Connection是活跃的,我们应该按照一定规则去刷新它的超时时间,如果超时时间更新不及时就会被12.2.1中的超时判断所剔除。
- Tars中对Connection超时时间的刷新设置在了TC_EpollServer::NetThread::processNet中,这很容易理解,有数据到来或者有数据发送都表明这个端口是活跃的,需要继续延长它的超时时间。
- 上图的目的是将一个uid的过期时间从11:20:00更新为11:20:02
- 首先根据_vConnuid.second确认这个uid在_tl中的位置,然后进行下面refresh操作。
- 执行_tl.erase(_vConnuid.second)删除11:20:00中的uid,即虚线框中的uid
- 执行_vConnuid.second = _tl.insert(make_pair(“11:20:02”, uid))将uid的过期时间更新为11:20:02
12.3 简单聊一下魔数和muid
先举例子解释下魔数:
_iConnectionMagic = ((((uint32_t)_lastTimeoutTime) << 26) & (0xFFFFFFFF << 26)) + ((iIndex << 22) & (0xFFFFFFFF << 22));
- _lastTimeoutTime是当前时间戳,赋值1547262120
- iIndex是第几个NetThread,代码里说最多只有15个NetThread,是因为这里只留了4个bit来存储NetThread序号(后边可以看到), iIndex赋值为1
- 上面两者相加如下图
- 注意由于iIndex左移了22位,_lastTimeoutTime左移了26位,所以iIndex只能放在多出的4个bit中,即图中的红色框,也就导致了iIndex的取值受到15的限制
- 由于_iConnectionMagic的类型为uint32_t,所以实际还要再次截取一下
总结下:魔数在二进制表现上就是:时间戳的后6位 + iIndex的4位 + 22位0
再来说muid,muid出现在TC_EpollServer::NetThread::ConnectionList::add中,跟踪下代码发现来源于TC_EpollServer::NetThread::ConnectionList::getUniqId(),关键代码是:
_iConnectionMagic | uid
- uid的大小是收到限制的:只能22位bit,代码见:
void TC_EpollServer::NetThread::createEpoll(uint32_t iIndex)
{
…………
if(maxAllConn >= (1 << 22))
{
error("createEpoll connection num: " + TC_Common::tostr(maxAllConn) + " >= " + TC_Common::tostr(1 << 22));
maxAllConn = (1 << 22) - 1;
}
…………
}
- 所以muid的格式就变为了时间戳的后6位 + iIndex的4位 + 22位uid