目录

一、前言

二、netty的心跳工具

三、IdleStatehandler

1、构造方法

2、handlerAdded

3、定时任务

4、读事件空闲

5、写事件空闲


一、前言

心跳机制就是定时的给对端发送特殊的数据包 , 对端收到后回复特殊的数据包 , 这一次往返的ping-pong过程 , 就是一次心跳,心跳的目的是为了让双方感知 ,对方还活着。

TCP协议层也是有心跳机制的 , 但是他的心跳是2个小时 ,且依赖底层操作系统 ,整体来讲不是很灵活 , 所以一般都是在应用层自由实现。

二、netty的心跳工具

netty 提供了IdleStateHandler , ReadTimeoutHandler , WriteTimeoutHandler 三个工具类来监测链接的存活性 , 当然 , 我们也可以自由实现。

序号

名称

作用

1

IdleStateHandler

监测链接的读、写、读写空闲时间是否超过了配置的指定时间 , 如果超过了,则触发一个IdleStateEvent事件 ,我们可以通过重写 ChannelInboundHandler.userEventTrigged 方法来做处理。

2

ReadTimeouthandler

指定时间内,没有发生读事件 , 则抛出异常,并自动关闭链接 , 我们可以在execeptionCaught方法中处理这个异常

3

WriteTimeoutHandler

同上 , 只不过关心的是写事件

总结 : 这两类工具 , 整体来讲 , 各有千秋:

IdleStatehandler 是以事件的方式 ,方式上比较优雅, 让服务感知到对端的链接空闲了, 具体如何操作 , 全看我们自己

Read/WriteTimeoutHandler已异常的方式处理 , 简单有效 , 且直接关闭了链接

三、IdleStatehandler

当连接的空闲时间(读或者写)太长时,将会触发一个 IdleStateEvent 事件。然后,你可以通过你的 ChannelInboundHandler 中重写 userEventTrigged 方法来处理该事件

1、构造方法

private final boolean observeOutput;// 是否考虑出站时较慢的情况。默认值是false(不考虑)。 
private final long readerIdleTimeNanos; // 读事件空闲时间,0 则禁用事件
private final long writerIdleTimeNanos;// 写事件空闲时间,0 则禁用事件
private final long allIdleTimeNanos; //读或写空闲时间,0 则禁用事件

2、handlerAdded

当该handler被添加到pipeline时 , 会调用initialize方法

给定的监测时间大于0 , 就会被创建周期调度任务 。 同时 ,将state状态设置为1, 防止重复初始化。

private void initialize(ChannelHandlerContext ctx) {
    //当channel被销毁时 或者 已经初始化过 , 不在初始化这个方法 , 直接返回
    switch (state) {
    case 1:
    case 2:
        return;
    }
    state = 1;
    //初始化 写状态的部分监测
    initOutputChanged(ctx);

    lastReadTime = lastWriteTime = ticksInNanos();
    //添加了三个调度任务 
    if (readerIdleTimeNanos > 0) {
      // 这里的 schedule 方法会调用 eventLoop 的 schedule 方法,将定时任务添加进队列中
        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);
    }
}

调用initOutputChanged方法 , 初始化 监控出栈数据属性

private void initOutputChanged(ChannelHandlerContext ctx) {
    if (observeOutput) {
        Channel channel = ctx.channel();
        Unsafe unsafe = channel.unsafe();
        ChannelOutboundBuffer buf = unsafe.outboundBuffer();
        // 记录了出站缓冲区相关的数据,buf 对象的 hash 码,和 buf 的剩余缓冲字节数
        if (buf != null) {
            lastMessageHashCode = System.identityHashCode(buf.current());
            lastPendingWriteBytes = buf.totalPendingWriteBytes();
        }
    }
}

observeOutput 针对出栈较慢的情况监测 。记录最后一次输出消息的相关信息 , 并使用一个值firstXXXXIdleEvent标识是否再次活动过,每次读写活动都变更其为ture ,那么如果是false ,说明这段时间没有发生过读写 。

同时 , 如果第一次记录的出栈数据 和 第二次得到的不同 , 说明出栈缓慢 , 则不触发空闲时间。

3、定时任务

抽象父任务: AbstractIdleTask

读空闲任务:ReaderIdleTimeoutTask

写空闲任务:WriterIdleTimeoutTask

读写空闲任务:AllIdleTimeoutTask

private abstract static class AbstractIdleTask implements Runnable {
    public void run() {
        // 当通道不是打开状态 , 不再执行
        if (!ctx.channel().isOpen()) {
            return;
        }

        run(ctx);
    }    
}

4、读事件空闲

protected void run(ChannelHandlerContext ctx) {
    //重新计算下次任务调度的间隔时间
    long nextDelay = readerIdleTimeNanos;
    if (!reading) {
        //配置的时间 - (最后一次读取 距离目前的时间差)
        nextDelay -= ticksInNanos() - lastReadTime;
    }
    //最新的调度时间 <=0 说明 , 空闲了 , 
    if (nextDelay <= 0) {
        // 发生空闲了 , 添加下一次的调度任务
        readerIdleTimeout = schedule(ctx, this, readerIdleTimeNanos, TimeUnit.NANOSECONDS);

        boolean first = firstReaderIdleEvent;
        firstReaderIdleEvent = false;

        try {
            // 再次提交任务
            IdleStateEvent event = newIdleStateEvent(IdleState.READER_IDLE, first);
            // 触发用户 handler use
            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);
    }
}

5、写事件空闲

protected void run(ChannelHandlerContext ctx) {
    // 最后一次写的时间
    long lastWriteTime = IdleStateHandler.this.lastWriteTime;
    // 计算最新的任务调度时间
    long nextDelay = writerIdleTimeNanos - (ticksInNanos() - lastWriteTime);
    // 空闲了
    if (nextDelay <= 0) {
        // Writer is idle - set a new timeout and notify the callback.
        // 重新入调度队列
        writerIdleTimeout = schedule(ctx, this, writerIdleTimeNanos, TimeUnit.NANOSECONDS);

        boolean first = firstWriterIdleEvent;
        firstWriterIdleEvent = false;

        try {
            // 判断下是不是出栈慢的情况 ,如果是 不触发空闲事件
            if (hasOutputChanged(ctx, first)) {
                return;
            }
            //触发一个事件
            IdleStateEvent event = newIdleStateEvent(IdleState.WRITER_IDLE, first);
            channelIdle(ctx, event);
        } catch (Throwable t) {
            ctx.fireExceptionCaught(t);
        }
    } else {
        // Write occurred before the timeout - set a new timeout with shorter delay.
        // 以最新时间 重新入调度队列
        writerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
    }
}