netty源码分析-客户端启动

     最近的开发工作中使用到了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是一个什么功能的接口,我们来看一下它的继承关系就能大概猜出这是个什么功能的接口了。看类图关系,如下:

EventLoopGroup

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的初始化过程进行详细讲解。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值