netty源码解解析(4.0)-13 Channel NIO实现: 关闭和清理
Channel提供了3个方法用来实现关闭清理功能:disconnect,close,deregister。本章重点分析这个3个方法的功能的NIO实现。
disconnect实现: 断开连接
disconnect方法的调用栈如下:
io.netty.channel.AbstractChannel#disconnect()
io.netty.channel.DefaultChannelPipeline#disconnect()
io.netty.channel.AbstractChannelHandlerContext#disconnect()
io.netty.channel.AbstractChannelHandlerContext#disconnect(io.netty.channel.ChannelPromise)
io.netty.channel.AbstractChannelHandlerContext#invokeDisconnect
io.netty.channel.DefaultChannelPipeline.HeadContext#disconnect
io.netty.channel.AbstractChannel.AbstractUnsafe#disconnect
io.netty.channel.socket.nio.NioSocketChannel#doDisconnect
io.netty.channel.socket.nio.NioSocketChannel#doClose
disconnect稍微复杂一些, 在io.netty.channel.AbstractChannelHandlerContext#disconnect(io.netty.channel.ChannelPromise)实现中,会根据channel是否支持disconnect操作来决定下一步动作:
if (!channel().metadata().hasDisconnect()) {
next.invokeClose(promise);
} else {
next.invokeDisconnect(promise);
}
之所以这样设计,是因为TCP和UDP的disconnect含义是不一样的,对TCP来说disconnect就是关闭socket;对UDP来说,它没有连接的概念,默认情况下通过udp socket发送数据需要指定远程地址,但如果调用connect之后,就不需指定这个地址,数据报会被发送到connect指定的地址上,disconnect含义是删除connect指定的地址,发送数据时必须指定地址。所以在NIO的Channel实现中,TCP的disconnect是调用socket的close方法,UDP的disconnect是调用socket的disconnect方法,下面是两种不同的disconnect实现。
//TCP io.netty.channel.socket.nio.NioSocketChannel#doDisconnect
@Override
protected void doDisconnect() throws Exception {
doClose();
}
@Override
protected void doClose() throws Exception {
super.doClose();
javaChannel().close();
}
//UDP io.netty.channel.socket.nio.NioDatagramChannel#doDisconnect
@Override
protected void doDisconnect() throws Exception {
javaChannel().disconnect();
}
io.netty.channel.AbstractChannel.AbstractUnsafe#disconnect实现了disconnect的逻辑,先调用doDisconnect方法,这个方法是io.netty.channel.AbstractChannel定义的的抽象方法。如果channel的状态从active变成inactive,就调用pipeline的fireChannelInactive方法触发channelInactive事件。
close实现: 关闭channel
close方法的调用栈:
io.netty.channel.AbstractChannel#close()
io.netty.channel.DefaultChannelPipeline#close()
io.netty.channel.AbstractChannelHandlerContext#close()
io.netty.channel.AbstractChannelHandlerContext#close(io.netty.channel.ChannelPromise)
io.netty.channel.AbstractChannelHandlerContext#invokeClose
io.netty.channel.DefaultChannelPipeline.HeadContext#close
io.netty.channel.AbstractChannel.AbstractUnsafe#close(io.netty.channel.ChannelPromise)
io.netty.channel.AbstractChannel.AbstractUnsafe#close(final ChannelPromise promise, final Throwable cause ,final ClosedChannelException closeCause, final boolean notify)
io.netty.channel.AbstractChannel.AbstractUnsafe#doClose0
io.netty.channel.socket.nio.NioSocketChannel#doClose
close的逻辑实现在io.netty.channel.AbstractChannel.AbstractUnsafe#close(final ChannelPromise promise, final Throwable cause ,final ClosedChannelException closeCause, final boolean notify)中,这个close方法主要实现了一下几个功能:
- 确保在多线程环境下,多次调用close和一次调用的影响一致,并且可以通过promis得到同样的结果。
- 保证在执行close的过程中,不能向channel写数据。
- 调用doClose0执行执真正的close操作。
- 调用deregister对channel做最后的清理工作,并触发channelInactive, channelUnregistered事件。
以下是这个方法的代码:
private void close(final ChannelPromise promise, final Throwable cause,
final ClosedChannelException closeCause, final boolean notify) {
if (!promise.setUncancellable()) {
return;
} if (closeInitiated) {
if (closeFuture.isDone()) {
// Closed already.
safeSetSuccess(promise);
} else if (!(promise instanceof VoidChannelPromise)) { // Only needed if no VoidChannelPromise.
// This means close() was called before so we just register a listener and return
closeFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
promise.setSuccess();
}
});
}
return;
} closeInitiated = true; final boolean wasActive = isActive();
final ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
this.outboundBuffer = null; // Disallow adding any messages and flushes to outboundBuffer.
Executor closeExecutor = prepareToClose();
if (closeExecutor != null) {
closeExecutor.execute(new Runnable() {
@Override
public void run() {
try {
// Execute the close.
doClose0(promise);
} finally {
// Call invokeLater so closeAndDeregister is executed in the EventLoop again!
invokeLater(new Runnable() {
@Override
public void run() {
if (outboundBuffer != null) {
// Fail all the queued messages
outboundBuffer.failFlushed(cause, notify);
outboundBuffer.close(closeCause);
}
fireChannelInactiveAndDeregister(wasActive);
}
});
}
}
});
} else {
try {
// Close the channel and fail the queued messages in all cases.
doClose0(promise);
} finally {
if (outboundBuffer != null) {
// Fail all the queued messages.
outboundBuffer.failFlushed(cause, notify);
outboundBuffer.close(closeCause);
}
}
if (inFlush0) {
invokeLater(new Runnable() {
@Override
public void run() {
fireChannelInactiveAndDeregister(wasActive);
}
});
} else {
fireChannelInactiveAndDeregister(wasActive);
}
}
}
7-23行,在这个方法被多次调用的时候,只有一次可以执行的21行以后的代码。从代码看,这一点是用closeInitiated属性来保证的,但它是一个普通boolean类型的属性,在多线程情况下存在可见性问题。事实上一个channel unsafe实例的close方法,只会在一个线程中执行,closeInitiated只在这个方法中使用,因此不存在多线程间的可见性问题。虽然可能在多个不同的线程中多次调用Channel的close方法,但是这个close方法,只会在channel的eventLoop线程中执行。凡是通过io.netty.channel.DefaultChannelPipeline.HeadContext调用的channel unsafe方法,都一定在channel的eventLoop线程中执行。
26,27行,把channel unsafe的outboundBuffer设置为null, 这样,在close的过程中,所有channel的write方法都会通过promise返回错误。
28行,prepareToClose默认实现是返回null, 它是一个protected方法,可以根据需要覆盖它,用来在关闭之前做一些准备工作,同时指定一个executor,让接下来的关闭动作都在这个executor中执行。
33-49行,53-72行,这两段代码实现的都是功能都是一样的,不同的是33-49行在prepareToClose提供的executor中执行。调用doClose0执行关闭操作,清理outboundBuffer(43,44), 调用fireChannelInactiveAndDeregister(46)触发channelInactive和channelDeregister事件。63-72行,通过inFlush0属性检查当前是否正在进程flush操作,如果是,使用invokerLater确保在当前方法和flush操作完成之后再触发事件。
doClose0中是真正的关闭操作,它先调用doClose,然后设置promise的返回值:
//io.netty.channel.AbstractChannel.AbstractUnsafe#doClose0
private void doClose0(ChannelPromise promise) {
try {
doClose();
closeFuture.setClosed();
safeSetSuccess(promise);
} catch (Throwable t) {
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
//io.netty.channel.socket.nio.NioSocketChannel#doClose
@Override
protected void doClose() throws Exception {
super.doClose();
javaChannel().close();
}
fireChannelInactiveAndDeregister是调用deregister实现,也就是说,正常情况下,调用Channel的close方法之后就会自动完成一个channel最后的清理工作,不需要再调用deregister方法。
private void fireChannelInactiveAndDeregister(final boolean wasActive) {
deregister(voidPromise(), wasActive && !isActive());
}
deregister实现:从eventLoop中注销channel
deregister的调用栈:
io.netty.channel.AbstractChannel#deregister()
io.netty.channel.DefaultChannelPipeline#deregister()
io.netty.channel.AbstractChannelHandlerContext#deregister()
io.netty.channel.AbstractChannelHandlerContext#deregister(io.netty.channel.ChannelPromise)
io.netty.channel.AbstractChannelHandlerContext#invokeDeregister
io.netty.channel.DefaultChannelPipeline.HeadContext#deregister
io.netty.channel.AbstractChannel.AbstractUnsafe#deregister(io.netty.channel.ChannelPromise)
io.netty.channel.AbstractChannel.AbstractUnsafe#deregister(io.netty.channel.ChannelPromise, boolean)
io.netty.channel.nio.AbstractNioChannel#doDeregister
deregister的逻辑在中实现io.netty.channel.AbstractChannel.AbstractUnsafe#deregister(final ChannelPromise promise, final boolean fireChannelInactive),这个方法的实现比较简单,主要就是调用doDeregister方法执行deregister操作,然后触发channelInactive事件(如果fireChannelInactive参数是true)和channelUnregistered事件。
private void deregister(final ChannelPromise promise, final boolean fireChannelInactive) {
if (!promise.setUncancellable()) {
return;
}
if (!registered) {
safeSetSuccess(promise);
return;
}
invokeLater(new Runnable() {
@Override
public void run() {
try {
doDeregister();
} catch (Throwable t) {
logger.warn("Unexpected exception occurred while deregistering a channel.", t);
} finally {
if (fireChannelInactive) {
pipeline.fireChannelInactive();
}
if (registered) {
registered = false;
pipeline.fireChannelUnregistered();
}
safeSetSuccess(promise);
}
}
});
}
这里使用invokeLater执行主要逻辑的目的是为了保证把当前正在eventLoop队列中所有任何都执行完之后再执行真正的deregister操作。
doDeregister默认实现是空,什么都没做,它是个protected方法。真正的实现在io.netty.channel.nio.AbstractNioChannel中,它只是简单地调用eventLoop的cancel方法把SocketChannel对应的SelectionKey从Selector中删除,这样selector就不会监听到这个socket上的任何事件了。
@Override
protected void doDeregister() throws Exception {
eventLoop().cancel(selectionKey());
}
netty源码解解析(4.0)-13 Channel NIO实现: 关闭和清理的更多相关文章
- netty源码解解析(4.0)-11 Channel NIO实现-概览
结构设计 Channel的NIO实现位于io.netty.channel.nio包和io.netty.channel.socket.nio包中,其中io.netty.channel.nio是抽象实 ...
- 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方法调用成功之后才 ...
- netty源码解解析(4.0)-15 Channel NIO实现:写数据
写数据是NIO Channel实现的另一个比较复杂的功能.每一个channel都有一个outboundBuffer,这是一个输出缓冲区.当调用channel的write方法写数据时,这个数据被一系列C ...
- netty源码解解析(4.0)-3 Channel的抽象实现
AbstractChannel和AbstractUnsafe抽象类 io.netty.channel.AbstractChannel 从本章开始,会有大量的篇幅涉及到代码分析.为了能够清晰简洁的地说明 ...
- netty源码解解析(4.0)-17 ChannelHandler: IdleStateHandler实现
io.netty.handler.timeout.IdleStateHandler功能是监测Channel上read, write或者这两者的空闲状态.当Channel超过了指定的空闲时间时,这个Ha ...
- netty源码解解析(4.0)-18 ChannelHandler: codec--编解码框架
编解码框架和一些常用的实现位于io.netty.handler.codec包中. 编解码框架包含两部分:Byte流和特定类型数据之间的编解码,也叫序列化和反序列化.不类型数据之间的转换. 下图是编解码 ...
- netty源码解解析(4.0)-10 ChannelPipleline的默认实现--事件传递及处理
事件触发.传递.处理是DefaultChannelPipleline实现的另一个核心能力.在前面在章节中粗略地讲过了事件的处理流程,本章将会详细地分析其中的所有关键细节.这些关键点包括: 事件触发接口 ...
- netty源码解解析(4.0)-20 ChannelHandler: 自己实现一个自定义协议的服务器和客户端
本章不会直接分析Netty源码,而是通过使用Netty的能力实现一个自定义协议的服务器和客户端.通过这样的实践,可以更深刻地理解Netty的相关代码,同时可以了解,在设计实现自定义协议的过程中需要解决 ...
随机推荐
- docker日志设置定期清理
1.新建/etc/docker/daemon.json,若有就不用新建了 2.添加log-dirver和log-opts参数,样例如下 "log-driver":"jso ...
- OpenCV图像分割2
1.GrubCut算法 2.K-means聚类算法 3.分水岭算法
- 第二节——vue多页面开发
我们平常用vue开发的时候总觉得vue好像就是专门为了单页面应用而诞生的,其实不是.因为vue在工程化开发的时候很依赖webpack,而webpack是将所有的资源整合到一块,弄成一个单页面. 但是v ...
- 可遇不可求的Question之MySQL系统变量interactive_timeout 与 wait_timeout 篇
mysql>show variables like '%timeout'; 打印结果如下: +----------------------------+-------+ | Variable_n ...
- 【python-strip】Python strip()方法
strip()方法用于移除字符串首尾的指定字符(默认是空格) 比如: str = "0000000this is string example....wow!!!0000000"; ...
- Vuejs——(12)组件——动态组件
版权声明:出处http://blog.csdn.net/qq20004604 目录(?)[+] 本篇资料来于官方文档: http://cn.vuejs.org/guide/components ...
- Python之旅Day1 数据类型初识(数字|字符串|列表|数据运算) 编码 表达式(if...else|for|while)
初识PYTHON Python(英国发音:/ˈpaɪθən/ 美国发音:/ˈpaɪθɑːn/), 是一种面向对象的解释型计算机程序设计语言,由荷兰人Guido van Rossum(吉多·范罗苏姆)于 ...
- utf-8转gb2312
近日在对一个json串进行转码时,显示中文乱码,原因是json串编码方式为utf-8,而我程序在windows上采用的是多字节编码方式,即采用gb2312编码.这里就存在一个utf-8到gb2312的 ...
- GC垃圾回收机制,iOS内存管理。
问题: MRC中通过调用静态方法创建的新对象,不再使用时需要对其发送release消息吗? 不需要,因为约定静态方法创建的对象会自动将其放入自动释放池,即已对其发送autorelease消息,因此不可 ...
- H5的Page Visibility API
概述 哈哈,又学了一个H5的API.今天突然对动态获取网页的选中状态很感兴趣,然后去查了下,发现真的有个API控制它--Page Visibility API.于是把学到的东西记录下来,供以后开发时参 ...