新启动的线程的作用
新的线程是个死循环,如果发现有任务的话会执行selectNow()
,如果返回0就会开始执行任务,否则没任务就执行选择器的select()
方法阻塞,可以看下这个选择策略类,细节后面会讲,先知道为什么开始会执行任务而不是阻塞:
执行NioEventLoop的run方法
线程其实是执行这个方法:
执行具体类NioEventLoop
的run()
,这个才是真正的任务,省略了大部分东西,把主要的显示出来而来:
@Override
protected void run() {
for (;;) {
select(curDeadlineNanos); //可能会被选择策略延迟,如果有任务的话
processSelectedKeys();
runAllTasks()或者runAllTasks(long timeoutNanos);//执行任务
}
前两个方法暂时不讲,我们就说最后一个runAllTasks
,因为开始的时候会先执行前面提交的任务,前两个方法不会执行。那到底是有什么任务呢,其实就是前面提交的一个注册任务:
执行任务一(通道注册register0)
主要是里面的doRegister();
private void register0(ChannelPromise promise) {
try {
...
pipeline.invokeHandlerAddedIfNeeded();//handlerAdded事件
safeSetSuccess(promise);//设置注册成功
pipeline.fireChannelRegistered();//channelRegistered事件
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();//channelActive事件
} else if (config().isAutoRead()) {
beginRead();
}
}
} catch (Throwable t) {
...
}
}
doRegister
可以看到里面的selectionKey
就是调用NIO
的ServerSocketChannelImpl
类来register
的,此时注册才算是完成了,不过要注意的是这时候的注册事件是0
,不监听事件。
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
...
}
}
}
}
pipeline.invokeHandlerAddedIfNeeded()(含有新任务提交)
handlerAdded
事件被触发,刚好是ChannelInitializer
,他会去调用initChannel
。
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel().isRegistered()) {
if (initChannel(ctx)) {
removeState(ctx);
}
}
}
最后就会调用了ServerBootstrap
的初始化通道的init(Channel channel)
这个方法,就会提交任务到当前事件循环的taskQueue
:
safeSetSuccess(promise);
这个就是要回调啦,设置注册成功了,于是会调用我们在前面监听注册完成的回调:
里面就要调用doBind0
来绑定端口了。
doBind0(含有新任务提交)
里面会提交任务到当前事件循环的taskQueue
,任务里面才是去真正的绑定地址,顺便添加了一个失败关闭的回调:
之后的还会执行一些通知回调,这个后面会将,然后任务完成。
执行任务二(管道添加ServerBootstrapAcceptor)
事件循环继续执行任务,这次就是执行刚才添加的第一个任务:
先添加一个处理器,在倒数第二个位置上,这个就是连接接收器,后面会分发给worker
组的,这个具体后面讲:
执行任务三(doBind0中的任务)(含有新任务提交)
事件循环继续执行任务,这次就是执行刚才添加的第二个任务:
最后的跟到:
@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
...
doBind(localAddress);//真正的绑定
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();//准备监听连接事件
}
});
}
safeSetSuccess(promise);//绑定完成
}
doBind(localAddress);
跟下去就到了NioServerSocketChannel
的doBind
方法,这里才是真正的绑定:
其实内部就是Nio
原生的ServerSocketChannelImpl
的bind
方法:
可见netty
最终的注册和绑定都是封装了Nio
原生ServerSocketChannelImpl
的操作。
然后还有个新任务invokeLater
提交:
然后设置绑定成功的结果:
然后添加绑定失败的回调:
其实失败就是关闭通道:
此时前面已经绑定成功了,所以直接就可以回调operationComplete
,因为我们自定义的也监听了绑定事件,所以这个时候也会有回调,也就说两个回调:
自定义的:
这个也可以看出为什么说netty
是事件驱动的了,都是异步的事件回调,当然这个非常好,但是带来的一个问题就是执行的顺序其实是不确定的,对编程来说是有一定难度的,所以还是需要有一定整体流程的把握,不然就各种回调触发,自己也搞不清楚,摸不着头脑了。
执行任务四(invokeLater任务)
前面doBind0
里添加的任务,触发通道激活事件:
他这个顺序也对,先把注册完成,然后绑定完成,然后再触发通道激活。
然后里面会调用通道的unsafe
的beginRead
,最终是通道的doBeginRead
:
里面会设置关心OP_ACCEPT
事件:
至此通道注册到选择器和通道绑定端口都已经完成。
有可能要执行任务五
不执行任务五的情况
这个任务是可能会提交,也可能不会执行,关键看自定义的写法,如果你是这样写,那就不会有个的方法执行:
因为addListener
的内部会判断是否有完成绑定,有的话才会调用notifyListeners
:
里面才会提交任务:
如果没阻塞添加的时候可能绑定任务没完成。所以也就不会执行进去,也就没有这个任务了。
执行任务五的情况
如果你是这种写法的话:
上一篇讲过,先会阻塞,直到绑定完成了调用设置成功结果safeSetSuccess(promise);
,会唤醒主线程,然后去addListener
的时候,发现已经完成了,所以就会调用notifyListeners
,因为会提交任务。
至此启动过程基本讲完了,后面会慢慢讲里面的细节。
总结
还是用图比较好表达,整理了一个大概的流程图,红色表示回调,绿色表示提交了任务,蓝色表示执行任务:
好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。