Netty源码分析--Channel注册&绑定端口(下)(七)
接下来,我们看到的就是两个非常重要的方法
就是 processSelectedKeys() 和 runAllTasks() 方法了。
selectionKey中ready的事件,如accept、connect、read、write等,由processSelectedKeys方法触发。属于I/O任务。
添加到taskQueue中的任务,如register0、bind0等任务,由runAllTasks方法触发。属于非I/O任务。
两种任务的执行时间比由变量ioRatio控制,默认为50,则表示允许非IO任务执行的时间与IO任务的执行时间相等。
我们看一下 processSelectedKeys() 方法, 因为 selectedKeys != null 所以进入 processSelectedKeysOptimized() 方法。
由于没有这里只是启动服务端,没有客户端接入进来,所以我们先跳过processSelectedKeys(),一会我们结合客户端接入来讲这里。
直接看 runAllTasks() 方法。
Runnable task = pollTask(); 这个就是从 taskQueue 中拿出一个task。
然后循环执行这个任务, safeExecute(task)。
这个方法也是很简单,就是直接执行Runnable接口中的run()方法(这里并不是启动一个线程,而是仅仅的执行一个普通的run方法)。
大家想一下这里的这个task应该是什么呢?
大家还记得这段代码吗? 就是这个 register0() 方法。
我们先进入到 doRegister() 方法
继续传入当前的eventloop中的selector, opt = 0, 第三个参数 this 就是当前的 NioServerSocketChannel。 进入register 方法
大家看我圈出来的这一句,熟悉吗?我当时将NIO的时候是不是讲到了。
这里就是把当前的channel注册到这个多路复用器上。并且把 NioServerSocketChannel 传进去当做附件 attach, 注册的 interestOps = 0
好了,当执行完task,由于是一个死循环,那么会继续执行刚刚的整个过程。
好了,总结一下: 也就是说有一个线程一直在这里不断循环的等待新的 selectionKey中ready的事件,如accept、connect、read、write等。 如果有待处理的task,将会去优先处理的task.
一会我们会启动一个客户端看一下是怎么交互的。
整个注册完成之后,接下来就是 绑定端口 ,将服务对外开放出去。
我们看下AbstractBootstrap中的 doBind() 方法。
由于整个注册过程是异步的,所以这里 regFuture.isDone() 是否已经完成,如果完成直接执行doBind0(),如果没有完成,那么就监听异步响应方法,等待成功之后,再执行doBind0()方法。
我们进入doBind0()方法
我们看其实就是向eventLoop中的任务队列中添加一个task。
这里我们debug来看一下
另外在 AbstractBootstrap中打一个断点,在这里等待注册事件先完成。
好的,我们启动服务端。
断点进来了, 我们再在 NioEventLoop 中打一个断点,因为这里是处理task的地方
我们发现有一个主线程,一个子线程,如下图
切换到子线程,我们看下 task 的执行过程。
因为switch中的hasTask() 是true,那么我们就直接看
从任务队列中取出一个task,我们看到就是刚刚我们的那个任务。然后通过safeExecute(task)执行run方法
继续F5。我们看进入到了runnable中的run方法。
接下来就是一段链式调用,链式访问pipleline中的handler TailContext -> ServerBootstrapAcceptor -> LoggingHandler -> HeadContext
TailContext 和 ServerBootstrapAcceptor 中没有bind方法,直接进入LoggingHandler的bind方法,打一个日志
继续f5进入到 HeadContext中的bind方法
先判断是否激活,如果没有,则稍后链式调用handlers中的 channelActive()方法。
进入doBind方法
ok,到这里绑定端口成功。
目前为止,Server服务端启动完成,接下来我们看一下,一个客户端是怎么接入进来并且进行读写操作的。