【netty学习笔记八】IdleStateHandler心跳检测机制

在节点通信时,经常需要心跳机制来探测对方是否是存活的。在netty中,IdleStateHandler就能提供这种心跳检测功能。让我们先看看例子:
服务端添加的handler

.childHandler(new ChannelInitializer() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
   ChannelPipeline p = ch.pipeline();
   //添加IdleStateHandler,5s检测一次读事件
   p.addLast(new IdleStateHandler(5, 0, 0));
   p.addLast(new TimeServerHandler());
   p.addLast(new HeartBeatServerHandler());
}

public class HeartBeatServerHandler extends ChannelInboundHandlerAdapter {
    int lossConnectCount = 0;
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent){
            IdleStateEvent event = (IdleStateEvent)evt;
            if (event.state()== IdleState.READER_IDLE){
                lossConnectCount++;
                if (lossConnectCount>2){
                    System.out.println("关闭这个不活跃通道!");
                    ctx.channel().close();
                }
            }
        }else {
            super.userEventTriggered(ctx,evt);
        }
    }
}

服务端我们添加一个IdleStateHandler,若5s内无读事件则触发心跳处理方法HeartBeatServerHandler#userEventTriggered,若连续2次无读事件,则关闭这个客户端channel。
客户端:

.handler(new ChannelInitializer(){
@Override
protected void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline().addLast(new IdleStateHandler(5, 0, 0));
    ch.pipeline().addLast(new TimeClientHandler());
    }
});

在看IdleStateHandler实现前,我们先想一下如果自己实现的话会怎么实现呢?
首先,IdleStateHandler在每个客户端接入时,会生成一个对象。同时要初始化做一些事,比如对读或写事件进行定时判断,看在指定的时间内有没有感兴趣的读/写事件,没有则触发事件,做一些自定义的事情。同时上次读/写事件完成后需更新时间,以便定时任务能及时感知。定时判断可以利用EventLoop父类自带的schedule调度方法, 更新上次读/写事件完成后的时间需要监听读完成、写完成事件,需要实现入站、出站接口。
让我们看看netty中的实现:

public class IdleStateHandler extends ChannelDuplexHandler

继承了ChannelDuplexHandler类,此类实现了入站、出站接口。继续看构造方法:

public IdleStateHandler(boolean observeOutput,
            long readerIdleTime, long writerIdleTime, long allIdleTime,
            TimeUnit unit) {
        if (unit == null) {
            throw new NullPointerException("unit");
        }

        this.observeOutput = observeOutput;

        if (readerIdleTime <= 0) {
            readerIdleTimeNanos = 0;
        } else {
            readerIdleTimeNanos = Math.max(unit.toNanos(readerIdleTime), MIN_TIMEOUT_NANOS);
        }
        if (writerIdleTime <= 0) {
            writerIdleTimeNanos = 0;
        } else {
            writerIdleTimeNanos = Math.max(unit.toNanos(writerIdleTime), MIN_TIMEOUT_NANOS);
        }
        if (allIdleTime <= 0) {
            allIdleTimeNanos = 0;
        } else {
            allIdleTimeNanos = Math.max(unit.toNanos(allIdleTime), MIN_TIMEOUT_NANOS);
        }
    }

初始化了读、写、读或写的超时参数。再看看初始化调度方法:

private void initialize(ChannelHandlerContext ctx) {
        // Avoid the case where destroy() is called before scheduling timeouts.
        // See: https://github.com/netty/netty/issues/143
        //state: 0 - none, 1 - initialized, 2 - destroyed
        switch (state) {
        case 1:
        case 2:
            return;
        }

        state = 1;
        initOutputChanged(ctx);

        lastReadTime = lastWriteTime = ticksInNanos();
        if (readerIdleTimeNanos > 0) {
            readerIdleTimeout = schedule(ctx, new ReaderIdleTimeoutTask(ctx),
                    readerIdleTimeNanos, TimeUnit.NANOSECONDS);
        }
        if (writerIdleTimeNanos > 0) {
            writerIdleTimeout = schedule(ctx, new WriterIdleTimeoutTask(ctx),
                    writerIdleTimeNanos, TimeUnit.NANOSECONDS);
        }
        if (allIdleTimeNanos > 0) {
            allIdleTimeout = schedule(ctx, new AllIdleTimeoutTask(ctx),
                    allIdleTimeNanos, TimeUnit.NANOSECONDS);
        }
    }

initialize方法在addLast -> handlerAdded中会调用。比如我们设置了检测读事件,那readerIdleTimeNanos>0,会执行schedule方法。这里会传一个ReaderIdleTimeoutTask对象过去,让我们先看看ReaderIdleTimeoutTask做了什么:

private final class ReaderIdleTimeoutTask extends AbstractIdleTask {

        ReaderIdleTimeoutTask(ChannelHandlerContext ctx) {
            super(ctx);
        }

        @Override
        protected void run(ChannelHandlerContext ctx) {
            //读超时时间
            long nextDelay = readerIdleTimeNanos;
            //如果还在读时间进行中则不进行判断
            if (!reading) {
                //判断当前时间-上次读时间是否大于超时时间
                nextDelay -= ticksInNanos() - lastReadTime;
            }

            if (nextDelay <= 0) {
                // Reader is idle - set a new timeout and notify the callback.
               
                readerIdleTimeout = schedule(ctx, this, readerIdleTimeNanos, TimeUnit.NANOSECONDS);

                boolean first = firstReaderIdleEvent;
                firstReaderIdleEvent = false;

                try {
                     //触发fireUserEventTriggered方法
                    IdleStateEvent event = newIdleStateEvent(IdleState.READER_IDLE, first);
                    channelIdle(ctx, event);
                } catch (Throwable t) {
                    ctx.fireExceptionCaught(t);
                }
            } else {
                // Read occurred before the timeout - set a new timeout with shorter delay.
                readerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
            }
        }
    }
private abstract static class AbstractIdleTask implements Runnable {

        private final ChannelHandlerContext ctx;

        AbstractIdleTask(ChannelHandlerContext ctx) {
            this.ctx = ctx;
        }

        @Override
        public void run() {
            if (!ctx.channel().isOpen()) {
                return;
            }
            //调用子类的run方法
            run(ctx);
        }

        protected abstract void run(ChannelHandlerContext ctx);
    }

再看看schedule方法:

ScheduledFuture schedule(ChannelHandlerContext ctx, Runnable task, long delay, TimeUnit unit) {
        return ctx.executor().schedule(task, delay, unit);
    }
 ScheduledFuture schedule(final ScheduledFutureTask task) {
        if (inEventLoop()) {
            scheduledTaskQueue().add(task);
        } else {
            execute(new Runnable() {
                @Override
                public void run() {
                    scheduledTaskQueue().add(task);
                }
            });
        }

        return task;
    }

schedule方法最终会调用AbstractScheduledEventExecutor#schedule方法,将定时任务包装成ScheduledFutureTask放入scheduledTaskQueue队列中。在eventLoop的runAllTasks中会拉取task进行调度(如果到了此延迟任务执行的时候)。
再看看上次读事件更新:

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
            //读事件进行中,会将reading设为true,此时不会除非读检测事件。
            reading = true;
            firstReaderIdleEvent = firstAllIdleEvent = true;
        }
        ctx.fireChannelRead(msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        //读事件完成后会更新读时间
        if ((readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) && reading) {
            lastReadTime = ticksInNanos();
            reading = false;
        }
        ctx.fireChannelReadComplete();
    }

其他检测事件也类似,这里就不分析了。

你可能感兴趣的:(【netty学习笔记八】IdleStateHandler心跳检测机制)