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); }