Netty事件处理流程
前面在EventLoop系列源码解析中说到了处理各种事件的其实是 NioEventLoop 中的 processSelectedKey(SelectionKey k, AbstractNioChannel ch)方法:
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
//校验key的可用性
if (!k.isValid()) {
final EventLoop eventLoop;
try {
eventLoop = ch.eventLoop();
} catch (Throwable ignored) {
return;
}
if (eventLoop != this || eventLoop == null) {
return;
}
unsafe.close(unsafe.voidPromise());
return;
}
try {
int readyOps = k.readyOps();
// 连接connect事件
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
//写write事件
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
ch.unsafe().forceFlush();
}
// 接受连接accept事件 或者 读read事件
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
上面是接收到四种事件的处理过程,其实基本都是相同的逻辑,所以我就选择accept事件来讲解下整个流程是怎么样的?也是因为前面我们讲了服务器端的一个启动流程源码,所以选择讲讲它的accept事件处理过程。 unsafe.read()这个方法有两种实现,根据继承关系可以知道服务器的accept事件会调用AbstractNioMessageChannel.NioMessageUnsafe的read方法:其实unsafe.read()这个方法的两种实现在前面Unsafe系列源码讲解时都有详细讲解到
public void read() {
assert AbstractNioMessageChannel.this.eventLoop().inEventLoop();
ChannelConfig config = AbstractNioMessageChannel.this.config();
ChannelPipeline pipeline = AbstractNioMessageChannel.this.pipeline();
Handle allocHandle = AbstractNioMessageChannel.this.unsafe().recvBufAllocHandle();
allocHandle.reset(config);
boolean closed = false;
Throwable exception = null;
try {
int localRead;
try {
//因为可能有多个客户端同时发起连接,所以在do-while循环中调用doReadMessages()
do {
//整个read方法最重要的也就是这个doReadMessages方法,
//执行完后生成的子channel也就加入到readBuf中了
//List<Object> readBuf 是实际就是存放与客户端通信的 socketChannel 的容器
localRead = AbstractNioMessageChannel.this.doReadMessages(this.readBuf);
if (localRead == 0) {
break;
}
if (localRead < 0) {
closed = true;
break;
}
allocHandle.incMessagesRead(localRead);
} while(allocHandle.continueReading());
} catch (Throwable var11) {
exception = var11;
}
localRead = this.readBuf.size();
//这里遍历生成的子channel加入到,触发ChannelRead事件,具体的流程跟前面提到的fireChannelActive一样,基本没区别
//ChannelRead事件入站事件,因为在HeadContext和TailContext中的ChannelRead没有做处理
//所以进入到ServerBootStrapAccept的channelRead方法,这个方法在前面服务器启动流程中讲解ServerBootStrapAccept时详细说过
//ServerBootStrapAccept.channelRead的参数msg实际上是一个Channel类型
for(int i = 0; i < localRead; ++i) {
AbstractNioMessageChannel.this.readPending = false;
pipeline.fireChannelRead(this.readBuf.get(i));
}
this.readBuf.clear();
allocHandle.readComplete();
//所有的连接请求都处理完后会调用主handler中的pipeline的fireChannelReadComplete来触发ChannelReadComplete事件(入站)
//后续流程和fireChannelActive很类似,不在赘述
pipeline.fireChannelReadComplete();
if (exception != null) {
closed = AbstractNioMessageChannel.this.closeOnReadError(exception);
pipeline.fireExceptionCaught(exception);
}
if (closed) {
AbstractNioMessageChannel.this.inputShutdown = true;
if (AbstractNioMessageChannel.this.isOpen()) {
this.close(this.voidPromise());
}
}
} finally {
if (!AbstractNioMessageChannel.this.readPending && !config.isAutoRead()) {
this.removeReadOp();
}
}
}
先进入doReadMessages方法看看,然后在看看ServerBootStrapAccept的channelRead方法(ServerBootStrapAccept这个类后面会提到)
protected int doReadMessages(List<Object> buf) throws Exception {
//借用SocketUtils.accept方法接受连接,生成一个子channel
SocketChannel ch = SocketUtils.accept(this.javaChannel());
try {
if (ch != null) {
//将这个子Channel包装成一个NioSocketChannel,然后加入到buf中
//需要注意的是,在new NioSocketChannel()时,会创建出channel中的pipeline,且会传入将来感兴趣的事件是read事件,因为是子channel
buf.add(new NioSocketChannel(this, ch));
return 1;
}
} catch (Throwable var6) {
logger.warn("Failed to create a new channel from an accepted socket.", var6);
try {
ch.close();
} catch (Throwable var5) {
logger.warn("Failed to close a socket.", var5);
}
}
return 0;
}
ServerBootStrapAccept的channelRead方法
public void channelRead(ChannelHandlerContext ctx, Object msg) {
//这个msg是什么?它是一个子channel,而且包装成NioSocketChannel。
final Channel child = (Channel)msg;
//将提前设置好的handler加入到pipeline中,还有一些其他的ops参数等等
//也就是channelRead这个方法完成了channel的一些配置
child.pipeline().addLast(new ChannelHandler[]{this.childHandler});
AbstractBootstrap.setChannelOptions(child, this.childOptions, ServerBootstrap.logger);
Entry[] var4 = this.childAttrs;
int var5 = var4.length;
for(int var6 = 0; var6 < var5; ++var6) {
Entry<AttributeKey<?>, Object> e = var4[var6];
child.attr((AttributeKey)e.getKey()).set(e.getValue());
}
try {
//childGroup.register(child)这个方法,跟服务器的register十分相似,不再赘述
this.childGroup.register(child).addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
ServerBootstrap.ServerBootstrapAcceptor.forceClose(child, future.cause());
}
}
});
} catch (Throwable var8) {
forceClose(child, var8);
}
}
至此处理accept事件就完成了所有的流程,其他三种connect、write、read事件处理流程上几乎和处理accept事件的流程大同小异,各位读者可以类比自行解读,也是对自己学习的一种验证方式。
整个Netty现在除了ByteBuf这一块还没讲,其他的都已经讲完了,整体下来笔者还是比较满意的,先将每一个组件中比较重要的内容单独讲解,再大家对每一个细节都有所了解后,在将它们串起来将整体的流程(服务器启动流程)和事件处理流程,因为这些流程是一个比较有连续性的内容,如果一开始就讲这些流程,中途总是卡壳停下来讲每个组件的细节,会让这个过程很不流畅,这样读者就很难坚持下来,会打击读者们的学习积极性,并且效果也不一定好。希望大家有疑问或者笔者理解错的地方在评论区提出,笔者会定期查看消息,会在第一时间回复,共同学习进步。