在互联网领域,客户端和服务端之间通常需要建立和保持TCP长连接 。所谓长连接,就是通信双方在建立TCP连接后进行数据通信,一次或若干次通信交互完成之后,不主动断开连接,而是保持TCP连接不释放,在随时需要通信的时候,不再需要重新建立连接 。长连接可以提高通信速度、确保实时性、避免短时间内重复连接所造成的网络资源浪费,例如:即时通信,物联网等应用场景 。对于服务器来说,接入和保持海量的客户端长连接,需要付出大量的服务器资源(网络、内存、CPU、文件句柄等) 。由于很多客观原因(例如网络环境、客户端本身出现故障等),双方会建立一些无效的连接,既没有有效的数据通信,又不会主动关闭,称之为“死”连接 。为了提高服务器资源的利用率,需要将“死”连接主动关闭释放资源,这就是心跳保活机制 。所谓心跳保活,就是在通信过程中,通信双方定期给对方发送心跳包(一种特殊的数据报文),表示发送方还存活着 。服务器收到客户端定期发送的心跳包之后,就认为客户端还活着,反之,如果超过规定时间内没有收到心跳包,则认为客户端已“死”,需要将TCP连接关闭 。因此,服务端需要管理所有客户端连接会话,记录所有会话的超时时间,定期把超时的会话连接进行清除 。
如何定期把超时的会话清除,如何将有数据通信的活跃连接的会话进行保持?
一种简单和常用的实现方法是在每次客户端连接建立的时候,就设置一个定时器和该TCP连接关联,该定时器在指定超时时间到达之后会关闭该关联的TCP连接 。如果在该定时器超时时间到达之前,关联的TCP连接链路有数据通信,则重置定时器的超时时间 。这种方法可以非常精确设置每一个TCP连接保活的超时时间 。但是当客户端接入数量达到海量的时候,该方法会产生大量的定时器任务,对于每一次客户端连接,都要产生一个对应的定时任务,定时任务的数量等于客户端连接数,定时任务的维护将耗费比较多的计算资源 。
那么,在海量的场景,我们真的需要非常精确的超时保活吗?当然实际场景对于超时,我们不需要那么精确 。因此,有一种更加高效的处理会话保活的算法,叫做时间分桶算法,这种算法可以极大减少了定时任务的数量,只使用一个定时线程就可以处理,极大降低海量连接情况下的计算资源的占用 。
时间分桶算法,采用批量处理和近似超时的思想,提升对于客户端超时判断的执行效率 。服务器有两类线程,第一类线程是IO处理线程,处理客户端连接后的IO事件,例如连接建立、报文读取、连接关闭等事件;第二类线程是会话清理线程,定期清除超时时间桶中的批量超时会话 。具体两种线程的执行过程如下:
第一类IO线程的处理逻辑:
1. 首先,将连续的时间按照固定间隔DT切割成片段,每一个片段就是一个时间桶 。建立从任意时刻t到时间桶的映射关系B(t),如图1所示,B(t1)->B0, B(t2)->B1 。
文章插图
图1
2. 连接初始化:当客户端C新建连接的时候,计算出超时的时刻t’=t+DT(假设连接超时时间为DT),计算出B(t’)=B1,因此将连接C1的会话句柄保存在编号为B1的时间桶中,如下图所示:
文章插图
图2
【服务器海量TCP连接如何高效保活?】3. 连接保活:当收到客户端C的数据报文的时候,刷新客户端新的超时时刻t’<-now+DT,如果得到的时间桶比C原来所在的时间桶更新,即:B(t’)>B(t),则将会话C转移到B(t’)的时间桶中去 。如下图所示:
文章插图
图3
4. 连接删除:服务端探测到客户端C的连接发生关闭事件之后,直接从C所在时间桶B(t)中删除该会话 。
第二类清理线程的处理逻辑:
1. 初始化:t为当前系统毫秒时间,to为离当前时间最近一个时间桶的超时时刻(时间桶的上界即为时间桶的整体超时时刻)to=UP(B(t)) 。
2. 更新系统当前时刻t<-now() 。
3. 如果t<to(t所在的时间桶还未到超时时刻),则线程睡眠(to-t)毫秒,并且跳转到步骤2;
4. 否则(t所在时间桶已经到超时时刻),将B(t)中的所有会话批量删除和关闭连接 。超时的时间桶中没有被转移走的会话全是超时的 。
5. 更新离当前时间最近一个时间桶的的超时时刻to<-to+DT,并跳转到步骤2 。
推荐阅读
- tcp,icmp,http 基于wireshark报文分析快速过滤报文时延
- Kali工具大阅兵之weevely,看黑客如何通过webshell控制服务器
- 服务器BIOS和BMC等知识详解
- TCP 半连接队列和全连接队列满了,怎么破?
- 跟踪服务器功耗的工具和技巧
- 服务器概念、组成、分类和架构之争
- Centos7 新服务器基础环境安装
- 一行代码引来的安全漏洞,就让我们丢失了整个服务器的控制权
- 说起来 TCP 的连接与释放真是个浪漫的故事呢!
- 利用CentOS7云主机搭建NPS内网穿透代理服务器