Netty自带的心跳机制——IdleStateHandler

一、前言

        Netty提供了对心跳机制的天然支持,心跳可以检测远程端是否存活,或者活跃。

今天我们就一起初识一下Netty4的心跳机制。Netty4.0提供了一个类,名为IdleStateHandler,这个类可以对三种类型的心跳检测。

二、项目中的应用

Netty自带的心跳机制——IdleStateHandler

 上述是项目中应用到的心跳机制关键代码,主要步骤:

  1. 在pipeline中添加IdleStateHandler;
  2. 在IdleStateHandler后面再添加IdleHandler,这个handler是我们自定义的,后面会再分析它

  2.1 自定义的IdleHandler代码

@ChannelHandler.Sharable
public class IdleHandler extends ChannelInboundHandlerAdapter {

    private static final Logger LOG = LoggerFactory.getLogger(IdleHandler.class);

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object paramObject) throws Exception {
        LOG.info("userEventTriggered");
        if (paramObject instanceof IdleStateEvent) {
            IdleState state = ((IdleStateEvent) paramObject).state();
            if (state == IdleState.ALL_IDLE) {
                //关闭连接
                ctx.channel().close();
            }
        } else {
            super.userEventTriggered(ctx, paramObject);
        }
    }
}

         从上面代码可以看出,IdleHandler继承了ChannelInboundHandlerAdapter,仅重载了userEventTriggered方法。在该方法中,会执行关闭连接的逻辑。

那么问题来了,为什么要加自定义的IdleHandler代码,并重载userEventTriggered方法?
——这个问题答案下面会分析到。

三、IdleStateHandler源码解析

3.1 IdleStateHandler总览

public IdleStateHandler(
            long readerIdleTime, long writerIdleTime, long allIdleTime,
            TimeUnit unit) {
        this(false, readerIdleTime, writerIdleTime, allIdleTime, unit);
    }

前三个的参数解释如下:

  1. readerIdleTime:为读超时时间(即测试端一定时间内未接受到被测试端消息
  2. writerIdleTime:为写超时时间(即测试端一定时间内向被测试端发送消息)
  3. allIdleTime:所有类型的超时时间

        这个类主要也是一个ChannelHandler,也需要被载入到ChannelPipeline中,加入我们在服务器端的ChannelInitializer中,在我的项目中(第二节有代码截图):

...
    .addLast(new IdleStateHandler(0, 0, 30, TimeUnit.SECONDS))
    .addLast(new IdleHandler())
...

这段代码的意思是:

        在服务器端会每隔30秒来检查一下channelRead方法被调用的情况,如果在30秒内该链上的channelRead方法都没有被触发,就会调用userEventTriggered方法。

3.2 channelRead、channelActive、channelIdle方法源码

初步地看下IdleStateHandler源码,先看下IdleStateHandler中的channelRead方法:

Netty自带的心跳机制——IdleStateHandler

        在我当前项目的版本中透传和记录调用时间分别放在了channelRead方法和channelReadComplete方法中,在其他版本中,这两处代码都在channelRead中实现,这处小改动是不影响主流程的,为方便比较,我贴上其他版本的代码:

Netty自带的心跳机制——IdleStateHandler

        代码其实表示该方法只是进行了透传,不做任何业务逻辑处理,让channelPipe中的下一个handler处理channelRead方法,但是记录了一下这里的调用时间。

特别说明:

fireChannelRead方法表示传递消息到下一个处理器。

扩展:

fireChannelRead用法 - sunshine_star - 博客园

我们再看看channelActive方法:

Netty自带的心跳机制——IdleStateHandler

这里有个initialize的方法,这是IdleStateHandler的精髓,接着探究:

Netty自带的心跳机制——IdleStateHandler

 这里会触发一个Task,ReaderIdleTimeoutTask,这个task是部分源码是这样的:

Netty自带的心跳机制——IdleStateHandler

        代码逻辑是这样的,用当前时间减去最后一次channelRead方法调用的时间,假如这个结果是6s,说明最后一次调用channelRead已经是6s之前的事情了,你设置的是5s,那么nextDelay则为-1,说明超时了,那么则执行③处代码,会触发channelIdle方法,channelIdle会执行fireUserEventTriggered方法,即下一个处理器的userEventTriggered方法:

Netty自带的心跳机制——IdleStateHandler

 看到这里前面问题的答案就出来了:

为什么要加自定义的IdleHandler代码,并重载userEventTriggered方法?

——因为在自带的IdleStateHandler处理器,在心跳检测超时时,最终会执行下一个handler的userEventTriggered方法来处理。

初略地看下就是这么多了,这就是IdleStateHandler的基本原理了。

四、总结 

简而言之:

        IdleStateHandler这个类会根据你设置的超时参数的类型和值,循环去检测channelRead和write方法多久没有被调用了,如果这个时间超过了你设置的值,那么就会触发对应的事件,read触发read,write触发write,all触发all。

  • 如果超时了,则会调用userEventTriggered方法,且会告诉你超时的类型
  • 如果没有超时,则会循环定时检测,除非你将IdleStateHandler移除Pipeline
上一篇:Netty学习之用户空间和内核空间


下一篇:使用Netty和动态代理实现一个简单的RPC