作用:用于空闲连接处理,当出现reader/writer空闲时,触发IdleStateEvent Netty事件,ChannelInboundHandler通过userEventTriggered来捕获这个事件,从而进行处理。
一、构造函数
一般我们使用的IdleStateHandler的构造函数有4个参数:
- long readerIdleTimeSeconds:Channel多久没有读取数据会触发IdleStateEvent,其中IdleState为IdleState.READER_IDLE。
- long writerIdleTimeSeconds:Channel多久没有写数据会触发IdleStateEvent,其中IdleState为IdleState.WRITER_IDLE。
- long allIdleTimeSeconds:Channel多久没有读和写数据会触发IdleStateEvent,其中IdleState为IdleState.ALL_IDLE。
- TimeUnit unit:时间单位
二、使用示例
在Netty的Channel中注册两个Handler,注意用于处理IdleStateEvent事件的Handler必须在IdleStateHandler之后注册,否则会出现捕获不到事件的情况(详细原因可以去详细了解Netty的Channel)。
pipeline.addLast("idleStateHandler", new IdleStateHandler(0, 0, 10,TimeUnit.MILLISECONDS));
pipeline.addLast("serverIdleHandler", new ServerIdleHandler());
我们这里的当10秒中没有读写操作时,将会产生一个IdleStateEvent事件,其中IdleState为IdleState.ALL_IDLE,并将其传递给之后的Handler。我们这里在ServerIdleHandler中进行监听这个事件。
其中ServerIdleHandler的源代码如下(需要自己实现)。
下面的示例用于定时关闭空闲的连接,如果发现IdleStateEvent直接将连接关闭。
public class ServerIdleHandler extends ChannelDuplexHandler {
/**
* 如果为IdleStateEvent事件,直接将连接关闭。
*/
@Override
public void userEventTriggered(final ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
ctx.close();
} else {
super.userEventTriggered(ctx, evt);
}
}
}
三、简单的源码分析
IdleStateHandler中有三个ScheduledFuture对象,
readerIdleTimeout/writerIdleTimeout/allIdleTimeout,以及三个内部类ReaderIdleTimeoutTask/WriterIdleTimeoutTask/AllIdleTimeoutTask。这里简单介绍一下readerIdleTimeout。
在Channel创建激活之后,将会调用IdleStateHandler中的initialize方法,该方法的源代码如下。
private void initialize(ChannelHandlerContext ctx) {
// Avoid the case where destroy() is called before scheduling timeouts.
// See: https://github.com/netty/netty/issues/143
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方法中通过schedule方法来注册定时任务。下面是schedule的源码。
/**
* ctx.executor() 默认情况下获取的是用于I/O操作的EventLoop,他是一个ScheduledExecutorService,IdleStateHandler使用这个类来执行定时任务,保留定时产生的ScheduledFuture。
* 为了避免Task占用太多的时间影响I/O操作,可以通过配置NioEventLoop的setIoRatio(int ioRatio)方法来调整这个Group的I/O操作和Task操作的时间比例,默认为50%,即I/O操作和Task操作1:1,高连接情况下建议调高这个值。
*
*/
ScheduledFuture<?> schedule(ChannelHandlerContext ctx, Runnable task, long delay, TimeUnit unit) {
return ctx.executor().schedule(task, delay, unit);
}
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 {
// 核心部分:当出现长时间没有读取数据的时候,产生一个IdleStateEvent事件。
IdleStateEvent event = newIdleStateEvent(IdleState.READER_IDLE, first);
/**
* 这部分等价于:ctx.fireUserEventTriggered(evt);传递事件给下一个ChannelHandler
*/
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);
}
}
}
IdleStateHandler类中还有WriterIdleTimeoutTask和AllIdleTimeoutTask两个事件,同样的用于当长时间没有写或者长时间没有做任何操作触发IdleStateEvent事件,这里不再介绍。