下单决心研究Netty源码,将用三篇文章记录自己看源码流程,方便后续复习!这是第一篇,也就是服务端启动流程源码剖析,通过netty线程模型图做主流程,寻找图中流程步骤在源码中的具体体现!
NIO线程模型图:
Netty的源码流程就和这个图差不多;接下来跟着这幅图一步一步找到和图中对应的源码位置;
首先就是初始化BossGroup了!
也就是这幅图中的两行代码;两种做的事都是一样的,只是传参的区别罢了!
直接点进这个方法:
流程一幕了然,就是一个个构造方法的嵌套调用传递参数罢了!
那么这个线程数的默认值是多少呢?
private static final int DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));// 可用处理器 * 2,如果配置了就用配置好的
继续往下!
创建这两个变量的目的是什么呢?接着往下看
总结一下:BossGroup传递的参数是1,也就说上一步会为Bossgroup创建一个长度为1的children数组,然后再为这个数组中的元素赋值,赋值的类型是什么 呢?-----EventExecutor;看名字像是一个线程池!究竟是不是线程池呢?我们还得看看newChild做了什么;
newChild()
将初始化的线程池executor传进去
可以看到,返回值是EventLoop;所以说,每一个Child实际是一个EventLoop;在回顾一下最上面说到的线程模型:
不就是这个EventLoop吗,那图中还有两个关键东西:Selector 和 TaskQueue;我们接下来的目标就是找到这两个玩意儿,并试图弄清楚他们的作用!
那就接着往下:
taskQueue 找到了,也就是说:EventLoop其实是一个线程池;之后线程会消费taskQueue中的任务,咱们先混个眼熟!现在你知道为什么网上的资料都说EventLoop是一个线程池了;
现在Selector也有了:相信了解Nio的都对这个Selector非常熟悉;可以说它就是Nio最重要的组件也不为过!
通过注册监听事件,Os就会将就绪事件放到Selector中,这样就能做到响应式IO;
具体如何使用这个Selector,后面我们会清楚的了解到的!
那么至此,newChild方法就看完了;线程模型图中的第一步我们就清楚的知道是怎么一回事了;
那么接下来就是
这个无非就是将上面我们创建的两个Group传进去,为其成员变量赋值!后面肯定会用到!
接着往下:
工厂模式的体现!大胆猜测一下:这个channel就是后续我们要经常使用的!但是我们又不能每次都去new,通过将其构造方法放到工厂中,形成一个模版;后续我们只要产生一个连接就会产生一个Channel,我们就会通过这个反射工厂去创建需要的channel;拭目以待!
然后再看
这没啥可讲的!
总结:无非就是给bootstrap中的成员变量进行赋值,后续程序启动的时候便可以操作这些属性完成相应的操作!
属性都有了,终于可以启动了吧?
好的,咱们继续;看看他具体是如何绑定端口的,以及如何运用上面bootstrap已经完成赋值的属性的!
很好,Channel终于被实例化了,具体怎么实例化的?实例化做了哪些东西呢?这就得看构造方法了;前面我们传入的是一个空构造:
所以在反射实例化的时候会调用NioSocketChannel的空构造方法,我们去看看
继续看super方法,主要搞懂这个16是什么意思
所以16是什么意思呢?看翻译就能就能知道:感心趣的操作;其实就是一个事件的代表;OP_ACCEPT
根据这几个关键信息:
不难猜出;后续Channel注册成功后,会对连接事件感心趣,具体是如何绑定这个连接事件的,等会我们就知道了!
然后继续调用super方法
我们知道:Netty中一条Channel中是有很多handler处理器的,那么这些handler是由谁控制的呢?就是pipeline;
很好,那么pipeline估计就是初始化里面的节点了,
果不其然,就是这样!后续调用addlast就会往链表里里面插入handler!
总结:
channel初始化的同时初始化了pipeline,pipeline初始化创建了一个head 、tail两个结点,绑定在Channel身上!
接下来就看init这个方法,比较重要!
添加一个handler,现在不用管,后续会掉用到!
init就看完了:
继续往下:
首先是调用group方法获取一个eventloop
Netty怎么异步执行任务的呢?我们都说EventLoop是一个线程池,会执行任务,具体体现就在这个地方!
可以看到eventLoop.execute就是提交任务;
taskQueue的作用终于看见了吧!!
然后看线程执行,是如何消费的!
流程有点复杂,主要看几个关键方法就行了!
启动线程
select方法是干嘛的?
select方法是阻塞等待事件发生,并将事件数量返回;我们传了一个过期时间,如果时间过了都还没有事件就返回;有了这个前提之后我们就知道,要处理事件了(读事件、写事件等);
但是现在肯定是没有的,因为刚启动,前面我们说的读事件并没有注册监听所以暂时没有事件;继续往下!
暂时不看processSelectedKeys(),而是看runAllTasks();
到这里回顾一下我们的主线任务:线程模型图:
不就是上面三个方法吗?
runAllTask()
这个方法看名字就知道什么意思了,无非就是从taskqoeue中拿任务然后执行;这没什么好说的!贴个图:
好了!所以我们现在要去看看register的run方法看看具体是做了什么事!
也就是register0方法!
说实话,这里就是把channel注册到selector上,但是为什么传0值,注册的事件对应是什么我不知道,我查了半天了也没有个答案(找到答案了:表示此时将服务端channel注册到多路复用器上,服务端channel感兴趣的事件标识符是0,即此时对任何事件都不感兴趣。(真正开始对接收事件感兴趣是在服务端channel监听端口之后)。 );总之就了解一个点:NIO 程序运行起来势必会产生相当多的channel,每一个channel都有自己关心的事件;通过将自己注册到selector中去之后便可响应式的获取自己事件的触发情况从而做出相应的读写操作!
然后接着往下看吧
第一个方法,看名字就能猜出来;再开始的时候我们不是往pipeline中加入了一个initialHandler嘛
这个方法就是执行这个初始化方法,执行完毕之后是这个效果:本质上就是往责任链中加入了一个handler;用来处理后续的事件请求;比方说有客户端连接之类的;
接着往下看:
pipeline.fireChannelRegistered();
这个方法:在netty中fire前缀都是与责任链相关的方法,该方法目的就是调用handler中的
然后继续看channelregister方法是干啥呢?最终跟踪到这一段代码
也就是责任链调用所有的handler的channelregister方法!
本文从eventGroup开始讲起,看了服务端初始化BossGroup和WorkerGroup的过程;了解了eventLoop的本质原理;其实就是内部有一个线程池还有一个taskqueue;线程池里面只有一个线程;即执行taskqueue的任务,同时又轮询selector中的事件;处理Accpet请求;
然后就是启动器的原理探究,无非就是初始话一些属性参数;真正使用还是要在bind方法的逻辑里面进行
终于:bind开始了;从宏观上来讲就是绑定一个端口;注册监听事件;主要还是将几个重要的组件运行起来,首当其冲的就是Channel,然后就是pipeline;以及handler的注册;channel的注册到selector中;
重点:netty异步任务是怎么做的; channel是如何初始化的;理解Netty整个线程模型;
上图流程源码已经展示完毕,后续流程即将更新!