最近的开发工作中使用到了netty,对比jdk原生的NIO写法,netty简单了很多,而且性能也是很不错,就对netty如何做到的比较感兴趣,就开始对源码进行了研究和学习。这也是我第一次比较系统的学习一种优秀框架的源代码,为了加强记忆和梳理逻辑,对这次源代码的分析做一下记录。
源代码的分析首先从下载源码开始,进入到netty的github复制地址,在git上执行git clone https://github.com/netty/netty.git,下载源码。国内访问git的速度很慢,git clone代码会很长时间,也可以直接下载netty的压缩包到本地,然后使用IDE导入。如下图
分析客户端的源码就从netty自带的example的EchoClient开始。首先是对EventLoopGroup的实例化,然后对Bootstrap的实例化,然后就是对Bootstrap的各种配置,最后就是bootstrap的连接操作。
这里我们先分析EventLoopGroup的实例化过程。EventLoopGroup是一个什么功能的接口,我们来看一下它的继承关系就能大概猜出这是个什么功能的接口了。看类图关系,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/b5b4cecead7cec8e8c4e7d3dc2019495.png)
EventLoopGroup继承的顶层接口是jdk的并发框架Executor,然后继承了定时调度任务的接口,因此它的继承和实现类都具有任务执行和定时执行的功能,因此实际上netty在处理IO操作和任务处理的核心上就是这些功能。客户端实际实例化的对象是NioEventLoopGroup,这个类是EventLoopGroup实现的一种,使用一种线程池的使用。
NioEventLoopGroup的实例化过程就是初始化该线程池的线程数量,执行的任务,Selector的提供者,默认的线程选择策略工厂的实例,以及线程拒绝的处理。这些参数的初始化有的是调用本类的构造函数,有的是逐层级的调用父类的。该类默认的初始线程数量传入的是0,当值为0时,netty会使用框架中默认的数量为CPU的核数*2,下面我们来看一下这个过程。
然后一级级的调用到
DEFAULT_EVENT_LOOP_THREADS的值为如果没有设置程序启动参数,那么默认情况下线程的个数为cpu的核数乘以2,咱们来看一下他的值是如何设置的
到这里就非常清楚了,如果调用空构造函数或者传入值为0的线程数都会采用netty默认的线程数量,这个也解释了为什么在服务端的boss线程池初始化的时候为什么要传入1的缘由。
初始化过程最终会定位到MultithreadEventExecutorGroup的构造函数上,该构造函数的最主要功能是根据传入的线程数量,构造一个线程数组然后,对数组中的每个线程进行初始化。数组中的线程初始化调用了newChild()方法,这个方法又是在子类中实现的,也就是在客户端初始化的NioEventLoopGroup中实现的。
总结一下NioEventLoopGroup的初始化过程就是不断调用本类及父的构造方法,然后初始化调用本类的线程初始化方法。这个过程验证了父类能做的在父类中做,具体的使用让子类自己去实现。
Bootstrap的实例化比较简单就不介绍了,对于它的一系列配置比较复杂,咱们来看一下。
第一步将初始化的线程池放入到该类里也不介绍了。
第二步配置 channel传入的是NioSocketChannel.class,这一步根据传入的channel类别构造对应的channel工厂,然后使用该工厂创建该channel的实例对象。调用过程为:channel()->channelFactory()->new ReflectiveChannelFactory<C>(),最后将构造的channel工厂赋值给Bootstrap的成员变量channelFactory上,后期根据该变量对channel进行初始化,这个channel的初始化过程后面再细说。
第三步option的配置主要是对channel的一些配置。
第四步handler的配置是对ChannelPipeline的一些配置。该方法会传入一个ChannelInitializer的实例,然后在initChannel()方法内配置netty的一系列处理业务逻辑的handler。ChannelInitializer继承自ChannelInboundHandlerAdapter,也属于handler的一种。在调用bootstrap的connect()方法时,会进行一系列的配置和初始化。
connect()方法的调用过程很复杂,主要是对channel的初始化和注册。逐层打开connect()方法的调用会发现调用到了initAndRegister()方法。该方法就是对channel的初始化和注册的核心部分。在initAndRegister()方法中先调用了init()方法,然后调用config().group().register(channel)进行注册。
init()方法中主要是对配置的handler添加到ChannelPipeline中,看源码如下:
@Override @SuppressWarnings("unchecked") void init(Channel channel) throws Exception { ChannelPipeline p = channel.pipeline(); //添加BootStrap配置的ChannelInitializer p.addLast(config.handler()); ......... }
register()最终会调用Channel的Unsafe的register方法,Unsafe是操作底层操作系统的对象,依附于Channel对象内,不对外使用,因此叫不安全的。注册的方法调用链如下:
initAndRegister->config().group().register()->SingleThreadEventLoop.register()->AbstractUnsafe.register()->AbstractUnsafe.register0()->AbstractUnsafe.doRegister()
doRegister()方法由子类AbstractNioUnsafe实现,具体的代码如下:
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
//调用底层的channel进行注册
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
if (!selected) {
eventLoop().selectNow();
selected = true;
} else {
throw e;
}
}
}
}
整个客户端的启动过程就算介绍完了,后续对chennel的初始化过程进行详细讲解。