Netty对读写空闲检测的支持

Posted 点滴积累相伴成长

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Netty对读写空闲检测的支持相关的知识,希望对你有一定的参考价值。

说起读写空闲检测机制,如果在之前的工作中没有接触过相关开发,可能会觉得这是一处难点。即便系统中涉及到了相关的机制,但大多数情况下这个功能点也会被封装成底层组件,不通过分析源码,不容易清晰理解。


根据我的了解,读写检测机制一般会应用在即时通讯领域和集群部署的环境当中。结合我的工作经验来讲,我之前参与的一个广电项目中,分布式缓存系统之间要进行数据同步,系统要间进行长链接通讯,涉及到心跳检测机制,比如消息发起方会每隔3秒钟向消息接收方发送一个数据(或心跳)包,如果指定时间内对方没有数据包返回,则再次发送,数次之后仍然没有数据包返回,那么认为接收方服务宕机或异常,此后同步不成功的数据写入到备份表,待链接重新建立或手动触发来进行再次同步。


机制大体清楚之后,下面我们写一个简单的例子来了解一下Netty对读写空闲检测支持,这个例子还是基于上一个聊天程序来进行扩展。


首先,定义ConnectionHandler类用于处理读写空闲事件,此类继承ChannelInboundHandlerAdapter(从命名可以看出此类是一个适配器类),需要重写它的userEventTriggered方法,代码如下:


public class ConnectionHandler extends ChannelInboundHandlerAdapter {

   //当读写空闲发生时,IdleStateHandler处理器被调用,会触发空闲状态事件,evt代表该空闲状态事件;

   @Override

   public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {

       if (evt instanceof IdleStateEvent) {

           IdleStateEvent event = (IdleStateEvent) evt;

           String eventType = null;

           switch (event.state()) {

               case READER_IDLE:

                   eventType = "读空闲";

                   break;

               case WRITER_IDLE:

                   eventType = "写空闲";

                   break;

               case ALL_IDLE:

                   eventType = "读写空闲";

                   break;

           }

           System.out.println(ctx.channel().remoteAddress() + "超时事件: " + eventType);

           ctx.channel().close();

       }

   }

}


此方法的第二个参数evt表示被触发的空闲事件,需要通过instanceof关键字来判断这个事件的类型是否是我们所关心的空闲状态事件——IdleStateEvent。

如果是的话,调用这个对象的state方法来分析被触发的空闲状态事件的类型,state方法的返回类型为枚举类型IdelState,它分为三种状态READER_IDLE(读空闲),WRITER_IDLE(写空闲)和ALL_IDLE(读写空闲)。通过switch语句可以对不同的空闲状态进行条件判断,进而执行相应的处理逻辑,接下来我们通过上下文对象ctx获取channel链接信息,调用close方法将链接关闭。


然后,按照套路将我们编写的ConnectionHandler类添加到ChannelInitializer中,此处应用到了我们上一个聊天程序服务端的Initializer类,我们需要对之前编写的ChatServerInitializer类进行简单改造,具体的改造代码如下:


public class ChatServerInitializer extends ChannelInitializer<SocketChannel> {

   @Override

   protected void initChannel(SocketChannel ch) throws Exception {

       ChannelPipeline pipeline = ch.pipeline();

       pipeline.addLast(new IdleStateHandler(1, 1, 1, TimeUnit.MINUTES));//空闲状态检测的一个处理器;

       pipeline.addLast(new ConnectionHandler());//空闲状态事件发生时,执行userEventTriggered回调方法;

       pipeline.addLast(new DelimiterBasedFrameDecoder(4096, Delimiters.lineDelimiter()));

       pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));

       pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));

       pipeline.addLast(new ChatServerHandler());

   }

}


从代码中我们看到,我们新增了一个IdleStateHandler类,此类由Netty所提供,它是本文的重点,它表示当channel链接不执行读操作,写操作或读写操作时会触发空闲状态事件。结合我们上一个聊天程序的例子来讲,这里所说的读操作,实际上是指客户端没有向服务端发送数据,因此服务端无法从Socket中读取数据;写操作则与之相反,服务端不向Socket中写入数据,则会触发写空闲事件,因此客户端也就接收不到服务端发来的任何数据了;读写空闲就更好理解了,读或写空闲状态只要满足其一,空闲状态事件就会被触发。(这里IdleStateHandler类的构造方法有四个参数,前三个参数分别代表了读空闲,写空闲,读写空闲状态事件触发所需要等待的空闲时间,第四个参数为时间单位,我们这里指定的是分钟,此IdleStateHandler对象表示服务端如果在一分钟内没有从某一链接读取或没有向某一链接写入数据,就会触发空闲状态事件。


最后,当空闲状态事件被触发时,我们编写的ConnectionHandler类的userEventTriggered回调方法将会被调用,根据不同的空闲状态来执行特定的处理逻辑。聊天程序服务端的启动类不需要修改,至此我们的改造基本完成。如果一个客户端在一分钟内没有向服务端发送消息,服务端控制台会打印如下信息,并关闭与此客户端的链接:


/127.0.0.1:51985超时事件: 写空闲

/127.0.0.1:51985 下线


(客户端程序在接下来的文章中介绍。)


需要额外说一些的是,我们在实现ChannelInitializer类的initChannel方法时,往往需要在pipeline中加入多个Handler处理器,这些处理器有我们自己编写的,也有Netty内置的,细心观察会发现,Netty程序的编写还是有迹可循的。另外,我们之前曾讲过pipeline对象实际上是一个包含头尾节点的双向链表,任务从pipeline的第一个handler执行到最后一个,再从最后一个handler返回到第一个。这跟我们Servlet编程中的过滤器或拦截器的机制类似,本质上都是对责任链设计模式的应用。


感谢阅读,下一篇文章再见。

以上是关于Netty对读写空闲检测的支持的主要内容,如果未能解决你的问题,请参考以下文章

netty系列之:netty对marshalling的支持

对netty5支持HTTP协议的代码简析

netty学习总结

Netty—— AIO示例代码

Netty—— AIO示例代码

网络I/o编程模型17 netty框架实现心跳机制