Netty 源码分析1.3 ServerBootstrap的bind方法

###################################################################################
后续文章中都会对一些代码逻辑进行说明,但原文的英文注释一般不会直译,进行保留,只会说明那些没有注释的地方
###################################################################################

本文中关联的所有文章的总目录可以参看系列文章目录

1前言

      前面我们已经了解了NioEventLoopGroup这个类的创建流程以及其中的业务。如果没有了解的可以参看这个连接:Netty 源码分析一 NioEventLoopGroup创建逻辑
      而看完上面的类创建后,我们需要了解更多的该类功能,而该类的功能我们不可能一个方法一个方法的看其被什么地方调用,这样太乱,我们还是按DiscardServer类中的代码逻辑来看

ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc()));
                     }
                     p.addLast(new DiscardServerHandler());
                 }
             });

      上面这一块代码跟NioEventLoopGroup相关的也就是调用ServerBootstrap 类的group方法,这个方法其实只是进行了一些内部变量的赋值,没有什么特殊的额外的逻辑。那我们就再看bind方法

b.bind(PORT)

2 bind方法逻辑

2.1 方法的执行的序列图

在这里插入图片描述
      从其中的序列图中可以看出,主要的逻辑还是在AbstractBootstrap这个父类的doBind(final SocketAddress localAddress)方法中来执行的;我们来具体看下这块的代码

2.1.1 doBind方法

private ChannelFuture doBind(final SocketAddress localAddress) {
        // 这里就是根据b.channel(NioServerSocketChannel.class)设置的渠道类型实例化该类,
        // 并且对这个类进行一些初始化设置,具体的大致流程可以参看上面的序列图中4~11这些步骤,
        // 返回的实际上是DefaultChannelPromise这个对象.
        final ChannelFuture regFuture = initAndRegister();
        // 实际上调用的是DefaultChannelPromise 类中的channel,而该类中设置的channel是NioServerSocketChannel
        final Channel channel = regFuture.channel();
        if (regFuture.cause() != null) {
            // 如果前端register没有问题,此处就不会进来
            return regFuture;
        }

        if (regFuture.isDone()) {
            // At this point we know that the registration was complete and successful.
            ChannelPromise promise = channel.newPromise();
            doBind0(regFuture, channel, localAddress, promise);
            return promise;
        } else {
            // Registration future is almost always fulfilled already, but just in case it's not.
            final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
            regFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    Throwable cause = future.cause();
                    if (cause != null) {
                        // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
                        // IllegalStateException once we try to access the EventLoop of the Channel.
                        promise.setFailure(cause);
                    } else {
                        // Registration was successful, so set the correct executor to use.
                        // See https://github.com/netty/netty/issues/2586
                        promise.registered();

                        doBind0(regFuture, channel, localAddress, promise);
                    }
                }
            });
            return promise;
        }
    }

1. doBind方法内部的initAndRegister 方法
      上面方法代码第一条就是执行initAndRegister方法,而该方法的大致逻辑
就是根据b.channel(NioServerSocketChannel.class)设置的渠道类型实例化该NioServerSocketChannel类,并且对这个类进行一些初始化设置,具体的大致流程可以参看上面的序列图中12~15这些步骤,而详细的代码如下所示:

final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
        	// 实例化该NioServerSocketChannel类
            channel = channelFactory.newChannel();
            // 调用其子类ServerBootstrap中的一些ServerSocketChannel渠道的设置
            init(channel);
        } catch (Throwable t) {
            if (channel != null) {
                // channel can be null if newChannel crashed (eg SocketException("too many open files"))
                channel.unsafe().closeForcibly();
                // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
                return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
            }
            // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
            return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
        }

		// 1. config()是调用其父类的这个重写方法,而该方法实际上创建的是ServerBootstrapConfig(ServerBootstrap) 这个类; 
		// 2. ServerBootstrapConfig.group()方法实际上调用的就是ServerBootstrap类中赋值的parentGroup所对应的NioEventLoopGroup这个对象;
		// 3. register(channel), 实际上执行的就是NioEventLoopGroup类的register方法,传入的是 NioServerSocketChannel这个对象  
	   // 4. 最后返回的regFuture 是DefaultChannelPromise这个类的对象数据
		ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }

        // If we are here and the promise is not failed, it's one of the following cases:
        // 1) If we attempted registration from the event loop, the registration has been completed at this point.
        //    i.e. It's safe to attempt bind() or connect() now because the channel has been registered.
        // 2) If we attempted registration from the other thread, the registration request has been successfully
        //    added to the event loop's task queue for later execution.
        //    i.e. It's safe to attempt bind() or connect() now:
        //         because bind() or connect() will be executed *after* the scheduled registration task is executed
        //         because register(), bind(), and connect() are all bound to the same thread.

        return regFuture;
    }

      至此我们可以看到再次使用NioEventLoopGroup 类的register(Channel) 方法的逻辑,这个里面具体执行了哪些业务,我们放到后续的文章中进行说明,在此先省略;我们继续看后续的业务执行。

1.1 initAndRegister 内部执行的 regFuture.cause()
      而对于regFuture.cause()这句代码,实际上执行的是如下的代码

@Override
    public Throwable cause() {
        return cause0(result);
    }

    private Throwable cause0(Object result) {
    	// 如果前面在执行config().group().register(channel)这行代码注册成功后,
    	// result里面放入的只是一个Object对象,返回result类型不会是CauseHolder,因为方法返回的是null
        if (!(result instanceof CauseHolder)) {
            return null;
        }
        if (result == CANCELLATION_CAUSE_HOLDER) {
            CancellationException ce = new LeanCancellationException();
            if (RESULT_UPDATER.compareAndSet(this, CANCELLATION_CAUSE_HOLDER, new CauseHolder(ce))) {
                return ce;
            }
            result = this.result;
        }
        return ((CauseHolder) result).cause;
    }

1.2 initAndRegister 内部的init 方法执行
      init方法我们通过序列图也可以看出来,它实际上执行的是ServerBootstrap 类中实现的init方法,具体的代码如下所示:

@Override
    void init(Channel channel) {
        // 传入的channel就是在创建服务时设置的NioServerSocketChannel 这个channel对象;该对象是在父类的initAndRegister中进行实例化的;
        setChannelOptions(channel, options0().entrySet().toArray(newOptionArray(0)), logger);
        setAttributes(channel, attrs0().entrySet().toArray(newAttrArray(0)));

        // 而通过NioServerSocketChannel这个类在初始化时构建的对象中知道pipeline这个属性实际指向的是 DefaultChannelPipeline 这个对象
        ChannelPipeline p = channel.pipeline();

        final EventLoopGroup currentChildGroup = this.childGroup;
        final ChannelHandler currentChildHandler = this.childHandler;
        final Entry<ChannelOption<?>, Object>[] currentChildOptions = this.childOptions.entrySet()
            .toArray(newOptionArray(0));
        final Entry<AttributeKey<?>, Object>[] currentChildAttrs = this.childAttrs.entrySet()
            .toArray(newAttrArray(0));

        // 这个是在DefaultChannelPipeline 这个通道中加入新的ChannelHander
        // ======== 1 =====后续特殊说明标识=======
        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) {
                // 这个ch就是NioServerSocketChannel 
                // =========== 2 =====后续特殊说明标识=======
                final ChannelPipeline pipeline = ch.pipeline();
                // config是ServerBootstrapConfig这个类,而这个类其实很简单,就是针对ServerBootstrap设置的信息进行封装,在服务设置时的
                // group,channel,option,handler,childHandler都可以通过该config进行访问;
                // 所以config.handler()这个方法返回的就是ServerBootstrap.handler里面设置的 LoggingHandler这个配置(这个是针对EchoServer这个main方法启动来说的) 
                // ========= 3=====后续特殊说明标识=======
                ChannelHandler handler = ServerBootstrap.this.config.handler();
                if (handler != null) {
                    pipeline.addLast(handler);
                }

                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(ch, currentChildGroup,
                            currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }
  • 上面代码中说明下,针对标识中2的initChannel方法传入的Channel参数进行特殊说明下:
          并不是Channel参数传入的一定会是NioServerSocketChannel这个对象;需要根据上下文的代码来确定传入的具体是哪个Channel;

那上面为什么传入的会是NioServerSocketChannel这个对象了?
      因为在往p.addLast() 也就是上面代码中标识为1的在进行添加时,是通过的DefaultChannelPipeline来添加的,而该类又是NioServerSocketChannel对象中的pipeline;
而ChannelInitializer类的initChannel这个接口方法原文的注释翻译后如下所示:

通道注册后将调用此方法。 方法返回后,该实例将从Channel的ChannelPipeline中删除。

所以在通道注册成功后就会通过通道中的ChannelPipeline来调用这个方法;而NioServerSocketChannel也是一个通道,它在initAndRegister这个方法中我们通过源码可以知道,它最后会被注册;而注册的这块具体逻辑,我们可以参看后面的文章可以提前了解相应的逻辑;Netty 源码分析一 NioEventLoopGroup类的register方法,而针对注册成功后会调用这些通过addLast添加到pipeline里面的ChannelHandler是在代码哪里被调用的,我们可以参看这篇文章的具体分析:

  • 上面代码中针对标识为3的,我在这里特殊说明下,并不是所有的config.handler()返回的都是LoggingHandler这个对象,为什么我在这里说加入的是这个对象了?是因为我这个系统文章都是以EchoServer (代码可以参看 Netty 源码分析入口 这里)这个类里面的代码来说明的;Netty 源码分析一 NioServerSocketChannel类

2. doBind方法中doBind0方法
      当initAndRegister 方法执行完后,我们通过前面的doBind方法的代码也可以看出在NioServerSocketChannel通道注册成功后,会执行doBind0这个方法,而这个方法中大致处理的内容就是当渠道注册成功后,就会进行地址的绑定,并且会添加一个关闭通道时的监听器

private static void doBind0(final ChannelFuture regFuture, final Channel channel,
                                final SocketAddress localAddress, final ChannelPromise promise) {

        // This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up
        // the pipeline in its channelRegistered() implementation.
        channel.eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                if (regFuture.isSuccess()) {
                    // 实际上执行的是NioServerSocketChannel的bind方法
                    channel.bind(localAddress, promise)
                        .addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
                } else {
                    promise.setFailure(regFuture.cause());
                }
            }
        });
    }

通过上面的代码我们可以知道在往NioServerSocketChannel里面的NioEventLoop线程池加入了一个渠道注册的任务;


      至此我们分析了ServerBootstrap的bind方法主要执行的逻辑,而在这个执行逻辑中还涉及到了以下几点的执行:
      NioEventLoopGroup.register 方法的逻辑
      NioServerSocketChannel.bind(SocketAddress localAddress, ChannelPromise promise) 方法的逻辑

      所以下一篇我们还是回归到前一篇的NioEventLoopGroup这个类的源码学习;把这个类学习完后,我们再学习其它相关联的内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值