1. 程式人生 > 其它 >【Netty】(6) ---原始碼ServerBootstrap

【Netty】(6) ---原始碼ServerBootstrap

【Netty】6 ---原始碼ServerBootstrap

之前寫了兩篇與Bootstrap相關的文章,一篇是ServerBootstrap的父類,一篇是客戶端Bootstrap類,部落格地址:

【Netty】原始碼AbstractBootstrap
【Netty】原始碼 Bootstrap

所以接下來 有關ServerBootstrap 原始碼的分析,如果上面已經分析過了,就不再陳述。

一、概念

ServerBootstrap可以理解為伺服器啟動的工廠類,我們可以通過它來完成伺服器端的 Netty 初始化。
作用職責:EventLoop初始化,channel的註冊過程,關於pipeline的初始化

,handler的新增過程,服務端連線分析。
下面也先看下原始碼

// 定義一對執行緒組
​         // 主執行緒組, 用於接受客戶端的連線,但是不做任何處理,跟老闆一樣,不做事
​         EventLoopGroup bossGroup = new NioEventLoopGroup();
​         // 從執行緒組, 老闆執行緒組會把任務丟給他,讓手下執行緒組去做任務
​         EventLoopGroup workerGroup = new NioEventLoopGroup();

     try {
         // netty伺服器的建立, 輔助工具類,用於伺服器通道的一系列配置
         ServerBootstrap serverBootstrap = new ServerBootstrap();
         serverBootstrap.group(bossGroup, workerGroup)           //繫結兩個執行緒組
                        .channel(NioServerSocketChannel.class)   //指定NIO的模式
                        .handler(new LoggingHandler(LogLevel.INFO))
                        .childHandler(new HelloServerInitializer()); // 子處理器,用於處理workerGroup

         // 啟動server,並且設定8088為啟動的埠號,同時啟動方式為同步
         ChannelFuture channelFuture = serverBootstrap.bind(8088).sync();

         // 監聽關閉的channel,設定位同步方式
         channelFuture.channel().closeFuture().sync();
     } finally {
         //退出執行緒組
         bossGroup.shutdownGracefully();
         workerGroup.shutdownGracefully();
     }
 }

二、原始碼解析

1、group(bossGroup, workerGroup)

這裡跟客戶端明顯的一個區別就是,客戶端只傳入了一個NioEventLoopGroup,而服務端傳入了兩個。

 /**
  * 這裡呼叫的是 ServerBootstrap 類本身的 group 方法 發現傳入的兩個EventLoopGroup
  * 一個賦值給父類(AbstractBootstrap),另一個賦值給 該物件本身屬性
  */
 public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {

     //呼叫父類的group方法
     super.group(parentGroup);
     if (childGroup == null) {
         throw new NullPointerException("childGroup");
     }
     if (this.childGroup != null) {
         throw new IllegalStateException("childGroup set already");
     }
     this.childGroup = childGroup;
     return this;
 }

而在伺服器端的初始化時, 我們設定一個是 bossGroup, 另一個是 workerGroup. 那麼這兩個 EventLoopGroup 到底是怎麼分工的?
bossGroup 是用於服務端 的 accept 的, 即用於處理客戶端的連線請求.workerGroup 它們負責客戶端連線通道的 IO 操作
關於 bossGroup 與 workerGroup 的關係, 我們可以用如下圖來展示(盜圖):

首先, 伺服器端 bossGroup 不斷地監聽是否有客戶端的連線, 當發現有一個新的客戶端連線到來時, bossGroup 就會為此連線初始化各項資源,然後從 workerGroup 中選出一個 EventLoop 繫結到此客戶端連線中. 那麼接下來的伺服器與客戶端的互動過程就全部在此分配的 EventLoop 中了。至於 bossGroup 和 workerGroup 和 channel 如何聯絡到一起的,等下面再講bind(host)方法的時候在用原始碼展示,因為是通過bind(host)開始將他們聯絡到一起的。

2、channel(NioServerSocketChannel.class)方法

這裡和上一篇說的channl是差不多的,具體原始碼可以看上一篇就可以了,只是這裡傳入的類是NioServerSocketChannel,而客戶端是NioSocketChannel,但他們都是通過類的反射
機制獲得類的物件的。同樣真正用到該物件的時候,也是在bind(host)方法裡。

有關NioServerSocketChannel物件和之前的NioSocketChannel物件本身是沒有講過的。

3、handler()和childHandler()

我們跟客戶端比較發現還是有明顯區別的, 和 EventLoopGroup 一樣, 伺服器端的 handler 也有兩個, 一個是通過 handler() 方法設定 handler 欄位, 另一個是通過
childHandler() 設定 childHandler 欄位。不過handler()方法並不是必須的,而childHandler()方法是必須呼叫的
看程式碼

    /**handler(new LoggingHandler(LogLevel.INFO))
     * 
     * 我們發現channel方法呼叫的是父類(AbstractBootstrap)的方法
     * 所以這個 handler  欄位與 accept 過程有關, 即這個 handler 負責處理客戶端的連線請求
     */
    public B handler(ChannelHandler handler) {
        if (handler == null) {
            throw new NullPointerException("handler");
        }
        this.handler = handler;
        return self();
    }


    /** 再看childHandler(class)
     * 
     *很明顯 這個childHandler 方法是屬於ServerBootstrap 本身的方法
     * 所以推測: 這個childHandler 就是負責和客戶端的連線的 IO 互動
     */
    public ServerBootstrap childHandler(ChannelHandler childHandler) {
        if (childHandler == null) {
            throw new NullPointerException("childHandler");
        }
        this.childHandler = childHandler;
        return this;
    }

有關handler和childHandler在哪個地方會被運用,等下將bind()方法的時候,我們在看他的原始碼。

4、bind(host)方法

bind(host)才是整個流程的關鍵,前面做得只是初始化了一些netty客戶端執行的物件(可以理解成只是建立了物件,並沒有使用它),但真正用到這些這些物件,
還是在bind(host)方法裡。我們一步一步跟著原始碼走,裡面會省略一些不重要的程式碼

        /**
         * 1、呼叫父類(AbstractBootstrap)的方法
         * <p>
         * 作用: 根據埠號 建立一個InetSocketAddress物件,用於連線連線伺服器
         */
        public ChannelFuture bind(int inetPort) {
            return bind(new InetSocketAddress(inetPort));
        }

        /**
         * 2、繼續呼叫父類(AbstractBootstrap)的方法
         * <p>
         * 作用: 做一些校驗工作
         */
        public ChannelFuture bind(SocketAddress localAddress) {
            validate();
            if (localAddress == null) {
                throw new NullPointerException("localAddress");
            }
            return doBind(localAddress);
        }


        /**
         * 3、繼續呼叫父類(AbstractBootstrap)的方法
         * <p>
         * 作用: 這個方法做了很多事情
         */
        private ChannelFuture doBind(final SocketAddress localAddress) {
            //3、1 具體看下面3、1的程式碼部分
            final ChannelFuture regFuture = initAndRegister();
            final Channel channel = regFuture.channel();

            ChannelPromise promise = channel.newPromise();
            doBind0(regFuture, channel, localAddress, promise);
            return promise;
        }
    }

    /**
     * 3、1  這步做了很多重要的事情
     */
    final ChannelFuture initAndRegister() {
        Channel channel = null;

        //這裡終於呼叫newChannel方法了,這裡就是之前BootStrap講的ReflectiveChannelFactory物件的方法,這裡的
        //channel 物件是NioServerSocketChannel。
        channel = channelFactory.newChannel();
        //這個方法也太重要了 和handle有關 下面3.1.1 講它
        init(channel);

        //這裡的group()獲取的就是bootstrap ,這裡面會呼叫next方法 來迴圈獲取下一個channel 具體的我就不點進去分析了
        //這裡group().register(channel) 將 bossGroup 和 NioServerSocketChannel 關聯起來了.
        ChannelFuture regFuture = config().group().register(channel);

        return regFuture;
    }

    /**
     * 3.1.1 首先可以看到into的方法在父類(AbstractBootstrap)已經提供,只是子類寫具體實現程式碼
     */
    abstract void init(Channel channel) throws Exception;

    /**
     * 我們再來看ServerBootstrap實現了init方法,這裡面做了很多事
     * 比如workerGroup相關,還有handel相關
     */
    @Override
    void init(Channel channel) throws Exception {

        //通過channel獲得ChannelPipeline,說明每一個channel都會對應一個ChannelPipeline
        ChannelPipeline p = channel.pipeline();

        //這裡終於獲得workerGroup 物件
        final EventLoopGroup currentChildGroup = childGroup;
        //這裡獲得childHandler物件
        final ChannelHandler currentChildHandler = childHandler;
        final Entry<ChannelOption<?>, Object>[] currentChildOptions;
        final Entry<AttributeKey<?>, Object>[] currentChildAttrs;

        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) throws Exception {
                final ChannelPipeline pipeline = ch.pipeline();
                //獲得handel方法傳入的物件
                ChannelHandler handler = config.handler();

                //這一步說明 .handler(new LoggingHandler(LogLevel.INFO))方法不是必須要的
                //如果你沒有調handler方法也沒有關係 ,因為它會在這路做一層判斷
                if (handler != null) {
                    pipeline.addLast(handler);
                }

                //到這裡執行緒就開始啟動運行了 發現已經講Channel,ChannelPipeline,workerGroup,childHandler等全部聯絡到了一起。
                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }

原始碼部落格推薦

有些原始碼自己也沒有去分析 比如:NioServerSocketChannel物件本身,ChannelPipeline物件。