我们从之前的篇文章已经学习到了服务器端在NIOEventLoop中是如何处理io事件的,我们本讲的开始也是这个地方,就是当客户端新连接接入后具体的执行流程,好话不多少我们先粘贴出我们代码分析的上下文NioEventLoop的run方法体的processSelectedKey()方法,这是我们本章故事的起点,就是当bossgroupEventLoop监听到OP_ACCEPT事件后的一系列步骤。
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
if (!k.isValid()) {
final EventLoop eventLoop;
try {
eventLoop = ch.eventLoop();
} catch (Throwable ignored) {
// If the channel implementation throws an exception because there is no event loop, we ignore this
// because we are only trying to determine if ch is registered to this event loop and thus has authority
// to close ch.
return;
}
// Only close ch if ch is still registerd to this EventLoop. ch could have deregistered from the event loop
// and thus the SelectionKey could be cancelled as part of the deregistration process, but the channel is
// still healthy and should not be closed.
// See https://github.com/netty/netty/issues/5125
if (eventLoop != this || eventLoop == null) {
return;
}
// close the channel if the key is not valid anymore
unsafe.close(unsafe.voidPromise());
return;
}
try {
int readyOps = k.readyOps();
// We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise
// the NIO JDK channel implementation may throw a NotYetConnectedException.
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
// remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
// See https://github.com/netty/netty/issues/924
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
// Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
// Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
ch.unsafe().forceFlush();
}
// Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
// to a spin loop
//这是我们本章故事的起点,就是当bossgroupEventLoop监听到OP_ACCEPT事件后。
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
if (!ch.isOpen()) {
// Connection already closed - no need to handle write.
return;
}
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
目录
再开始分析之前我们有一个前置条件一定需要讲清楚的是unsafe这个成员变量到底是干什么的,为什么哪都有他,他的作用是什么,接下来我们先看channel的分类,看完这部分之后,我们自然而然就知道unsafe是什么鬼了。
Channel的分类
我们看到,实际上channel下分了两条分支,一条为NioSocketChannel 另一条为NioServerSocketChannel ,换一种说法就是一条是服务端对应的channel,另外一条是客户端对应的channel。
而NioByteUnsafe 和NioMessageUnsafe则作为成员变量介入其中,我们简单分析一下其作用。我们通过下图可以看到,基本上都是对io相关操作的封装,我们这可以进一步理解为对不同io协议的封装,比如我们的NioMessageUnsafe和NioMessageUnsafe 就是分别对客户端和服务端io操作的nio实现的封装协议。我们相关的io操作都是通过他们来实现的。
channel的集成关系参见T
NioSocketChannel的创建
由上面对 unsafe的分析我们知道这会调用服务端的unsafe的read方法,即NioMessageUnsafe的read方法,进入read方法,进入doReadMessages(readBuf)方法,此处是我们重点分析的对象,readBuf为List<Object> readBuf一个Object类型的数组,进入该方法后我们分别可以看到该方法做了如下操作,调用jdk的accept方法创建客户端channel,创建NioSocketChannel并将服务端channel的对象和ch客户端新生成的对象作为参数传递进去,到此为止,我们来到了本小节的核心内容,NioSocketChannel的创建过程.大体逻辑和NioServerSocketChannel类似,故我们不做深入分析,读者参见如下代码即可,已经添加了相关注释。
//由上面对 unsafe的分析我们知道这会调用服务端的unsafe的read方法,即NioMessageUnsafe的read方法
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
if (!ch.isOpen()) {
// Connection already closed - no need to handle write.
return;
}
}
public void read() {
...
try {
do {
//此处是我们重点分析的对象,readBuf为List<Object> readBuf一个Object类型的数组
int localRead = doReadMessages(readBuf);
if (localRead == 0) {
break;
}
if (localRead < 0) {
closed = true;
break;
}
allocHandle.incMessagesRead(localRead);
} while (allocHandle.continueReading());
} catch (Throwable t) {
exception = t;
}
}
protected int doReadMessages(List<Object> buf) throws Exception {
//这调用jdk的accept方法创建客户端channel
SocketChannel ch = javaChannel().accept();
try {
if (ch != null) {
//创建NioSocketChannel并将服务端channel的对象和ch客户端新生成的对象作为参数传递进去,到此为止,我们来到了本小节的核心内容,NioSocketChannel的创建过程
buf.add(new NioSocketChannel(this, ch));
return 1;
}
} catch (Throwable t) {
logger.warn("Failed to create a new channel from an accepted socket.", t);
try {
ch.close();
} catch (Throwable t2) {
logger.warn("Failed to close a socket.", t2);
}
}
return 0;
}
//入口函数 ,大体逻辑和NioServerSocketChannel类似,故我们不做深入分析
public NioSocketChannel(Channel parent, SocketChannel socket) {
//进入该方法
super(parent, socket);
config = new NioSocketChannelConfig(this, socket.socket());
}
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
//将设置感兴趣的事件为读事件
super(parent, ch, SelectionKey.OP_READ);
}
//简单的设置成员变量并设置为非阻塞模式
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);
this.ch = ch;
this.readInterestOp = readInterestOp;
try {
ch.configureBlocking(false);
} catch (IOException e) {
try {
ch.close();
} catch (IOException e2) {
if (logger.isWarnEnabled()) {
logger.warn(
"Failed to close a partially initialized socket.", e2);
}
}
throw new ChannelException("Failed to enter non-blocking mode.", e);
}
}
//创建相关成员变量
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
//回到入口函数,进入NioSocketChannelConfig方法
public DefaultSocketChannelConfig(SocketChannel channel, Socket javaSocket) {
super(channel);
if (javaSocket == null) {
throw new NullPointerException("javaSocket");
}
//保存jdk底层的socket
this.javaSocket = javaSocket;
//设置为socket
// Enable TCP_NODELAY by default if possible.
if (PlatformDependent.canEnableTcpNoDelayByDefault()) {
try {
setTcpNoDelay(true);
} catch (Exception e) {
// Ignore.
}
}
}
新连接NioEventLoop的分配和selector注册
分析完NioSocketChannel的创建后我们在回到NioMessageUnsafe的read()方法,在接受到请求之后我们会调用事件传播机制,通知相关的handler进行该事件的处理,这里我们注意到我们将NioSocketChannel也在事件中进行传播了。这里我们先略过netty的事件传播机制跳过这一部分我们直接进行断点调试,看看到底是哪个handler进行的处理,并且做了什么处理。下章节我们会重点讲一下netty的事件传播机制。最终事件到ServerBootstraphandler中进行处理,分别作了如下操作:1 将我们用户代码的childHandler放入socketchannel的pipeline中,2 参数的相关设置,3 属性的相关设置,4 childGroup.register(child)这里就是我们本节要分析的重点了,我们看到会调用childGroup.register(child)将客户端channel进行注册.
代码跟进MultithreadEventLoopGroup的register方法,next方法会调用choser对象获取一个eventLoop,然后调用eventLoop的register方法进行注册,最终我们会调用channel().unsafe().register(this, promise)方法进行channel的注册操作,我们最终来到了 register0(promise)方法。
最终调用jdk底层的代码将channel注册到与之绑定的eventLoop的 selector上面,但在这里我们注意到一个问题这在注册的时候好像没有关心任何事件,我们知道客户端的channel的最主要的作用就是读事件了,那这个读事件又是在哪里进行的呢?这也是我们下一小节重点分析的内容,到此为止,我们客户端新连接接入的channel就注册到了workereventLoopGroup的某一个eventLoop得selector上面去了。代码如下:
public void read() {
...
try {
try {
do {
int localRead = doReadMessages(readBuf);
if (localRead == 0) {
break;
}
if (localRead < 0) {
closed = true;
break;
}
allocHandle.incMessagesRead(localRead);
} while (allocHandle.continueReading());
} catch (Throwable t) {
exception = t;
}
int size = readBuf.size();
//在接受到请求之后我们会调用事件传播机制,通知相关的handler进行该事件的处理,这里我们注意到我们将NioSocketChannel也在事件中进行传播了
for (int i = 0; i < size; i ++) {
readPending = false;
pipeline.fireChannelRead(readBuf.get(i));
}
readBuf.clear();
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
if (exception != null) {
closed = closeOnReadError(exception);
pipeline.fireExceptionCaught(exception);
}
if (closed) {
inputShutdown = true;
if (isOpen()) {
close(voidPromise());
}
}
} finally {
...
}
}
}
}
//ChannelHandlerContext(DefaultChannelPipeline$HeadContext#0的read事件传播,只是简单的进行事件的向下传递
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ctx.fireChannelRead(msg);
}
//最终事件到ServerBootstraphandler中进行处理
public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg;
//将我们用户代码的childHandler放入socketchannel的pipeline中
child.pipeline().addLast(childHandler);
//参数的相关设置
for (Entry<ChannelOption<?>, Object> e: childOptions) {
try {
if (!child.config().setOption((ChannelOption<Object>) e.getKey(), e.getValue())) {
logger.warn("Unknown channel option: " + e);
}
} catch (Throwable t) {
logger.warn("Failed to set a channel option: " + child, t);
}
}
//属性的相关设置
for (Entry<AttributeKey<?>, Object> e: childAttrs) {
child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
}
//childGroup.register(child)这里就是我们本节要分析的重点了,我们看到会调用childGroup.register(child)将客户端channel进行注册
try {
childGroup.register(child).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
forceClose(child, future.cause());
}
}
});
} catch (Throwable t) {
forceClose(child, t);
}
}
//代码跟进MultithreadEventLoopGroup的register方法,next方法会调用choser对象获取一个eventLoop,然后调用eventLoop的register方法进行注册
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
//最终我们会调用channel().unsafe().register(this, promise)方法进行channel的注册操作
public ChannelFuture register(final ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
promise.channel().unsafe().register(this, promise);
return promise;
}
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
...
//这里绑定将channel和eventLoop进行绑定
AbstractChannel.this.eventLoop = eventLoop;
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
//由于现在在服务端的eventLoop线程中,故执行该方法,execute方法我们前面的文章中已经分析过了,这里我们就略过他直接看 register0(promise)方法
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
logger.warn(
"Force-closing a channel whose registration task was not accepted by an event loop: {}",
AbstractChannel.this, t);
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
}
//最终调用jdk底层的代码将channel注册到与之绑定的eventLoop的 selector上面,但在这里我们注意到一个问题这在注册的时候好像没有关心任何事件,我们知道客户端的channel的最主要的作用就是读事件了,那这个读事件又是在哪里进行的那,这也是我们下一小节重点分析的内容,到此为止,我们客户端新连接接入的channel就注册到了workereventLoopGroup的某一个eventLoop得selector上面去了
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
selectionKey = javaChannel().register(eventLoop().selector, 0, this);
return;
} catch (CancelledKeyException e) {
if (!selected) {
// Force the Selector to select now as the "canceled" SelectionKey may still be
// cached and not removed because no Select.select(..) operation was called yet.
eventLoop().selectNow();
selected = true;
} else {
// We forced a select operation on the selector before but the SelectionKey is still cached
// for whatever reason. JDK bug ?
throw e;
}
}
}
}
NioSocketChannel读事件的注册
private void register0(ChannelPromise promise) {
try {
...
doRegister();
...
//到此为止我们已经是接受到了客户端的请求,故isActive返回true,调用 pipeline.fireChannelActive()方法,触发fireChannelActive事件
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
} catch (Throwable t) {
...
}
}
//ChannelHandlerContext处理该事件并调用 readIfIsAutoRead方法
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelActive();
readIfIsAutoRead();
}
private void readIfIsAutoRead() {
if (channel.config().isAutoRead()) {
channel.read();
}
}
public Channel read() {
//触发pipeline的读事件
pipeline.read();
return this;
}
public final ChannelPipeline read() {
//调用TailContext的读操作
tail.read();
return this;
}
//最终调用
public void read(ChannelHandlerContext ctx) {
unsafe.beginRead();
}
public final void beginRead() {
assertEventLoop();
if (!isActive()) {
return;
}
try {
doBeginRead();
} catch (final Exception e) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireExceptionCaught(e);
}
});
close(voidPromise());
}
}
//通过AbstractNioChannel的doBeginRead方法设置为读事件
protected void doBeginRead() throws Exception {
// Channel.read() or ChannelHandlerContext.read() was called
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}
readPending = true;
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
}
该方法较为简单,但是有些难以理解的部分是我们的事件传播机制,如果把netty的事件传播机制原理明白这里的这点疑问就迎刃而解了,下一章节我们来分析netty的事件传播机制,在下一章的末尾我们会拿这个案例做分析算是将这一章的疑问依据下一章的基础做解答,也算是这一章的补充部分。