Netty框架服务端感知客户端状态——IdleStateHandler

前段时间用Netty搭了个Mqtt broker,初步实现了端到端的通信,Mqtt协议是基于发布/订阅(publish/subscribe)这种模型的,客户端之间是不知道彼此的存在,是解耦的,但在有些业务场景中,我们想要两个端知道彼此的存在以及状态,这就需要broker去维护两端的关系。

  • 使用TCP协议层的Keeplive机制,但是该机制默认的心跳时间是2小时,依赖操作系统实现不够灵活;

  • 应用层实现自定义心跳机制,比如Netty实现心跳机制;

Netty中自己有一套单独的心跳机制,靠IdleStateHandler这个类来监测,下面从我的理解出发介绍一下这个类以及心跳机制检测的实现过程。

一、IdleStateHandler的由来

首先来看下IdleStateHandler继承的父类以及实现的接口:

继承关系

可以看到IdleStateHandler最终实现了ChannelHandler这个接口,这个接口中只定义如下三个方法:


ChannelHandler
  • handlerAdded(ChannelHandlerContext ctx):当一个handler加入的时候调用;
  • handlerRemoved(ChannelHandlerContext ctx):当一个handler销毁的时候调用;
  • exceptionCaught(ChannelHandlerContext ctx, Throwable cause):当一个handler发生异常的时候调用。

handler可以理解为channel的执行者,发生在channel里的各种事务都由handler去执行,一个客户端连接到broker就会产生一个channel,可以看到三个方法每个都携带了一个参数ChannelHandlerContext,这个就是理解为发生这些事务的客户端。
ChannelHandler接口只是定义了客户端最基本的三个操作,连接、断开和异常,更丰富的功能由其子类以及实现类提供。

ChannelHandler子类

ChannelHandler接口有两个子类ChannelInboundHandler、ChannelOutboundHandler,他们分别负责处理入站I/O事件以及出站I/O操作,对应的有三个实现类:

  • ChannelInboundHandlerAdapter:实现具体的入站I/O事件逻辑;
  • ChannelOutboundHandlerAdapter:实现具体的出战I/O操作逻辑;
  • ChannelDuplexHandler:实现具体的入站以及出站I/O事件,算是上面两个的综合体。

从上面的继承关系图可以看到,本文将要着重介绍IdleStateHandler就是继承自ChannelDuplexHandler这个类,说明IdleStateHandler也是具备处理入站以及出站I/O事件的能力。

二、IdleStateHandler的创建及使用

IdleStateHandler有三个构造方法,构造方法里只是做了一些全局变量的赋值。

IdleStateHandler构造方法

其余两个构造方法最终都是调用的这个方法,直接看这个,其中有5个参数:

  • observeOutput:从字面意思看这个Boolean类型的参数决定是否考虑写空闲状态时的字节消耗,默认是false,具体作用还没用到过,这里先不过多考虑,就按默认值来;
  • readerIdleTime:这个值决定,多长时间没有read操作发生,将触发IdleState.READER_IDLE状态,当这个参数值为0时,将一直不会触发;
  • writerIdleTime:这个值决定,多长时间没有write操作发生,将触发IdleState.WRITER_IDLE状态,当这个参数值为0时,将一直不触发;
  • allIdleTime:这个值决定,多长时间没有read和write操作发生,将触发IdleState.ALL_IDLE状态,当这个参数值为0时,将一直不触发;
  • unit:时间单位

关于空闲状态,在IdleState枚举类中定义了三种,分别是READER_IDLE、WRITER_IDLE、ALL_IDLE,分别对应读空闲、写空闲、读写都空闲
具体使用方法,在这个类的顶部有个例子:

IdleStateHandler使用示例

照上图那样,创建IdleStateHandler对象时,传入相关的参数,然后将IdleStateHandler对象放到通道管道中,这样这个管道就实现了心跳机制监测。

三、IdleStateHandler实现的原理

在IdleStateHandler类中有三个和handler连接时有关(入站)的回调,分别是channelActive(ChannelHandlerContext ctx)、channelRegistered(ChannelHandlerContext ctx)、handlerAdded(ChannelHandlerContext ctx),其中handlerAdded是ChannelHandler接口中定义的方法,另外两个则是ChannelInboundHandler接口中定义的方法,这三个回调中都调用了initialize方法:

initialize方法

在这个方法中会过滤掉几个值小于或等于0的情况,所以在构造方法中会有当设置为0一直不会触发的介绍,就是在这里过滤掉了,当三个时间值都大于0的时候,调用了schedule(ChannelHandlerContext ctx, Runnable task, long delay, TimeUnit unit)方法,一路追踪下去,会发现这个方法的最终实现是将传进来的task加到了一个任务队列中,队列这种数据结构,先进先出的特性保证了任务的顺序执行。

task加入到任务队列中

任务加入到了任务队列中,剩下的就是队列开始轮询,拿到任务后根据其delay时间调用其run()方法开始执行任务,下面我们看看这个task里面实现了什么东西:

ReaderIdleTimeoutTask

这个任务里面创建了一个IdleState.READER_IDLE状态的Event对象,调用channelIdle方法将这个Event对象传了进去,下面看看channelIdle方法的具体实现:

channelIdle

这里调用了ChannelHandlerContext的fireUserEventTriggered(final Object event)方法。

调用链

最终可以看到,这个ReaderIdleTimeoutTask任务触发最终调用了ChannelInboundHandler接口中的userEventTriggered(ChannelHandlerContext ctx, Object evt)方法,所以我们的childHandler只要实现了ChannelInboundHandler这个接口,就能拿到事件回调,就像IdleStateHandler类中顶部示例中那样:

image.png

到这里我们就理清楚了IdleStateHandler的整个脉络:

1、初始化的时候传入三个时间参数,分别表示读操作超时时间、写操作超时时间、读写操作超时时间;
2、handler连接的时候调用initialize方法,创建对应的计时任务,将任务放进任务队列中;
3、各自的计时任务中创建对应的状态事件,最后调用userEventTriggered方法,将event和ctx传递出去。

这样基于Netty框架搭建的mqtt broker拿到客户端的事件后,就能在某个客户端发生意外断连后,将这一情况告知需要响应的其他客户端。

你可能感兴趣的:(Netty框架服务端感知客户端状态——IdleStateHandler)