怎么设置读写超时的监听函数呢,首先从文档开始,或者看看官方有没有例子,一般任何平台的官方都会或多或少的提供例子。
官方文档有这样一个类new IdleStateHandler(readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds),我这里就用中式英语翻译,大体的意思就是空闲状态的处理者,第一个参数设置读超时时间,第二个参数设置写超时时间,第三个设置总的超时时间。
怎么用呢,咱们先看看它的父类是什么public class IdleStateHandler extends ChannelDuplexHandler,那么ChannelDuplexHandler又是什么鬼,public class ChannelDuplexHandler extends ChannelInboundHandlerAdapter,继承与ChannelInboundHandlerAdapter,前几篇介绍过ChannelInboundHandlerAdapter这个类标记了这个管道是用来连接读管道的,那么疑问来了,写超时它是怎么计算的。通过以下入口点,进入空闲状态的处理者内部。
ch.pipeline().addLast("IdleState",
new IdleStateHandler(5, 5, 10));
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);
}
}
构造方法里,判断设置的时间值,如果小于等于0,设置时间为0,总时间取默认值和设置值的最大值。直接点类进入后我们只能跟踪到构造方法,其他方法基本上都是被回调的方法,看这个
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.
}
}
handlerAdded()方法在什么时候被执行呢,第一篇讲过连接之前,要进行注册,那么跟进去看看是不是那个地方执行的,该方法在DefaultChannelPipeline的callHandlerAdded0方法里调用
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
//检查handler是否合法
checkMultiplicity(handler);
newCtx = newContext(group, filterName(name, handler), handler);
addLast0(newCtx);
// If the registered is false it means that the channel was not registered on an eventloop yet.
// In this case we add the context to the pipeline and add a task that will call
// ChannelHandler.handlerAdded(...) once the channel is registered.
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
newCtx.setAddPending();
executor.execute(new Runnable() {
@Override
public void run() {
//被调用
callHandlerAdded0(newCtx);
}
});
return this;
}
}
//被调用
callHandlerAdded0(newCtx);
return this;
}
看代码中文注释部分,此方法原来是在添加的时候被调用,和注册没关系
。
如果此时连接已经建立,那么就初始化这个管道,0k,开始执行initialize方法,
private void initialize(ChannelHandlerContext ctx) {
//第一次进入的时候state为默认值,肯定不是1和2,跳过
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);
}
}
启动任务最后还是加入到NioEventLoop的消息队列中,并延时执行,任务执行每次都是在读写以后。接下来看一下这几个任务都干了什么
读任务的代码:
protected void run(ChannelHandlerContext ctx) {
long nextDelay = readerIdleTimeNanos;
//是否正在读,每次调用管道读的时候reading会变成true,而读完后reading变成false
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 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);
}
}
}
通过上面代码可以发现,每次执行到任务时获取当前的时间减去上一次读取的时间,如果大于最大读取时间的话,那么回调超时接口。那么回调的是什么呢?跟进看一下
protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception {
ctx.fireUserEventTriggered(evt);
}
最后又通过管道包装类把回调分发出去
@Override
public ChannelHandlerContext fireUserEventTriggered(final Object event) {
invokeUserEventTriggered(findContextInbound(), event);
return this;
}
最后又执行到循环调用队列方法的地方
private void invokeUserEventTriggered(Object event) {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).userEventTriggered(this, event);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireUserEventTriggered(event);
}
}
因为将管道添加进去的时候,invokeHander已然返回true,所以必然执行 ((ChannelInboundHandler) handler()).userEventTriggered(this, event);
也就是每一个读管道的
public void userEventTriggered(ChannelHandlerContext ctx, Object evt)
throws Exception {
// TODO Auto-generated method stub
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state().equals(IdleState.ALL_IDLE)) {
// 未进行写操作发送心跳包
String heratBeat = "{\"event\":\"HEARTBEAT\"}";
ctx.write(Unpooled.copiedBuffer(heratBeat.getBytes()));
ctx.flush();
}
}
super.userEventTriggered(ctx, evt);
}
这个方法,我们可以在这个里面做一些事情,比如一定时间没有和服务器发信息了,那么发送心跳,看
super.userEventTriggered(ctx, evt),它的父类方法都做了什么
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
ctx.fireUserEventTriggered(evt);
}
奥,这不是又把事件分发给队列的下一个管道处理类了吗,默认的解析器我们不会重写 userEventTriggered方法,所以他会直接分发给下一个管道类处理。
那么继续,从上面的读超时任务,知道有一个最后读取数据的时间变量lastReadTime,那么它是怎么计算的呢?
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
if ((readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) && reading) {
lastReadTime = ticksInNanos();
reading = false;
}
ctx.fireChannelReadComplete();
}
这个值是读数据完成的回调方法里执行的,第二篇有详细介绍
public final void read() {
final ChannelConfig config = config();
final ChannelPipeline pipeline = pipeline();
final ByteBufAllocator allocator = config.getAllocator();
final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
allocHandle.reset(config);
ByteBuf byteBuf = null;
boolean close = false;
try {
do {
byteBuf = allocHandle.allocate(allocator);
allocHandle.lastBytesRead(doReadBytes(byteBuf));
if (allocHandle.lastBytesRead() <= 0) {
// nothing was read. release the buffer.
byteBuf.release();
byteBuf = null;
close = allocHandle.lastBytesRead() < 0;
break;
}
allocHandle.incMessagesRead(1);
readPending = false;
//读管道处理操作回调
pipeline.fireChannelRead(byteBuf);
byteBuf = null;
} while (allocHandle.continueReading());
//读取完成的操作回调
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
if (close) {
closeOnRead(pipeline);
}
} catch (Throwable t) {
handleReadException(pipeline, byteBuf, t, close, allocHandle);
} finally {
// Check if there is a readPending which was not processed yet.
// This could be for two reasons:
// * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
// * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
//
// See https://github.com/netty/netty/issues/2254
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}
}
看代码中文注释,现在读超时的思路已经清晰。接着看写任务做了哪些
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);
}
}
}
执行逻辑和读差不多,其他跳过直接看
lastWriteTime这个参数哪来的
private final ChannelFutureListener writeListener = new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
lastWriteTime = ticksInNanos();
firstWriterIdleEvent = firstAllIdleEvent = true;
}
};
哎要,它是在回调函数中设置的值
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
// Allow writing with void promise if handler is only configured for read timeout events.
if (writerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
ChannelPromise unvoid = promise.unvoid();
unvoid.addListener(writeListener);
ctx.write(msg, unvoid);
} else {
ctx.write(msg, promise);
}
}
是在write方法中加入的回调值,额什么时候执行write呢
private static boolean isOutbound(ChannelHandler handler) {
return handler instanceof ChannelOutboundHandler;
}
只要实现了此接口就能监听的管道的写消息,所以
IdleStateHandler实现了
implements ChannelOutboundHandler接口,写方法当然会执行,就和第二篇写消息流程一样,不再具体分析。
第五篇的时候,我会来一个netty的总结,把类关系图弄一下,那样感觉更直观。