• netty源码解析(4.0)-17 ChannelHandler: IdleStateHandler实现


       io.netty.handler.timeout.IdleStateHandler功能是监测Channel上read, write或者这两者的空闲状态。当Channel超过了指定的空闲时间时,这个Handler会触发一个IdleStateEvent事件。

      在第一次检测到Channel变成active状态时向EventExecutor中提交三个延迟任务:

        ReaderIdleTimeoutTask: 检测read空闲超时。

        WriterIdleTimeoutTask: 检测write空闲超时。

        AllIdleTimeoutTask: 检测所有的空闲超时。

      任何一个延迟任务检测到空闲超时是会触发一个IdleStateEvent。无论如何,延迟任务都会再次把自己提交到EventExecutor中,等待下次执行。

      三个延迟任务对应于三个超时时间,都是可以独立设置的:

     1 public IdleStateHandler(boolean observeOutput,
     2             long readerIdleTime, long writerIdleTime, long allIdleTime,
     3             TimeUnit unit) {
     4         if (unit == null) {
     5             throw new NullPointerException("unit");
     6         }
     7 
     8         this.observeOutput = observeOutput;
     9 
    10         if (readerIdleTime <= 0) {
    11             readerIdleTimeNanos = 0;
    12         } else {
    13             readerIdleTimeNanos = Math.max(unit.toNanos(readerIdleTime), MIN_TIMEOUT_NANOS);
    14         }
    15         if (writerIdleTime <= 0) {
    16             writerIdleTimeNanos = 0;
    17         } else {
    18             writerIdleTimeNanos = Math.max(unit.toNanos(writerIdleTime), MIN_TIMEOUT_NANOS);
    19         }
    20         if (allIdleTime <= 0) {
    21             allIdleTimeNanos = 0;
    22         } else {
    23             allIdleTimeNanos = Math.max(unit.toNanos(allIdleTime), MIN_TIMEOUT_NANOS);
    24         }
    25     }

      这个类继承自io.netty.channel.ChannelDuplexHandler, 它是一个有状态的ChannelHandler, 定义了三个状态:

      private byte state; // 0 - none, 1 - initialized, 2 - destroyed

      state属性保存了它的状态。0:初始状态,1:已经初始化, 2: 已经销毁。

      这个ChannelHandler被加入到Channel的pipeline中之后,在Channel已经被register到EventLoop中,且处于Active状态时,会执行一次初始化操作,向EventExecutor提交前面提到的三个延迟任务。这初始化操作在initialize方法中实现。

     1     private void initialize(ChannelHandlerContext ctx) {
     2         // Avoid the case where destroy() is called before scheduling timeouts.
     3         // See: https://github.com/netty/netty/issues/143
     4         switch (state) {
     5         case 1:
     6         case 2:
     7             return;
     8         }
     9 
    10         state = 1;
    11         initOutputChanged(ctx);
    12 
    13         lastReadTime = lastWriteTime = ticksInNanos();
    14         if (readerIdleTimeNanos > 0) {
    15             readerIdleTimeout = schedule(ctx, new ReaderIdleTimeoutTask(ctx),
    16                     readerIdleTimeNanos, TimeUnit.NANOSECONDS);
    17         }
    18         if (writerIdleTimeNanos > 0) {
    19             writerIdleTimeout = schedule(ctx, new WriterIdleTimeoutTask(ctx),
    20                     writerIdleTimeNanos, TimeUnit.NANOSECONDS);
    21         }
    22         if (allIdleTimeNanos > 0) {
    23             allIdleTimeout = schedule(ctx, new AllIdleTimeoutTask(ctx),
    24                     allIdleTimeNanos, TimeUnit.NANOSECONDS);
    25         }
    26     }

      第4-10行,只有处于初始状态时才执行后面的操作,避免多次提交定时任务。

      第11行, 初始化对对Channel的outboundBuffer变化的监视,只有当observeOutput属性设置为true时才开启这个监视。

      第13-25行,分别提交三个延迟任务。

      initialize方法可能在三个地方被调用:

        @Override
        public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
            if (ctx.channel().isActive() && ctx.channel().isRegistered()) {
                // channelActive() event has been fired already, which means this.channelActive() will
                // not be invoked. We have to initialize here instead.
                initialize(ctx);
            } else {
                // channelActive() event has not been fired yet.  this.channelActive() will be invoked
                // and initialization will occur there.
            }
        }
    
        @Override
        public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
            // Initialize early if channel is active already.
            if (ctx.channel().isActive()) {
                initialize(ctx);
            }
            super.channelRegistered(ctx);
        }
    
    
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            // This method will be invoked only if this handler was added
            // before channelActive() event is fired.  If a user adds this handler
            // after the channelActive() event, initialize() will be called by beforeAdd().
            initialize(ctx);
            super.channelActive(ctx);
        }

      如果在Channel初始化的时候把这个Handler添加到pipeline中,那么这个Handler的channelActive方法一定会被调用,只需要在channleActive中调用initialize就可以打了。但是Handler可以在任何时候被加入到pipleline中。当ChannelHandler被添加到pipeline中时,Channel可能已经被register到EventLoop中,且已经处于Active状态,这种情况下,channelRegistered和channelActive方法都不会被调用,所以必须在handlerAdded中调用initialize。如果此时,Channnel已经处于Active状态,但还没被注册到EventLoop,只能在channelRegisted中调用initialize。

      

      初始化完成之后,延迟任务到期执行时会把自己再次提交到EventExecutor中,等待下次执行。同时会检查是否满足触发事件的条件,如果是就触发一条自定义的事件。

      

    read空闲超时检查

     1 private final class ReaderIdleTimeoutTask extends AbstractIdleTask {
     2         @Override
     3         protected void run(ChannelHandlerContext ctx) {
     4             long nextDelay = readerIdleTimeNanos;
     5             if (!reading) {
     6                 nextDelay -= ticksInNanos() - lastReadTime;
     7             }
     8 
     9             if (nextDelay <= 0) {
    10                 // Reader is idle - set a new timeout and notify the callback.
    11                 readerIdleTimeout = schedule(ctx, this, readerIdleTimeNanos, TimeUnit.NANOSECONDS);
    12 
    13                 boolean first = firstReaderIdleEvent;
    14                 firstReaderIdleEvent = false;
    15 
    16                 try {
    17                     IdleStateEvent event = newIdleStateEvent(IdleState.READER_IDLE, first);
    18                     channelIdle(ctx, event);
    19                 } catch (Throwable t) {
    20                     ctx.fireExceptionCaught(t);
    21                 }
    22             } else {
    23                 // Read occurred before the timeout - set a new timeout with shorter delay.
    24                 readerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
    25             }
    26         }
    27     }

      4-9行,判断是否read空闲超时。

      11-21行,read空闲超时,重新把自己提交成延迟任务。

      24行,read没有空闲超时,重新把自己提交成延迟任务。

      这里的关键是判断read空闲超时。lastReadTime是最近一次执行read的时间,readerIdleTimeNanos是初始化时设置的空闲超时时间,因此如果readerIdleTimeNanos - (ticksInNanos() - lastReadtime)  <= 0,表示已经read空闲超时了。令人困惑的是第5行,只有在reading==false才检查进行空闲超时的计算。笔者在<<netty源码解解析(4.0)-14 Channel NIO实现:读取数据>>一章中分析过Channel read的实现。一次read操作或触发多个read和一个readComplete事件,read操作由多个步骤组成。这reading属性用来表示正在read的状态。

     1     @Override
     2     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
     3         if (readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
     4             reading = true;
     5             firstReaderIdleEvent = firstAllIdleEvent = true;
     6         }
     7         ctx.fireChannelRead(msg);
     8     }
     9 
    10     @Override
    11     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
    12         if ((readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) && reading) {
    13             lastReadTime = ticksInNanos();
    14             reading = false;
    15         }
    16         ctx.fireChannelReadComplete();
    17     }

      3-4行,在设置了读空闲超时或所有空闲超时的情况下,会吧reading设置成true,表示当前正处于正在read的状态。

      12-14行,在设置了读空闲超时或所有空闲超时的情况下, 如果当前正处于read状态,把reading设置成false,同时更新最近一次执行read的时间。

    write空闲超时检查

     1     private final class WriterIdleTimeoutTask extends AbstractIdleTask {
     2 
     3         @Override
     4         protected void run(ChannelHandlerContext ctx) {
     5 
     6             long lastWriteTime = IdleStateHandler.this.lastWriteTime;
     7             long nextDelay = writerIdleTimeNanos - (ticksInNanos() - lastWriteTime);
     8             if (nextDelay <= 0) {
     9                 // Writer is idle - set a new timeout and notify the callback.
    10                 writerIdleTimeout = schedule(ctx, this, writerIdleTimeNanos, TimeUnit.NANOSECONDS);
    11 
    12                 boolean first = firstWriterIdleEvent;
    13                 firstWriterIdleEvent = false;
    14 
    15                 try {
    16                     if (hasOutputChanged(ctx, first)) {
    17                         return;
    18                     }
    19 
    20                     IdleStateEvent event = newIdleStateEvent(IdleState.WRITER_IDLE, first);
    21                     channelIdle(ctx, event);
    22                 } catch (Throwable t) {
    23                     ctx.fireExceptionCaught(t);
    24                 }
    25             } else {
    26                 // Write occurred before the timeout - set a new timeout with shorter delay.
    27                 writerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
    28             }
    29         }
    30     }

      6-8行,检查write空闲超时,和检查read空闲超时类似。

      12-21行,如果write空闲超时,且outboundBuffer中的数据没有变化, 触发write空闲超时事件。

      这里调用了hasOutputChanged方法检查outboundBuffer中的数据是否有变化。笔者在<<netty源码解解析(4.0)-15 Channel NIO实现:写数据>>中分write实现时,已经讲过,每个Channel都以一个outboundBuffer, write的数据会先序列化成Byte流追加到outboundBuffer中,然后再从outboundBuffer中顺序读出Byte流执行真正的write操作。在Handler的write方法没有被调用的情况下,如果outboundBuffer中有数据,且数据发送了变化,表示正在执行真正的write操作,反之则意味着Channel处于不可写的状态,无法执行真正的write操作。write空闲超时事件只会在write空闲超时且没有执行真正write操作的时候才会触发。另外,这个检查有个开关属性,只有observeOutput==true时才会检查。

      

      AllIdleTimeoutTask的实现和WriterIdleTimeoutTask类似,只不过检查超时的条件有些差别:read和write任何一个空闲超时都算超时。

    ReadTimeoutHandler实现

      ReadTimeoutHandler继承了IdleStateHandler类,它的功能是在触发read空闲超时事件时触发一个ReadTimeoutException异常,同时关闭Channel。 

        @Override
        protected final void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception {
            assert evt.state() == IdleState.READER_IDLE;
            readTimedOut(ctx);
        }
    
        /**
         * Is called when a read timeout was detected.
         */
        protected void readTimedOut(ChannelHandlerContext ctx) throws Exception {
            if (!closed) {
                ctx.fireExceptionCaught(ReadTimeoutException.INSTANCE);
                ctx.close();
                closed = true;
            }
        }

    WriteTimeoutHandler实现

      WriteTimeoutHandler继承了ChannelOutboundHandlerAdapter,它的功能是在触发监视Channel的write调用超时,如果超时则关闭掉这个Channel。和ReadTimeoutHandler不同,它监控的不是空闲超时,而是Channel的write方法返回的Promise超时。

      首先在write时候,为每个Promise添加一个监控超时的延迟任务:

        @Override
        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
            scheduleTimeout(ctx, promise);
            ctx.write(msg, promise);
        }
        private void scheduleTimeout(final ChannelHandlerContext ctx, final ChannelPromise promise) {
            // Schedule a timeout.
            final WriteTimeoutTask task = new WriteTimeoutTask(ctx, promise);
            task.scheduledFuture = ctx.executor().schedule(task, timeoutNanos, TimeUnit.NANOSECONDS);
    
            if (!task.scheduledFuture.isDone()) {
                addWriteTimeoutTask(task);
    
                // Cancel the scheduled timeout if the flush promise is complete.
                promise.addListener(task);
            }
        }

       然后,如果延迟任务执行的时候检查到Promise超时,就触发一个WriteTimeoutException异常,然后关闭掉这个Channel。

        protected void writeTimedOut(ChannelHandlerContext ctx) throws Exception {
            if (!closed) {
                ctx.fireExceptionCaught(WriteTimeoutException.INSTANCE);
                ctx.close();
                closed = true;
            }
        }

       WriteTimeoutTask类同时实现了Runnable和ChannelFutureListener接口,超时后会调用run方法。

     1         @Override
     2         public void run() {
     3             // Was not written yet so issue a write timeout
     4             // The promise itself will be failed with a ClosedChannelException once the close() was issued
     5             // See https://github.com/netty/netty/issues/2159
     6             if (!promise.isDone()) {
     7                 try {
     8                     writeTimedOut(ctx);
     9                 } catch (Throwable t) {
    10                     ctx.fireExceptionCaught(t);
    11                 }
    12             }
    13             removeWriteTimeoutTask(this);
    14         }

      7-10行,promise没有完成,触发WriteTimeoutException或其他异常。

          13行,write已经完成,删除当前的WriteTimeoutTask对象。

        如果promise已经完成, 会调用operationComplete方法, 清理掉当前的WriteTimeoutTask对象。

            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                // scheduledFuture has already be set when reaching here
                scheduledFuture.cancel(false);
                removeWriteTimeoutTask(this);
            }

       

      

  • 相关阅读:
    CMD常用命令
    SpringMVC常用方法总结
    Oracle数据库中,sql中(+)(-)的含义
    电脑配置Java环境变量之后,在cmd中仍然无法识别
    tomcat部署项目遇到的问题
    tomcat的stratup小黑框名字修改
    CSS定位
    cookie存储userID所遇到的问题
    修改Tomcat的server.xml之后,tomcat 部署项目报错:Removing obsolete files from server... Could not clean server of obsolete files: null java.lang.NullPointerException
    安装oracle11g跳不过下载软件更新[INS-30131] 执行安装程序验证所需的初始设置失败
  • 原文地址:https://www.cnblogs.com/brandonli/p/11252760.html
Copyright © 2020-2023  润新知