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中,等待下次执行。
三个延迟任务对应于三个超时时间,都是可以独立设置的:
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);
}
}
这个类继承自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方法中实现。
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);
}
}
第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空闲超时检查
private final class ReaderIdleTimeoutTask extends AbstractIdleTask {
@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 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);
}
}
}
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的状态。
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
reading = true;
firstReaderIdleEvent = firstAllIdleEvent = true;
}
ctx.fireChannelRead(msg);
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
if ((readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) && reading) {
lastReadTime = ticksInNanos();
reading = false;
}
ctx.fireChannelReadComplete();
}
3-4行,在设置了读空闲超时或所有空闲超时的情况下,会吧reading设置成true,表示当前正处于正在read的状态。
12-14行,在设置了读空闲超时或所有空闲超时的情况下, 如果当前正处于read状态,把reading设置成false,同时更新最近一次执行read的时间。
write空闲超时检查
private final class WriterIdleTimeoutTask extends AbstractIdleTask {
@Override
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);
}
}
}
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方法。
@Override
public void run() {
// Was not written yet so issue a write timeout
// The promise itself will be failed with a ClosedChannelException once the close() was issued
// See https://github.com/netty/netty/issues/2159
if (!promise.isDone()) {
try {
writeTimedOut(ctx);
} catch (Throwable t) {
ctx.fireExceptionCaught(t);
}
}
removeWriteTimeoutTask(this);
}
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);
}
netty源码解解析(4.0)-17 ChannelHandler: IdleStateHandler实现的更多相关文章
- netty源码解解析(4.0)-18 ChannelHandler: codec--编解码框架
编解码框架和一些常用的实现位于io.netty.handler.codec包中. 编解码框架包含两部分:Byte流和特定类型数据之间的编解码,也叫序列化和反序列化.不类型数据之间的转换. 下图是编解码 ...
- netty源码解解析(4.0)-20 ChannelHandler: 自己实现一个自定义协议的服务器和客户端
本章不会直接分析Netty源码,而是通过使用Netty的能力实现一个自定义协议的服务器和客户端.通过这样的实践,可以更深刻地理解Netty的相关代码,同时可以了解,在设计实现自定义协议的过程中需要解决 ...
- netty源码解解析(4.0)-19 ChannelHandler: codec--常用编解码实现
数据包编解码过程中主要的工作就是:在编码过程中进行序列化,在解码过程中从Byte流中分离出数据包然后反序列化.在MessageToByteEncoder中,已经解决了序列化之后的问题,ByteToMe ...
- netty源码解解析(4.0)-16 ChannelHandler概览
本章开始分析ChannelHandler实现代码.ChannelHandler是netty为开发者提供的实现定制业务的主要接口,开发者在使用netty时,最主要的工作就是实现自己的ChannelHan ...
- netty源码解解析(4.0)-11 Channel NIO实现-概览
结构设计 Channel的NIO实现位于io.netty.channel.nio包和io.netty.channel.socket.nio包中,其中io.netty.channel.nio是抽象实 ...
- netty源码解解析(4.0)-10 ChannelPipleline的默认实现--事件传递及处理
事件触发.传递.处理是DefaultChannelPipleline实现的另一个核心能力.在前面在章节中粗略地讲过了事件的处理流程,本章将会详细地分析其中的所有关键细节.这些关键点包括: 事件触发接口 ...
- netty源码解解析(4.0)-15 Channel NIO实现:写数据
写数据是NIO Channel实现的另一个比较复杂的功能.每一个channel都有一个outboundBuffer,这是一个输出缓冲区.当调用channel的write方法写数据时,这个数据被一系列C ...
- netty源码解解析(4.0)-14 Channel NIO实现:读取数据
本章分析Nio Channel的数据读取功能的实现. Channel读取数据需要Channel和ChannelHandler配合使用,netty设计数据读取功能包括三个要素:Channel, Eve ...
- netty源码解解析(4.0)-12 Channel NIO实现:channel初始化
创建一个channel实例,并把它register到eventLoopGroup中之后,这个channel然后处于inactive状态,仍然是不可用的.只有在bind或connect方法调用成功之后才 ...
随机推荐
- Java 集合类Hashmap
一.HashMap 简介 HashMap在程序员的开发过程中是一个十分常用的集合类,它是一个以键值对形式存在的集合类, 在开发中我们可以利用的它的一个key存在即替换的特性,实现一个更新的去重的操作. ...
- 哈工大计算机网络Week2-网络应用数据交换
目录 网络应用数据交换 P2P应用:原理与文件分发 纯P2P架构 文件分发:客户机/服务器 vs. P2P CS 为什么是这样的?不应该传送和发出难道是并行的??? P2P P2P文件分发典型例子:B ...
- 19.linux文件属性
1.linux文件属性 ls -lih i查看文件inode,h查看文件大小 文件总共10个属性 inode索引节点编号(唯一的) 文件类型和权限,第一个字符为类型,后面字符为权限 硬链接的数量 文件 ...
- python笔记(2)--字符串
一.字符串 字符串是不可变序列,具有序列的公共操作方法,具体操作见python笔记(1)--序列(列表 元组 range) 1.创建字符串 单引号:'Hello , I am Logan ! ' 双引 ...
- JAVA面试题 手写ArrayList的实现,在笔试中过关斩将?
面试官Q1:可以手写一个ArrayList的简单实现吗? 我们都知道ArrayList是基于数组实现,如果让你实现JDK源码ArrayList中add().remove().get()方法,你知道如何 ...
- 个人永久性免费-Excel催化剂功能第16波-N多使用场景的多维表转一维表
Excel表的多维表数据结构转换为一维表的数据结构,以供更进一步对数据进行加工整理,生成另外格式的汇总表,这是Excel数据处理的一大刚需,几乎每个Excel表哥.表姐都会遇到这样的使用场景.很可惜, ...
- Java用户程序
Java的用户程序分为两类:Java Application和Java Applet. 这两类程序在程序结构和执行机制上有一定的差异. Java Application是完整的程序,需要独立的Java ...
- python初步编写用户登录
python初步编写用户登录 python编写用户登录 用python写一个脚本,使得这个脚本在执行后,可以产生如下的效果: 1.用户的账号为:root 密码为:westos 2.用户账号和密码均输 ...
- java并发笔记之证明 synchronized锁 是否真实存在
警告⚠️:本文耗时很长,先做好心理准备 证明:偏向锁.轻量级锁.重量级锁真实存在 由[java并发笔记之java线程模型]链接: https://www.cnblogs.com/yuhangwang/ ...
- centos7上搭建zookeeper集群
1.下载zookeeper http://www.apache.org/dyn/closer.cgi/zookeeper/ 可以登录这个网站下载,然后上传到 centos上 修改成自己需要的版本 , ...