关闭操作,可能是客户端/服务端主动关闭,也可能是异常关闭。
Netty NIO Channel的close操作分成客户端和服务端Channel两种关闭。
NioSocketChannel的close() 方法,应用程序里可以主动关闭 NioSocketChannel 通道:
// AbstractChannel.java
@Override
public ChannelFuture close() {
return pipeline.close();
}
NioSocketChannel 继承 AbstractChannel 抽象类,那么close() 方法实际是 AbstractChannel 实现的。
在方法内部,会调用对应的 ChannelPipeline的close() 方法,将 close 事件在 pipeline 上传播。而 close 事件属于 Outbound 事件(之前文章有提回去看看呗),所以会从 tail 节点开始,最终传播到 head 节点,使用 Unsafe 进行关闭:
// DefaultChannelPipeline.java
@Override
public final ChannelFuture close() {
return tail.close();
}
// TailContext.java
@Override // FROM AbstractChannelHandlerContext.java 。因为 TailContext 继承 AbstractChannelHandlerContext 抽象类,该方法是它实现的。
public ChannelFuture close() {
return close(newPromise());
}
// HeadContext.java
@Override
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
unsafe.close(promise);
}
AbstractUnsafe的close() 方法,关闭 Channel:
@Override
public final void close(final ChannelPromise promise) {
assertEventLoop();
// 关闭
close(promise, CLOSE_CLOSED_CHANNEL_EXCEPTION, CLOSE_CLOSED_CHANNEL_EXCEPTION, false);
}
private void close(final ChannelPromise promise, final Throwable cause, final ClosedChannelException closeCause, final boolean notify) {
// 设置 Promise 不可取消
if (!promise.setUncancellable()) {
return;
}
// 若 关闭已经标记初始化,此时可能已经关闭完成
if (closeInitiated) {
// 关闭已经完成,直接通知 Promise 对象
if (closeFuture.isDone()) {
// Closed already.
safeSetSuccess(promise);
// 关闭未完成,通过监听器通知 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;
// 获得 Channel 是否激活
final boolean wasActive = isActive();
// 标记 outboundBuffer 为空
final ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
this.outboundBuffer = null; // Disallow adding any messages and flushes to outboundBuffer.
// 执行准备关闭
Executor closeExecutor = prepareToClose();
// 若 closeExecutor 非空
if (closeExecutor != null) {
closeExecutor.execute(new Runnable() {
@Override
public void run() {
try {
// 在 closeExecutor 中,执行关闭
// Execute the close.
doClose0(promise);
} finally {
// 在 EventLoop 中,执行
// Call invokeLater so closeAndDeregister is executed in the EventLoop again!
invokeLater(new Runnable() {
@Override
public void run() {
if (outboundBuffer != null) {
// 写入数据( 消息 )到对端失败,通知相应数据对应的 Promise 失败。
// Fail all the queued messages
outboundBuffer.failFlushed(cause, notify);
// 关闭内存队列
outboundBuffer.close(closeCause);
}
// 执行取消注册,并触发 Channel Inactive 事件到 pipeline 中
fireChannelInactiveAndDeregister(wasActive);
}
});
}
}
});
// 若 closeExecutor 为空
} else {
try {
// 执行关闭
// Close the channel and fail the queued messages in all cases.
doClose0(promise);
} finally {
if (outboundBuffer != null) {
// 写入数据( 消息 )到对端失败,通知相应数据对应的 Promise 失败。
// Fail all the queued messages.
outboundBuffer.failFlushed(cause, notify);
// 关闭内存队列
outboundBuffer.close(closeCause);
}
}
// 正在 flush 中,在 EventLoop 中执行执行取消注册,并触发 Channel Inactive 事件到 pipeline 中
if (inFlush0) {
invokeLater(new Runnable() {
@Override
public void run() {
fireChannelInactiveAndDeregister(wasActive);
}
});
// 不在 flush 中,直接执行执行取消注册,并触发 Channel Inactive 事件到 pipeline 中
} else {
fireChannelInactiveAndDeregister(wasActive);
}
}
}
方法参数 cause、closeCause ,关闭的“原因”。对于 close 操作来说,无论是正常关闭,还是异常关闭,都通过使用 Exception 来表示来源。在 AbstractChannel 类中,枚举了所有来源:
// AbstractChannel.java
private static final ClosedChannelException FLUSH0_CLOSED_CHANNEL_EXCEPTION = ThrowableUtil.unknownStackTrace(
new ClosedChannelException(), AbstractUnsafe.class, "flush0()");
private static final ClosedChannelException ENSURE_OPEN_CLOSED_CHANNEL_EXCEPTION = ThrowableUtil.unknownStackTrace(
new ClosedChannelException(), AbstractUnsafe.class, "ensureOpen(...)");
private static final ClosedChannelException CLOSE_CLOSED_CHANNEL_EXCEPTION = ThrowableUtil.unknownStackTrace(
new ClosedChannelException(), AbstractUnsafe.class, "close(...)");
private static final ClosedChannelException WRITE_CLOSED_CHANNEL_EXCEPTION = ThrowableUtil.unknownStackTrace(
new ClosedChannelException(), AbstractUnsafe.class, "write(...)");
private static final NotYetConnectedException FLUSH0_NOT_YET_CONNECTED_EXCEPTION = ThrowableUtil.unknownStackTrace(
new NotYetConnectedException(), AbstractUnsafe.class, "flush0()");
回到AbstractUnsafe的close中调用prepareToClose() 方法,执行准备关闭。
@Override
protected Executor prepareToClose() {
try {
if (javaChannel().isOpen() && config().getSoLinger() > 0) {
// We need to cancel this key of the channel so we may not end up in a eventloop spin
// because we try to read or write until the actual close happens which may be later due
// SO_LINGER handling.
// See https://github.com/netty/netty/issues/4449
doDeregister();
// 返回 GlobalEventExecutor 对象
return GlobalEventExecutor.INSTANCE;
}
} catch (Throwable ignore) {
// Ignore the error as the underlying channel may be closed in the meantime and so
// getSoLinger() may produce an exception. In this case we just return null.
// See https://github.com/netty/netty/issues/4449
}
return null;
}
上文配置StandardSocketOptions.SO_LINGER 大于 0。
(Socket 参数,关闭 Socket 的延迟时间,Netty 默认值为 -1 ,表示禁用该功能。
所以我们知道,如果大于0,在真正关闭Channel,需要阻塞直到延迟时间到或发送缓冲区中的数据发送完毕。
如果在 EventLoop 中执行真正关闭 Channel 的操作,则会阻塞 EventLoop 的线程。所以上面有返回 GlobalEventExecutor.INSTANCE 对象,作为执行真正关闭 Channel 的操作的执行器,他有自己的线程。
调用doDeregister()方法的原因是,SO_LINGER 大于 0 时,真正关闭 Channel ,需要阻塞直到延迟时间到或发送缓冲区中的数据发送完毕。
如果不取消该 Channel 的 SelectionKey.OP_READ 事件的感兴趣,就会不断触发读事件,导致 CPU 空轮询。
在 Channel 关闭时,会自动触发 SelectionKey.OP_READ 事件。而且,会不断不断不断的触发,如果不进行取消 SelectionKey.OP_READ 事件的感兴趣,那么就…hh
AbstractUnsafe的doDeregister() 方法,执行取消注册:
@Override
protected void doDeregister() throws Exception {
eventLoop().cancel(selectionKey());
}
调用cancel(SelectionKey key) 方法,取消 SelectionKey 。
AbstractUnsafe的doClose0(ChannelPromise promise) 方法,执行真正的关闭:
private void doClose0(ChannelPromise promise) {
try {
// 执行关闭
doClose();
// 通知 closeFuture 关闭完成
closeFuture.setClosed();
// 通知 Promise 关闭成功
safeSetSuccess(promise);
} catch (Throwable t) {
// 通知 closeFuture 关闭完成
closeFuture.setClosed();
// 通知 Promise 关闭异常
safeSetFailure(promise, t);
}
}
NioSocketChannel的doClose() 方法,执行 Java 原生 NIO SocketChannel 关闭:
@Override
protected void doClose() throws Exception {
// 执行父类关闭方法
super.doClose();
// 执行 Java 原生 NIO SocketChannel 关闭
javaChannel().close();
}
AbstractNioChannel中doClose() 方法,执行父类关闭方法:
@Override
protected void doClose() throws Exception {
// 通知 connectPromise 异常失败
ChannelPromise promise = connectPromise;
if (promise != null) {
// Use tryFailure() instead of setFailure() to avoid the race against cancel().
promise.tryFailure(DO_CLOSE_CLOSED_CHANNEL_EXCEPTION);
connectPromise = null;
}
// 取消 connectTimeoutFuture 等待
ScheduledFuture<?> future = connectTimeoutFuture;
if (future != null) {
future.cancel(false);
connectTimeoutFuture = null;
}
}
AbstractUnsafe中fireChannelInactiveAndDeregister(boolean wasActive) 方法,执行取消注册,并触发 Channel Inactive 事件到 pipeline 中:
private void fireChannelInactiveAndDeregister(final boolean wasActive) {
deregister(voidPromise() , wasActive && !isActive() );
}
private void deregister(final ChannelPromise promise, final boolean fireChannelInactive) {
// 设置 Promise 不可取消
if (!promise.setUncancellable()) {
return;
}
// 不处于已经注册状态,直接通知 Promise 取消注册成功。
if (!registered) {
safeSetSuccess(promise);
return;
}
// As a user may call deregister() from within any method while doing processing in the ChannelPipeline,
// we need to ensure we do the actual deregister operation later. This is needed as for example,
// we may be in the ByteToMessageDecoder.callDecode(...) method and so still try to do processing in
// the old EventLoop while the user already registered the Channel to a new EventLoop. Without delay,
// the deregister operation this could lead to have a handler invoked by different EventLoop and so
// threads.
//
// See:
// https://github.com/netty/netty/issues/4435
invokeLater(new Runnable() {
@Override
public void run() {
try {
// 执行取消注册
doDeregister();
} catch (Throwable t) {
logger.warn("Unexpected exception occurred while deregistering a channel.", t);
} finally {
// 触发 Channel Inactive 事件到 pipeline 中
if (fireChannelInactive) {
pipeline.fireChannelInactive();
}
// Some transports like local and AIO does not allow the deregistration of
// an open channel. Their doDeregister() calls close(). Consequently,
// close() calls deregister() again - no need to fire channelUnregistered, so check
// if it was registered.
if (registered) {
// 标记为未注册
registered = false;
// 触发 Channel Unregistered 事件到 pipeline 中
pipeline.fireChannelUnregistered();
}
// 通知 Promise 取消注册成功。
safeSetSuccess(promise);
}
}
});
}
嘿嘿,close()方法们的实现都很相似,所以在NioSocketChannel 和 NioServerSocketChannel 差异的关闭逻辑实现是通过给他们配置不同的 ChannelHandler 实现类。
在 Unsafe 接口上定义了 closeForcibly() 方法,立即关闭 Channel ,并且不触发 pipeline 上的任何事件。(仅仅用于 Channel 注册到 EventLoop 上失败的情况下)
/**
* Closes the {@link Channel} immediately without firing any events. Probably only useful
* when registration attempt failed.
*/
void closeForcibly();
AbstractUnsafe 对该接口方法,实现代码如下:
@Override
public final void closeForcibly() {
assertEventLoop();
try {
doClose();
} catch (Exception e) {
logger.warn("Failed to close a channel.", e);
}
}
服务端处理客户端主动关闭连接:
在客户端主动关闭时,服务端会收到一个 SelectionKey.OP_READ 事件的就绪,在调用客户端对应在服务端的 SocketChannel 的 read() 方法会返回 -1 ,从而实现在服务端关闭客户端的逻辑。在 Netty 的实现,在 NioByteUnsafe的read() 方法中。
调用closeOnRead(ChannelPipeline pipeline) 方法,关闭客户端的连接:
private void closeOnRead(ChannelPipeline pipeline) {
if (!isInputShutdown0()) {
// 开启连接半关闭
if (isAllowHalfClosure(config())) {
// 关闭 Channel 数据的读取
shutdownInput();
// 触发 ChannelInputShutdownEvent.INSTANCE 事件到 pipeline 中
pipeline.fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE);
} else {
close(voidPromise());
}
} else {
// 标记 inputClosedSeenErrorOnRead 为 true
inputClosedSeenErrorOnRead = true;
// 触发 ChannelInputShutdownEvent.INSTANCE 事件到 pipeline 中
pipeline.fireUserEventTriggered(ChannelInputShutdownReadComplete.INSTANCE);
}
}
调用 NioSocketChannel中isInputShutdown0() 方法,判断是否关闭 Channel 数据的读取:
// NioSocketChannel.java
@Override
protected boolean isInputShutdown0() {
return isInputShutdown();
}
@Override
public boolean isInputShutdown() {
return javaChannel().socket().isInputShutdown() || !isActive();
}
// java.net.Socket.java
private boolean shutIn = false;
/**
* Returns whether the read-half of the socket connection is closed.
*
* @return true if the input of the socket has been shutdown
* @since 1.4
* @see #shutdownInput
*/
public boolean isInputShutdown() {
return shutIn;
}
isAllowHalfClosure()方法,判断是否开启连接半关闭的功能:
// AbstractNioByteChannel.java
private static boolean isAllowHalfClosure(ChannelConfig config) {
return config instanceof SocketChannelConfig &&
((SocketChannelConfig) config).isAllowHalfClosure();
}
可通过 ALLOW_HALF_CLOSURE 配置项开启。Netty 参数,一个连接的远端关闭时本地端是否关闭,默认值为 false 。
调用 NioSocketChannel的shutdownInput() 方法,关闭 Channel 数据的读取:
@Override
public ChannelFuture shutdownInput() {
return shutdownInput(newPromise());
}
@Override
public ChannelFuture shutdownInput(final ChannelPromise promise) {
EventLoop loop = eventLoop();
if (loop.inEventLoop()) {
shutdownInput0(promise);
} else {
loop.execute(new Runnable() {
@Override
public void run() {
shutdownInput0(promise);
}
});
}
return promise;
}
private void shutdownInput0(final ChannelPromise promise) {
try {
// 关闭 Channel 数据的读取
shutdownInput0();
// 通知 Promise 成功
promise.setSuccess();
} catch (Throwable t) {
// 通知 Promise 失败
promise.setFailure(t);
}
}
private void shutdownInput0() throws Exception {
// 调用 Java NIO Channel 的 shutdownInput 方法
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().shutdownInput();
} else {
javaChannel().socket().shutdownInput();
}
}
在标记 inputClosedSeenErrorOnRead = true 后,在 NioByteUnsafe中read() 方法中,会主动对 SelectionKey.OP_READ 的感兴趣,避免空轮询。上面用提到哦~
// AbstractNioByteUnsafe.java
public final void read() {
final ChannelConfig config = config();
// 若 inputClosedSeenErrorOnRead = true ,移除对 SelectionKey.OP_READ 事件的感兴趣。
if (shouldBreakReadReady(config)) {
clearReadPending(); // 移除对 SelectionKey.OP_READ 事件的感兴趣
return;
}
}
// AbstractNioByteChannel.java
final boolean shouldBreakReadReady(ChannelConfig config) {
return isInputShutdown0() && (inputClosedSeenErrorOnRead || !isAllowHalfClosure(config));
}
差不多就这样啦。