标签:pip 触发事件 bool ann 数据 客户 了解 input out
Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html)
关闭连接分两种:主动关闭(正常关闭)和被动关闭(异常关闭)。
(1)关闭连接本质
一句话概括:关闭连接的本质是取消 Channel 在 Selelctor 的注册。
(2)要点
补充1:NIO 中,如果一个客户端进程退出,为什么会触发服务器的 OP_READ 事件?
epoll 触发一个对断关闭然后在 jvm 层被包装成了一个读事件。因为 epoll 收到退出事件的时候要触发一个读操作,读到 -1 认为退出,所以 java 从实际操作角度认为 epoll 的退出事件也是读。所以简化了 java 层处理的事件数。但这个时候用 channel.read() 方法读的时候,会报 java.io.IOException: 远程主机强迫关闭了一个现有的连接。如果是主动关闭可以在触发读事件第一件事是判断是否有效,比如先读一个字节 看看是不是 -1,如果是 -1 就停止。 如果异常是 reset by peer,则表示被动关闭,一个流氓方法是所有和链接相关的异常都 catch,然后关闭这个链接,没有更好的做法了,Netty 自己也是这样做的。
转载自《关于netty你需要了解的二三事》:https://cloud.tencent.com/developer/article/1452395
连接关闭是会触发 OP_READ 事件,无论是正常还是异常关闭,都会调用 closeOnRead 关闭连接,最终调用 unsafe.close 关闭连接。
AbstractNioByteChannel.NioByteUnsafe#read
-> closeOnRead
-> AbstractUnsafe#close
-> handleReadException
-> closeOnRead
在前面分析 AbstractNioByteChannel.NioByteUnsafe#read 时,我们忽略了异常的处理。现在回过头再看一下代码:
(1)NioByteUnsafe#read
try {
do {
byteBuf = allocHandle.allocate(allocator);
allocHandle.lastBytesRead(doReadBytes(byteBuf));
if (allocHandle.lastBytesRead() <= 0) {
byteBuf.release();
byteBuf = null;
// 1. 正常关闭,返回 -1
close = allocHandle.lastBytesRead() < 0;
if (close) {
readPending = false;
}
break;
}
} while (allocHandle.continueReading());
if (close) {
closeOnRead(pipeline);
}
} catch (Throwable t) {
// 2. 如果异常是IOException,也需要关闭
handleReadException(pipeline, byteBuf, t, close, allocHandle);
}
说明: 无论是正常关闭(allocHandle.lastBytesRead() = -1)还是异常关闭(IOException),都会调用 closeOnRead 关闭连接。
(2)closeOnRead
closeOnRead 方法调用 close 关闭连接。
private void closeOnRead(ChannelPipeline pipeline) {
if (!isInputShutdown0()) {
if (isAllowHalfClosure(config())) {
// 特殊需求
shutdownInput();
pipeline.fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE);
} else {
// 基本上都是调用 close 方法关闭连接
close(voidPromise());
}
} else {
inputClosedSeenErrorOnRead = true;
pipeline.fireUserEventTriggered(ChannelInputShutdownReadComplete.INSTANCE);
}
}
unsafe.close 关闭连接,最终调用 NioSocketChannel#doClose(javaChannel.close) 或 NioEventLoop#cancel(key.cancel) 关闭连接,本质都会调用到 SelectionKey#cancel 取消注册。unsafe.close 做了如下工作:
AbstractChannel.AbstractUnsafe#close
-> prepareToClose
-> doClose0
-> NioSocketChannel#doClose # √ javaChannel.close
-> fireChannelInactiveAndDeregister
-> deregister
-> AbstractNioChannel#doDeregister
-> NioEventLoop#cancel # √ key.cancel()
-> pipeline#fireChannelInactive
-> pipeline#fireChannelUnregistered
(1)close
private void close(final ChannelPromise promise, final Throwable cause,
final ClosedChannelException closeCause, final boolean notify) {
final boolean wasActive = isActive();
this.outboundBuffer = null; // 清理资源,不允许再写数据到缓冲区
Executor closeExecutor = prepareToClose(); // 1. 预关闭,设置soLinger后会阻塞关闭连接
doClose0(promise); // 2. 真正关闭连接
fireChannelInactiveAndDeregister(wasActive); // 3. 调用deregister,清理资源并触发事件
}
private void deregister(final ChannelPromise promise, final boolean fireChannelInactive) {
try {
doDeregister(); // 4. 调用eventloop.cancel
} catch (Throwable t) {
} finally {
pipeline.fireChannelInactive(); // 5. 触发channelInactive
pipeline.fireChannelUnregistered(); // 6. 触发dhannelUnregistered
}
}
(2)prepareToClose
prepareToClose 返回了一个线程用来单独执行关闭任务,因为开启 soLinger 后,关闭连接是阻塞的,需要异步关闭连接。NioSocketChannelUnsafe 中的实现如下:
@Override
protected Executor prepareToClose() {
// 配置soLinger后会阻塞关闭连接,返回一个默认的连接池执行关闭任务
if (javaChannel().isOpen() && config().getSoLinger() > 0) {
doDeregister();
return GlobalEventExecutor.INSTANCE;
}
return null;
}
(3)doClose
@Override
protected void doClose() throws Exception {
super.doClose();
javaChannel().close(); // 核心
}
(4)doDeregister
// AbstractNioChannel
@Override
protected void doDeregister() throws Exception {
eventLoop().cancel(selectionKey());
}
void cancel(SelectionKey key) {
key.cancel(); // 核心
cancelledKeys ++;
if (cancelledKeys >= CLEANUP_INTERVAL) {
cancelledKeys = 0;
needsToSelectAgain = true;
}
}
每天用心记录一点点。内容也许不重要,但习惯很重要!
关闭连接:本质是取消 Channel 在 Selelctor 的注册
标签:pip 触发事件 bool ann 数据 客户 了解 input out
原文地址:https://www.cnblogs.com/binarylei/p/12643807.html