概述
长连接是指在客户端和服务端建立tcp连接,进行一次数据请求和响应之后,不立即关闭连接,而是维持这个连接,后续请求和响应继续通过该连接进行处理。
基于TCP的keepalive机制实现
- 基于TCP的keepalive机制,由具体的TCP协议栈来实现长连接的维持。如在netty中可以在创建channel的时候,指定SO_KEEPALIVE参数来实现:
- 存在的问题:Netty只能控制SO_KEEPALIVE这个参数,其他参数,则需要从系统的sysctl中读取,其中比较关键的是tcp_keepalive_time,发送心跳包检测的时间间隔,默认为7200s,即空闲后,每2小时检测一次。如果客户端在这2小时内断开了,那么服务端也要维护这个连接2小时,浪费服务端资源;另外就是对于需要实时传输数据的场景,客户端断开了,服务端也要2小时后才能发现。服务端发送心跳检测,具体可能出现的情况如下:
(1)连接正常:客户端仍然存在,网络连接状况良好。此时客户端会返回一个 ACK 。 服务端接收到ACK后重置计时器,在2小时后再发送探测。如果2小时内连接上有数据传输,那么在该时间基础上向后推延2个小时;
(2)连接断开:客户端异常关闭,或是网络断开。在这两种情况下,客户端都不会响应。服务器没有收到对其发出探测的响应,并且在一定时间(系统默认为 1000 ms )后重复发送 keep-alive packet ,并且重复发送一定次数。
(3)客户端曾经崩溃,但已经重启:这种情况下,服务器将会收到对其存活探测的响应,但该响应是一个复位,从而引起服务器对连接的终止。
基于Netty的IdleStateHandler实现
通过会在应用层来实现长连接,Netty是基于IdleStateHandler实现的,IdleStateHandler是ChannelDuplexHandler的一个实现类,可以对读写IO进行空闲检测。
Netty超时检测的设计和底层实现
- 空闲检测时间
IdleStateHandler支持对读,写,读写三个维度的检测,通过构造函数来设值,如下:
可以分别控制读,写,读写超时的时间,单位为秒,如果是0表示不检测,所以如果全是0,则相当于没添加这个IdleStateHandler,连接是个普通的短连接。
- 定时检测
IdleStateHandler是在创建IdleStateHandler实例并添加到ChannelPipeline时添加定时任务来进行定时检测的,具体在initialize(ctx)方法实现;同时在从ChannelPipeline移除或Channel关闭时,移除这个定时检测,具体在destroy()实现,如图:
- 定时任务执行线程:调用ctx.executor方法获取一个执行定时任务的线程
如下为AbstractChannelHandlerContext的executor获取:如果存在executor则使用,否则使用channel所绑定的eventLoop线程来执行,而executor可以通过在pipeline的addLast方法指定一个额外的线程池group,这样executor就是从这个额外的线程池group中获取的一个线程:
在DefaultChannelPipeline的addLast方法:是否指定group
executor的获取:
- 空闲检测任务,以ReaderIdleTimeoutTask为例
(1)任务基类
(2)ReaderIdleTimeoutTask实现run方法:
- nextDelay的初始化值为超时秒数readerIdleTimeNanos,如果检测的时候没有正在读,且计算多久没读了:nextDelay -= 当前时间 - 上次读取时间,如果小于0,说明左边的readerIdleTimeNanos小于空闲时间(当前时间 - 上次读取时间)了,则超时了:
- 创建IdleStateEvent事件,IdleState枚举值为READER_IDLE,然后调用channelIdle方法分发给下一个ChannelInboundHandler,通常由用户自定义一个ChannelInboundHandler来捕获并处理:
(3)reading和lastReadTime的维护:在channelRead中,每次Channel有数据读入时,设值为true,表示正在读;channelReadComplement读完时,记录最近读取时间为当前时间,并重置reading为false。
如果是写的检测,由于写是异步的,则是使用listerner的方式:
案例:web服务保持与客户端(内部其他提供数据的服务)的长连接
- 将timeout包的IdleStateHandler放到pipeline中,用于检测channel的空闲超时事件,即可以指定channel多久没有read或者write操作,则触发读写超时IO事件,以下只对read空闲超时进行检测:
- 在pipeline中,在IdleStateHandler后面需要添加一个ChannelInboundHandler处理器来捕获IdleStateEvent事件:自定义ChannelInboundHandler的实现PingReqHandler,在userEventTriggered方法中ping对方来保持长连接: