一、简介
NioEventLoop的重要组成:Selector、线程、任务队列,他既会处理io事件,也会处理普通任务和定时任务.
1.下面是Selector,注意有两个哦后面会讲
2.下面的爷爷类提供的Thread变量,其实下面发excutor用的就是这个一个线程。
任务队列就是thread上面的这个taskQueue
变量
3.从曾祖父类中可以看到处理定时任务的任务队列
二、带着问题读源码
2.1selector在何时创建?
答案:在构造方法的openSelector()方法中被创建
2.2EventLoop为啥有两个Selector对象?
答案:为了在遍历SelectedKey时提升性能
继续往下看看openSelector方法:
SelectedSelectionKeySet的部分源码展现:
再看EventLoop构造方法就懂了:
1.selectorTuple
就是 new SelectorTuple(unwrappedSelector,new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet))
2.selectorTuple.selector
就是new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet))
3.new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet))
的底层可以看到,基本就是对unwrappedSelector进行了包装
2.3.EventLoop的Nio线程何时被启动?
答案:当首次调用execute方法时,通过state状态位控制线程只会启动一次
1.可以写一个测试代码,咱们追execute方法就行!
下面是其源码展示:
2.在inEventLoop
的底层是通过thread变量和当前线程是否相等来判断是不是Nio线程
第一次执行时thread等于null,当然会返回false
3.随后通过addTask
方法将任务提交到任务队列
4.第一次执行由于不属于nio线程,会执行startThread
2.4提交普通任务会不会结束select阻塞?
答案:会
1.重点追一下上面提及的run方法
发现netty的select底层是用了带超时时间的select方法
2.回到execute方法,分析后面的普通任务进来的执行逻辑
第二次execute方法进来,肯定是走下面这个wakeUp的逻辑
从底层可以看出wakeUp可以释放selector的select等待
2.5wakeup方法中的代码怎么理解? & wakenUp变量的作用是什么?
答案:只有其他线程提交任务时,才会调用上面2.4提到的selector.wakeup
wakenUp的作用:如果有多个其他线程都来提交任务,使用cas算法避免wakeUp被频繁调用
2.6每次循环时,什么时候会进入SelectStrategy.SELECT分支
答案:当没有任务时,才会进入SelectStrategy.SELECT分支,当有任务时,会调用selectNow方法,顺便拿到io事件
这得分析switch括号中方法的返回结果了,因为具体走哪个分支完全有这个case决定
由底层源码可知,档hasTasks()返回为fasle时,也就是没有任务才会走SELECT分支
如果有任务,则马上调用selector.selectNow把事件给拿到
2.7何时会select阻塞,阻塞多久?
答案:没有任务时阻塞一秒左右,只有三种情况才会跳出for的死循环:1.超时时间到了2.有任务3.有事件
2.8Nio空轮询bug在哪体现?如何解决?
答案:
1.bug的体现就是selector.select(timeout)方法可能没阻塞住,导致for循环一直空转(linux平台会产生)
2.解决办法是增加了selectCnt计数器,每次循环都会加1,默认达到512次就会认为bug产生,换一个新的selector,并会自动break
下面是for循环空转的终止条件,达到512次会重新创建一个新的selector,替换掉有问题的selector
2.9 ioRatio控制什么?设置为100有何作用?
答案:ioRatio控制处理io事件所占用的时间比例,如果设置100会让普通任务全部执行完(强烈不推荐!)。咱们可以看else的逻辑
由于Netty线程是单线程,如果处理普通任务的时间过长,势必会影响到io事件的处理
ioTime代表执行io事件处理耗费的时间
2.10selectedKeys优化是怎么回事?
答案:就是用数组类型的selectedKeys代替原来Set结构的SelectedKey,提升访问的效率
在上面2.2中提到了,selectedKeys是优化后数组类型的key集合,所以默认优化后会走if中的分支
继续追processSelectKeysOptimized
方法,发现逻辑大致就是拿到nioChannel
这个附件,执行其pipline中绑定的handler
processSelectedKey
方法的底层是根据事件的类型做不同的处理
三、Netty中的Accept流程
原生nio中accept流程主要干了下面六件事情:
上面2.6-2.10已经分析到了前面三件事情,本段落时分析后续的三件事情在哪执行!
1.继续分析2.10中提到的unsafe.read()
,这里会调到NioMessageUnsafe类中,我们关注doReadMessage这个方法即可
从doReadMessages
方法中可以看到nio原生的accept的过程,并主动创建了一个NioSocketChannel。而设置为非阻塞的过程在new NioSocketChannel
方法中,有兴趣的朋友可以追一下
2.回到unsafe.read
方法,往下继续看
这个pipeline.fireChannelRead
就会调用到Netty源码解读-server端中2.3.3提到的acceptHandler
也就是会调用到ServerBootstrapAcceptor.channelRead
,主要是设置一些参数,然后执行regitster方法
childGroup.register
这个方法和Netty源码解读-server端中2.3.1的register的过程基本一致,也会切换成nio线程执行register0方法
继续分析pipeline.fireChannelActive
,他势必也会执行head节点的active方法,又是熟悉的配方
又是在AbstractNioChannel.doBeginRead
方法中关注到read事件
3.read事件触发
虽然该事件也会走2.10中提到的unsafe.read()
,但和accept事件不同,他会进入NioByteUnsafe子类
会在doReadBytes方法中读取client端发送过来的数据,可以打断点看到经过这一行,byteBuf读指针发生了变化
后续的逻辑是通知其他的hander,链式执行…