【netty学习笔记】BootServerstrap源码分析

1.BootServerstrap.bind()

netty在服务端这一侧的启动入口 是通过ServerBootstrap这个类的bind方法。

所以我们想了解ServerBootstrap做了什么就需要通过这个方法入手(本篇文章只以TCP连接为切入点)。

我们先从整体上看下这个方法经过了哪些流程。

大体上是2个方法,一个是initAndRegister,一个是doBind0,因此bind方法应该是分为了上下两步,第一步是初始化channel并将channel注册到Selector中,第二步就是将channel绑定到端口上。(与java的nio呼应上了),同时它的返回值为ChannelFuture,而以Future结尾的一般都是异步操作。因此bind方法其实是个异步方法,也就是说什么时候bind好了,其实我们是不知道的。
接下我们就来看下initAndRegister这个方法中又做了哪些动作

private ChannelFuture doBind(final SocketAddress localAddress) {
        final ChannelFuture regFuture = initAndRegister();
        ...
        if (regFuture.isDone()) {
            ....
            doBind0(regFuture, channel, localAddress, promise);
            return ...;
        } else {
			...
            regFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (cause != null) {
						...
                    } else {
                        doBind0(regFuture, channel, localAddress, promise);
                    }
                }
            });
            return ...;
        }
    }

2.initAndRegister()

这个方法主要是处理channel的初始化与注册的.还是一样,先大体上看下它的主体流程是怎么样的。
大概分为两步,
第一步是init(Channel channel) 初始化channel
第二步是register(Channel channel) 将channel注册,(这个方法会与java的nio关联上)
其实从名字上面我们也能看出它内部的主要功能,见名知意。

我们先来看看第一步

2.1 init(Channel channel)

要初始化channel 首先我们要有一个channel才行,在代码中是通过channelFactory产生的channel,所以我们只要找到了工厂的实现就知道这个channel是怎么产生的了。

final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            channel = channelFactory.newChannel();
            init(channel);
        } catch (Throwable t) {
			...
        }

    }

channelFactory的实现叫做ReflectiveChannelFactory,通过名字我们就知道这是一个通过反射的方式生产channel的工厂,它是通过获取一个class的无参构造那生产channel的,因此ReflectiveChannelFactory构造器的入参class就决定了这个工厂能生产什么样的channel。
在实际的寻找过程中发现,BootServerstrp的channel方法会调用ReflectiveChannelFactory的构造器,而channel方法传入的class就是这个工厂能生产的channel。

    public B channel(Class<? extends C> channelClass) {
        if (channelClass == null) {
            throw new NullPointerException("channelClass");
        }
        return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
    }

选择通过工厂的方式生产channel就决定了一个BootServerstrap可以同时监听多个端口号。(如果这里选择的是直接new一个channel 那么就注定一个BootServerstrap只能监听一个端口号。当然实际实现并不会这么做,只是通过参数的一种推导,虽然有些 多余。。。)

废话这么多,终于要进入init方法内部了。
首先将BootServerstrap设置的option参数(可以查看ChannelOption看具体有哪些参数),arr就相当于java nio中channel的attachment(就是绑定这个channel的信息,当持有这个channel的时候就可以获取到这个消息)。
然后就是childOptionchildArr,这两个是什么东西呢?其实意思一样,但是作用的channel不一样,我们知道在java nio中ServerSocket收到accept消息后我们会通过accept方法获取到一个Scoket,而这个childOption和childArr就是作用于Netty收到Accept请求后创建的NioSocketChannel上的附加属性。

    @Override
   void init(Channel channel) {
   	//设置等待accept的channel参数
       setChannelOptions(channel, options0().entrySet().toArray(EMPTY_OPTION_ARRAY), logger);
       setAttributes(channel, attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));
       
       ChannelPipeline p = channel.pipeline();
       //现在只是拷贝child参数
       final EventLoopGroup currentChildGroup = childGroup;
       final ChannelHandler currentChildHandler = childHandler;
       final Entry<ChannelOption<?>, Object>[] currentChildOptions =
               childOptions.entrySet().toArray(EMPTY_OPTION_ARRAY);
       final Entry<AttributeKey<?>, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY);
       ....
   }

这里出现了一个新类叫做ChannelPipeline,现在可以先这样理解它,我们通过channel读取或写入的数据都会经过它的内部,而它的内部会有一些列handler专门去处理我们读取或写入的数据,当然我们也可以定义一些handler加入到pipeline里面去。(不仅仅是读写,还有一些列事件,后面在逐一分析)。

前面我们聊到java nio中ServerSocketChannel会在接收到Accept事件后 在注册一个SocketChanel,那么在netty中这个注册SocketChannel动作就应该放在ChannelPipeline的末尾(我以为会有个事件是Acceptable,但是在inBoundHandler里面没有找到这个方法),而这个handler就是ServerBootstrapAcceptor,而它的处理逻辑是在channelRead这个事件中,(accept连接会触发一次读取?这个没研究过。)。
然后就是通过ChannelInitializer的init方法往pipeline中注册handler(为什么不是直接添加handler 而是选择在外面包装一层?两则的唯一区别就在于时间点不同,add是当场就加入到pipeline中,而包装一层后需要有人调用init方法才能加入到pipeline中,感兴趣的可以尝试从这个角度去思考下原因。如果后面能确切的找到理由在来补上。。。),但是又一个反直觉的操作出现了,init中也不是直接加入,而是通过execute等待加入。(我认为可能是保证这个Handler一定被加载在最后面的一个)

    @Override
    void init(Channel channel) throws Exception {
		...
        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) throws Exception {
                final ChannelPipeline pipeline = ch.pipeline();
				...
                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }

就此init方法完毕了。

2.2 register(Channel channel)

当我们把前置条件都处理完毕后就进入注册流程,这里获取了一个group,然后通过这个group进行register操作,这个Group其实就是一个线程池(也就是人们提到的BossGroup,我们后面讲称呼他为parentGroup),主要处理一些前置事情,比如注册,连接,pipeline的handler注册等。
在上面一段我们知道在TCP中通信分为2步,第一步绑定端口的NioServerSocketChannel等待连接,第二步(或N步,因为可以有很多的客户端来连接)创建A对B的NioSocketChannel。

NioServerSocketChannel
NioSocketChannel

而第一步里面需要进行处理的一些列事情都是交给parentGroup这个线程池去处理,
第二步里面的事情就是交给childGroup(也有人称呼为wokerGroup)去处理。
为什么分为两个?因为一般来说读写操作会涉及到后续的业务流程,相比于简单的连接处理是非常耗时的。所以就将accept事件独立出一个线程池(一般都是单线程的)专门处理这类请求。

final ChannelFuture initAndRegister() {
   		...
        ChannelFuture regFuture = config().group().register(channel);
       	...
    }

好了,方法的前半部分我们已经知道了。那么我们就康康,register方法内部究竟在做什么。

首先我们获得了线程池中的一个线程eventLoop,然后通过channel方法里面的unsafe进行了register操作

 @Override
    public ChannelFuture register(final ChannelPromise promise) {
        ObjectUtil.checkNotNull(promise, "promise");
        //不同的unsafe实现由不同的channel自己实现,所以往最上层找就是了
        promise.channel().unsafe().register(this, promise);
        return promise;
    }

那么具体实现应该就在 unsafe.register() 里面了

我们还是分步走,第一步将这个channel与eventLoop进行绑定,以后channel的所有操作都交给这个eventLoop来处理,那么我们就不需要考虑并发问题。
第二步访问register0方法进行注册。

       @Override
        public final void register(EventLoop eventLoop, final ChannelPromise promise) {
 			...
            //1.将eventLoop与channel进行绑定
            AbstractChannel.this.eventLoop = eventLoop;
			
			//2.进行注册
            if (eventLoop.inEventLoop()) {
                register0(promise);
            } else {
                try {
                    eventLoop.execute(new Runnable() {
                        @Override
                        public void run() {
                            register0(promise);
                        }
                    });
                } catch (Throwable t) {
                   	...
                }
            }
        }

在第二步register0进行注册的流程中有分为了好几步,分别是

  1. 注册
  2. 将这个channel的pipeline中的handler全部从ChannlInitializer中释放出来加入到pipiLine中。
  3. 然后访问pipeline中所有handler的registered方法
private void register0(ChannelPromise promise) {
            ...
            //1.注册
            doRegister();
            ...
            //2.往pipeLine中添加handle
            pipeline.invokeHandlerAddedIfNeeded();	 
            ...     
            //3.访问pipeline中的所有handler的register方法
            pipeline.fireChannelRegistered();
            ...
}

需要注意的是,在注册时,设置channel感兴趣的事件是0,也就是没有。后面会根据具体的channel类型设置兴趣事件(可以看下具体实现的构造参数,他们会自己设置感兴趣的事件)。

好了,就此第一步注册算是结束了,我们后面就来看下bind的流程。

3.doBind0

这个方法内部很简单,向这个channel绑定的eventLoopGroup中添加一个bind任务。

private static void doBind0(
            final ChannelFuture regFuture, final Channel channel,
            final SocketAddress localAddress, final ChannelPromise promise) {
        //channelRegistered()方法将会先与下面的触发(因为这些时间都在同一个eventLoop中)
        //这样给了用户一种在bind前可以对channel操作的手段
        channel.eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                if (regFuture.isSuccess()) {
                    channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
                } else {
                    promise.setFailure(regFuture.cause());
                }
            }
        });
    }  

这时候我们的channel就会访问自身所有的pipeline中handler的bind方法,那么现在有多少个handler呢? 答案是 3+N,N就是你自己定义的handler,而3分别是
一个头部HeadContext,一个尾部TailContext,以及一个上面提到的ServerBootstrapAcceptor
那么我们就来看下他们的bind方法都实现了什么。

第一步HeadContext
访问HeadContext的bind方法 ,它调用了unsafe.bind()。分别执行doBind,pipeline.fireChannelActive()方法,doBind内部对于java1.7和1.7以上的有不同的bind方式。
第二步ServerBootStrapAcceptor,不做任何事情。
最后一步TailContext,它是个InboundHandler不处理这种事件。(InBoundHandler与OutBandHandler后面再聊)

至此整个bind流程就完了。

4.总结

我们在来从整体上梳理一下流程

第一层

initAndRegister-初始化与注册
bind-channel与端口进行绑定

第二层initAndRegister

init-设置channel参数
register-进行注册

第三层init

设置自身option和arr
设置child的option和arr
将handler设置到pipeline中
ServerBooststrapAdapter加入pipeline中

第三层register

对channel进行注册
将handler从ChannelInitializer中添加到pipeline中
通知registered事件

第二层bind

异步访问pipeline中所有的bind方法

第三层bind

第一个handler-HeadContext
进行bind
通知active事件
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

唐芬奇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值