回顾
- 创建服务端Channel
- 初始化服务端Channel(<--上一节)
- 注册selector(<--本节)
- 端口绑定、通知
Netty version:4.1.6
前两节的代码都是集中在initAndRegister()函数中的,这节也不例外,只是我们继续往下看罢了。
Selector是什么
在了解Netty注册selector之前,我们得要先知道Selector是个啥,关于这点,我们不妨看看java 8文档中的selector,从官方的一些简介和它的函数我们不难发现,selector其实就是一个负责管理Channel的东西,网上也有人称它为多路复用器。
它还可以检查一个或多个Channel(通道)的状态是否处于可读、可写。
为了更好的理解selector是个什么东西,我拿来之前的一张图:
注册Selector大致流程
开始追踪源码
让我们视角再次回到initAndRegister()函数中,如果忘记怎么进到这里面的,可以回到第一节中再看看。
现在我们先看看initAndRegister,在里面我们可以找到一个register的函数:
上面的register函数最终会调用到
io.netty.channel.AbstractChannel.AbstractUnsafe#register,
由于这个调用过程比较繁琐,我这里只列出一些关键步骤,详情还请去源码打断点:
- 进入io.netty.channel.MultithreadEventLoopGroup#register(io.netty.channel.Channel)。注意,这一步会遇到一个next方法,这个方法是分配NioEventLoop的,并且这个NioEventLoop是属于childGroup(workerGroup),如感兴趣,请看【创建选择器】。
- 进入io.netty.channel.SingleThreadEventLoop#register(io.netty.channel.Channel)
- 进入io.netty.channel.SingleThreadEventLoop#register(io.netty.channel.ChannelPromise),在这里可以看到以下代码:
promise.channel().unsafe().register(this, promise);
- 上面的register进去以后,就是:
io.netty.channel.AbstractChannel.AbstractUnsafe#register
- 此处还注册了NioEventLoop
现在将视角切换到io.netty.channel.AbstractChannel.AbstractUnsafe#register,看看函数部分源码:
可以看到上图的源码中出现了流程图中的两个步骤:eventLoop和resgiter0。并且可以看到断点中eventLoop就是我们自定义的NioEventLoop(Group)。
至于eventLoop具体干什么,先在这留个印象,以后再继续补充。
纠正,这里的register0方法不一定是调用if代码块中的register0方法,因为上面也说了next方法负责分配NioEventLoop,是不是if代码块取决于next方法的分配,以前截图的时候截错了,后期博客可能对这一块还有补充。
之后,我们将视角转移到上图的register0函数中,就可以发现流程图中剩下的三个函数:
io.netty.channel.AbstractChannel.AbstractUnsafe#register0
上图中还有个要注意的点,因为此时Netty还没绑定端口,所以此时isActive()还是false,而pipeline.fireChannelActive();没有被执行,也即ChannelInboundHandlerAdapter的channelActive函数其实还没被调用。
关于channelActive是哪里执行的,这里就需要补上前面章节没有补上的ServerHandler了:
ServerHander.java
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.concurrent.TimeUnit;
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("channelActive");
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) {
System.out.println("channelRegistered");
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
System.out.println("handlerAdded");
}
@Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
super.channelRead(ctx, msg);
new Thread(new Runnable() {
@Override
public void run() {
// 耗时的操作
String result = loadFromDB();
ctx.channel().writeAndFlush(result);
ctx.executor().schedule(new Runnable() {
@Override
public void run() {
// ...do something
}
}, 1, TimeUnit.SECONDS);
}
}).start();
}
private String loadFromDB() {
return "hello world!";
}
}
然后执行Server.java(代码上一节有,这里就不重复贴了),执行结果如下:
上上面说的channelActive,其实就是指上面ServerHandler.java的channelActive函数,而其中handlerAdded和channelRegistered事件对应的就是invokeHandlerAddedIfNeeded()和fireChannelRegistered()。
好了,现在将视角重新转回:io.netty.channel.AbstractChannel.AbstractUnsafe#register0
这里面有三个流程图中的函数:
- doRegister();
- invokeHandlerAddedIfNeeded();
- fireChannelRegistered();
doRegister
我们进到doRegister函数源码:
io.netty.channel.nio.AbstractNioChannel#doRegister
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
selectionKey = javaChannel().register(eventLoop().selector, 0, this);
return;
} catch (CancelledKeyException e) {
if (!selected) {
eventLoop().selectNow();
selected = true;
} else {
throw e;
}
}
}
}
可以发现这里底层是用jdk的api注册并绑定selector的,参数ops=0表示现在不关注任何事件(绑定端口后会关注accept事件)。
javaChannel().register(eventLoop().selector, 0, this);中的this,最终会被包装成一个叫Attachment的东西,以后可能用到,就在这里记一记,看到Attachment可以当做是Channel。
如果我们再点进上面的javaChannel()函数,可以看见如下代码:
io.netty.channel.nio.AbstractNioChannel#javaChannel
protected SelectableChannel javaChannel() {
return ch;
}
上面的ch类型为SelectableChannel,其实就是在【创建服务端Channel】那一节创建的,具体从下面这段代码开始:
io.netty.channel.socket.nio.NioServerSocketChannel#NioServerSocketChannel(java.nio.channels.ServerSocketChannel)
new NioServerSocketChannelConfig(this, javaChannel().socket());
最终到AbstractNioChannel中的this.ch=ch;
至于流程图中最后两个函数:invokeHandlerAddedIfNeeded和fireChannelRegistered这里就先不解析源码了,目前只需要知道它们操作最终体现就是上面Server.java执行结果中的前两行,或者先按照函数名或源码注释理解它们大概做了什么也行。