前言: 希望看这篇文章之前对Java Nio编程比较熟悉,并有用过Netty开发简单代码
服务端代码
先大致说一下NioEventLoopGroup组件的作用,可以把它看是作内部维护了一个NioEventLoop数组的对象,它的构造方法的参数用来指定维护数组的大小。NioEventLoop继承自Executor可以理解为一个NioEventLoop是一个单线程线程池。
ServerBootStrap作为一个封装Java Nio 的一个启动类接下来我们分析这个启动类
首先bootstrap.group(boss,eventExecutors)这段代码内部为SerBootStrap属性赋值,分别赋值给Group和childGroup。Group即boss线程组主要负责通过Java Nio建立连接 ,childGroup主要负责监听Group传递过来的Channel。
channel方法内部是根据类对象创建一个channelFactory工厂,用来通过反射创建NioServerSocketChannel,这里的Channel完全是Netty自己的类不是JavaNio自带的类
重点在bind方法内部
内部调用doBind方法,接下来注意initAndRegister方法
通过工厂方法创建channel,由于NioServerSocketChannel继承了AbstractChannel,先看父类构造方法都做了什么
unsafe类不是Java中的操作内存的Unsafe类,这里的unsafe类是用来读取和写入消息进行的封装,pipeline可以理解为是一个链表,每个节点是ChannelHandler和上下文信息封装而成
NioServerSockerChannel构造方法内还创建了Java Nio的ServerSocketChannel
这里代码图片就不粘贴了可以自己动手看,其内部就是把ServerSocketChannel赋值给NioServerSocketChannel的一个属性,如果后续代码中NioServerSocketChannel调用javaChannel()方法返回的就是这个属性即Java原生ServerSocketChannel。
Config内部等于保存了NioServerSocketChannel的引用,以及初始化了ByteBuf内存分配器
至此 channelFactory创建channel的方法结束
此时NioServerSocketChannel内部只是保留了对ServerSocketChannel的引用,并未对ServerSocketChannel注册事件等等
接下来注意Init方法
可以看到它主要做了一些参数信息的设置以及添加了一个HandlerContext,这里等于说是一个头节点和一个这个节点以及一个尾节点构成pipleline。这里留意一下execute方法,参数currentChildGroup就是开头的NioEventLoopGroup(child),这里的currentChildHandler是我们最开始片段的Handler,以及Options都是最开始专门为ChildHandler设置的
这里Init方法结束,接下来看initAndRegister方法中的Register方法
这里的config是上一步的config对象,这里的group是最开始创建的Boss线程组 NioEventLoopGroup,进入register方法内部
public ChannelFuture register(Channel channel) {return next().register(channel);}
next()方法主要是从线程组中挑出一个NioEventLoop即只有一个线程的线程池,再次深入register方法
我们发现这里的promise封装了NioServerSocketChannel和从Group中挑出的线程池
接下来是重中之重涉及到线程的开启和切换
Unsafe的register方法
这里的inEventLoop其实是判断当前线程是否是NioEventLoop中的线程,NioEventLoop中的线程采用懒惰加载,第一次为null所以判断结果为false。
我们可以看到他向单线程线程池(NioEventLoop)任务队列中提交了一个普通任务
接着调用startThread方法,直接看其内部的doStartThread方法
看左下角当前线程是NioEventLoopGroup中的一个线程,并把此线程赋值给Thread属性(懒加载),接着进入SingleThreadEventExecutor.this.run()方法,这里的this.run指的是NioEventLoop的run方法
这里我要引入一张图片来解释run方法的功能
我们现在只处在BossGroup中,注意下面的圆圈这里就是这段代码的功能我们一句一句看
这里的selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());方法是判断普通任务队列(非定时任务)是否有待完成的任务,这里显然是有的上面已经说了提交了一个任务register0,但是这里面的作用是如果有任务就调用selectNow方法,看返回的是否有事件发生。
因为NioEventLoop毕竟是线程池除了要监听channel事件还要处理普通任务和定时任务
如果没有普通任务返回的就是枚举类SELECT
long curDeadlineNanos = nextScheduledTaskDeadlineNanos();计算的是定时任务的最小时间,这里是为了下面调用Select.select(time)有限时间监听channel事件。正如之前所说:NioEventLoop毕竟是线程池除了要监听channel事件还要处理普通任务和定时任务
上面主要是解决定时任务的情况方法就是有限时间的等待,由于我们只提交了register0任务继续向下看,看它是如何处理普通任务和channel中发生事件的
这段代码要留意selectCnt++,ioRatio,processSelectedKeys(),ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio)
首先 processSelectedKeys()是为了处理Channel中有注册事件发生,我们代码中目前还没有连接事件
所以就看runAllTask方法
其内部就是从队列中拿到之前提交的task任务
看!该执行我们之前提交的任务了
看此方法内部 doRegister方法
这里任务的提交以及register方法都是在NioServerSocketChannel的父类中完成
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
这段代码就是把Netty中的NioServerSocketChannel当作附件给添加到Java Nio中ServerSocketChannel,并把它注册到selector中
同时调用HandlerContext的initChannel方法,把任务提交向pipleLine中添加一个HandlerContext
到这里启动流程基本就这些,事件的处理selectCnt++,ioRatio,processSelectedKeys(),下篇文章继续