TimeoutHandler
在开发TCP服务时,一个常见的需求便是使用心跳保活客户端。而Netty自带的三个超时处理器IdleStateHandler,ReadTimeoutHandler和WriteTimeoutHandler可完美满足此需求。其中IdleStateHandler可处理读超时(客户端长时间没有发送数据给服务端)、写超时(服务端长时间没有发送数据到客户端)和读写超时(客户端与服务端长时间无数据交互)三种情况。这三种情况的枚举为:
1 | public enum IdleState { |
以IdleStateHandler的读超时事件为例进行分析,首先看类签名:
1 | public class IdleStateHandler extends ChannelDuplexHandler |
注意到此Handler没有Sharable注解,这是因为每个连接的超时时间是特有的即每个连接有独立的状态,所以不能标注Sharable注解。继承自ChannelDuplexHandler是因为既要处理读超时又要处理写超时。
该类的一个典型构造方法如下:
1 | public IdleStateHandler(int readerIdleTimeSeconds, int writerIdleTimeSeconds, |
分别设定各个超时事件的时间阈值。以读超时事件为例,有以下相关的字段:
1 | // 用户配置的读超时时间 |
首先看初始化方法initialize():
1 | private void initialize(ChannelHandlerContext ctx) { |
初始化的工作较为简单,设定最近一次读取时间lastReadTime为当前系统时间,然后在用户设置的读超时时间readerIdleTimeNanos截止时,执行一个ReaderIdleTimeoutTask进行检测。其中使用的方法很简洁,如下:
1 | long ticksInNanos() { |
然后,分析销毁方法destroy():
1 | private void destroy() { |
可知销毁的处理也很简单,分析完初始化和销毁,再看这两个方法被调用的地方,initialize()在三个方法中被调用:
1 | public void handlerAdded(ChannelHandlerContext ctx) throws Exception { |
当客户端与服务端成功建立连接后,Channel被激活,此时channelActive的初始化被调用;如果Channel被激活后,动态添加此Handler,则handlerAdded的初始化被调用;如果Channel被激活,用户主动切换Channel的执行线程Executor,则channelRegistered的初始化被调用。这一部分较难理解,请仔细体会。destroy()则有两处调用:
1 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { |
即该Handler被动态删除时,handlerRemoved的销毁被执行;Channel失效时,channelInactive的销毁被执行。
分析完这些,在分析核心的调度任务ReaderIdleTimeoutTask:
1 | private final class ReaderIdleTimeoutTask implements Runnable { |
这个读超时检测任务执行的过程中又递归调用了它本身进行下一次调度,请仔细品味该种使用方法。再列出channelIdle()的代码:
1 | protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) |
本例中,该方法将写超时事件作为用户事件传播到下一个Handler,用户需要在某个Handler中拦截该事件进行处理。该方法标记为protect说明子类通常可覆盖,ReadTimeoutHandler子类即定义了自己的处理:
1 | @Override |
可知在ReadTimeoutHandler中,如果发生读超时事件,将会关闭该Channel。当进行心跳处理时,使用IdleStateHandler较为麻烦,一个简便的方法是:直接继承ReadTimeoutHandler然后覆盖readTimedOut()进行用户所需的超时处理。