zoukankan      html  css  js  c++  java
  • 5.不完全的 ServerBootstrap 启动过程源码分析

    通过名字可以看出它是一个服务端的引导, 就是为了引导创建 Channel.

    Bootstrap 的继承结构

    在 netty 的代码中, 类 ServerBootstrap 和类 Bootstrap 都继承自基类 AbstractBootstrap:

    channel类型 用于引导的bootstrap实现类
    ServerChannel ServerBootstrap
    Channel Bootstrap

    引导启动流程

    实例化 ServerBootstrap 后会调用, group 方法来设置 NioEventLoopGroup.

    也就是说 group 方法必须调用, 否则会出现 java.lang.IllegalStateException: group not set 异常.

    public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
        super.group(parentGroup);
        if (this.childGroup != null) {
            throw new IllegalStateException("childGroup set already");
        }
        this.childGroup = ObjectUtil.checkNotNull(childGroup, "childGroup");
        return this;
    }
    

    group 方法只能被调用一次, 否则也会抛出异常.

    在本篇笔记最开始有说过, 引导器为了引导创建 Channel, 而创建哪个 Channel 是通过 channel 方法确定.

    该方法最终会调用 channelFactory 方法并会给 channelFactory 变量赋值, 最终会通过反射调用无参构造函数创建对应的实例.

    @Deprecated
    public B channelFactory(ChannelFactory<? extends C> channelFactory) {
        ObjectUtil.checkNotNull(channelFactory, "channelFactory");
        if (this.channelFactory != null) {
            throw new IllegalStateException("channelFactory set already");
        }
    
        this.channelFactory = channelFactory;
        return self();
    }
    

    optionchildOption 都是 ConcurrentHashMap 类型, 是给对应的 io.netty.channel.AbstractChannel 和 Java 中的 SocketSocketChannel 以及 SocketChannel 设置参数的.

    最后在设置一些 ChannelHandler 用于处理 java.nio.channels.SocketChannel 请求, 该方法是必须. 但是还有一个与之类似的 handler() 方法, 该方法不是必须的, 主要用来处理 accept 过程.

    使用 bind 方法来绑定监听端口, 最终会调用 doBind 方法.

    final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            /*
             * 通过 ChannelFactory 创建对应的 NioServerSocketChannel,
             * 通过该类的无参构造方法创建 sun.nio.ch.ServerSocketChannelImpl 
             * 并设置 configureBlocking 为 false, 
             * 以及创建 DefaultChannelPipeline 和 NioMessageUnsafe.
             */
            channel = channelFactory.newChannel();
            // 该方法是抽象方法, 由于是创建服务端则会调用 ServerBootstrap 中的实现.
            init(channel);
        } catch (Throwable t) {
            if (channel != null) {
                channel.unsafe().closeForcibly();
                return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
            }
            return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
        }
    
        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }
    
        return regFuture;
    }
    

    config().group().register(channel), 之前两个方法的返回值是啥, 就不说了主要说一下 register 方法. 该方法会做以下操作:

    1. 从 bossGroup 中获取一个 EventLoop 后将 NioServerSocketChannel 与当前的 EventLoop 包装成 DefaultChannelPromise.
    2. 然后通过 NioMessageUnsafe 实例中的 register 方法, 将 ServerSocketChannelImpl 注册到 Selector.
      注意: 这里只是注册, 并不监听任何事件.
    3. 如果是首次注册 NioServerSocketChannel, 则会执行之前的 ChannelInitializer, 也就是下面 init 方法中添加的.
    4. 调用 ChannelHandler 的 channelRegistered 方法, 表示 NioServerSocketChannel 注册完成, 也就是注册到了 EventLoop 上.

    下面是 ServerBootstrap 中的 init 方法实现.

    // 这里的参数就是 NioServerSocketChannel
    void init(Channel channel) {
        // 设置 Channel 的 Option, 会使用 option 方法设置的参数.
        setChannelOptions(channel, options0().entrySet().toArray(EMPTY_OPTION_ARRAY), logger);
        // 设置 Channel 的 Attribute, 这个就是 业务数据 随着业务流转, 在特定地方可以取出.
        setAttributes(channel, attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));
    
        ChannelPipeline p = channel.pipeline();
    
        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);
    
        // 将这个 ChannelHandle 添加到 ChannelPipeline 的尾部.
        // 注意: 这里是 Channel 注册之前添加的任务, 还没有执行.
        p.addLast(new ChannelInitializer<Channel>() {
            // 注意该方法, 该方法在执行完成后, 该实例会从 ChannelPipeline 中移除.
            @Override
            public void initChannel(final Channel ch) {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = config.handler();
                if (handler != null) {
                    // 添加 ServerBootstrap 中 handler 的 ChannelHandler.
                    pipeline.addLast(handler);
                }
    
                // 给服务端的 eventLoop 添加一个任务.
                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        /**
                         * 这个类非常重要.
                         * 
                         * 会给 NioSocketChannel 中的 pipeline 添加 ChannelHandler.
                         * 设置 NioSocketChannel 和 SocketChannel 的 option, 以及设置业务数据.
                         */
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }
    

    initAndRegister() 方法执行完成后, 才会执行 doBind0 来绑定端口和设置 Channel 关心事件.

    下面是我自己画的简易启动过程, 配合上面看.

    讨论

    1.bossGroup 设置多个线程, 只会使用其中一个还是都会使用?
    你可能会看到有些例子中会使用有参构造方法来创建 bossGroup , 例如 new NioEventLoopGroup(1); 当然有些例子也会也是无参构造方法.
    当然有些文章也会说 Netty 支持三种线程模型, 说的有理有据, 然你无法反驳.
    但是我个人认为 Netty 只有一种线程模型, 就是从 主从 Reactor 多线程 模型演变来的.
    因此 bossGroup 设置多个线程, 也只会使用其中一个.

    2.如何让 bossGroup 使用多个线程来处理连接
    后续更新, 因为我也不知道.

    ChannelOption 参数

    public static final ChannelOption<ByteBufAllocator> ALLOCATOR = valueOf("ALLOCATOR");
    public static final ChannelOption<RecvByteBufAllocator> RCVBUF_ALLOCATOR = valueOf("RCVBUF_ALLOCATOR");
    public static final ChannelOption<MessageSizeEstimator> MESSAGE_SIZE_ESTIMATOR = valueOf("MESSAGE_SIZE_ESTIMATOR");
    public static final ChannelOption<Integer> CONNECT_TIMEOUT_MILLIS = valueOf("CONNECT_TIMEOUT_MILLIS");
    public static final ChannelOption<Integer> WRITE_SPIN_COUNT = valueOf("WRITE_SPIN_COUNT");
    public static final ChannelOption<WriteBufferWaterMark> WRITE_BUFFER_WATER_MARK = valueOf("WRITE_BUFFER_WATER_MARK");
    public static final ChannelOption<Boolean> ALLOW_HALF_CLOSURE = valueOf("ALLOW_HALF_CLOSURE");
    public static final ChannelOption<Boolean> AUTO_READ = valueOf("AUTO_READ");
    public static final ChannelOption<Boolean> AUTO_CLOSE = valueOf("AUTO_CLOSE");
    public static final ChannelOption<Boolean> SO_BROADCAST = valueOf("SO_BROADCAST");
    public static final ChannelOption<Boolean> SO_KEEPALIVE = valueOf("SO_KEEPALIVE");
    public static final ChannelOption<Integer> SO_SNDBUF = valueOf("SO_SNDBUF");
    public static final ChannelOption<Integer> SO_RCVBUF = valueOf("SO_RCVBUF");
    public static final ChannelOption<Boolean> SO_REUSEADDR = valueOf("SO_REUSEADDR");
    public static final ChannelOption<Integer> SO_LINGER = valueOf("SO_LINGER");
    public static final ChannelOption<Integer> SO_BACKLOG = valueOf("SO_BACKLOG");
    public static final ChannelOption<Integer> SO_TIMEOUT = valueOf("SO_TIMEOUT");
    public static final ChannelOption<Integer> IP_TOS = valueOf("IP_TOS");
    public static final ChannelOption<InetAddress> IP_MULTICAST_ADDR = valueOf("IP_MULTICAST_ADDR");
    public static final ChannelOption<NetworkInterface> IP_MULTICAST_IF = valueOf("IP_MULTICAST_IF");
    public static final ChannelOption<Integer> IP_MULTICAST_TTL = valueOf("IP_MULTICAST_TTL");
    public static final ChannelOption<Boolean> IP_MULTICAST_LOOP_DISABLED = valueOf("IP_MULTICAST_LOOP_DISABLED");
    public static final ChannelOption<Boolean> TCP_NODELAY = valueOf("TCP_NODELAY");
    public static final ChannelOption<Boolean> SINGLE_EVENTEXECUTOR_PER_GROUP = valueOf("SINGLE_EVENTEXECUTOR_PER_GROUP");
    

    参考资料

    Bootstrap

  • 相关阅读:
    测试数据生成利器
    9.22“月饼杯”递归算法欢乐赛测试报告总结
    lemon评测软件配置使用方法
    1200:分解因数
    大犇博客
    C++ |递归原理与构造技巧
    2018.9.8信息奥赛集训评测报告总结
    1195 判断整除
    计算机图形学初步
    AcWing
  • 原文地址:https://www.cnblogs.com/scikstack/p/13524072.html
Copyright © 2011-2022 走看看