netty極簡教程(六):Netty是如何屏蔽ServerSocketChannel啓動的

現在我們已經基本瞭解了netty底層使用的組件,就明白了netty爲什麼是事件驅動模型:(netty極簡教程(四):netty極簡教程(五):Netty的Reactor模型演進及JDK nio聊天室實現
接下來追蹤下netty的啓動源碼,驗證reactor模型在netty的實現


示例源碼: https://github.com/jsbintask22/netty-learning

示例

我們以第一節打印客戶端信息的代碼爲例:

NioEventLoopGroup bossLoopGroup = new NioEventLoopGroup(1);  // 1
NioEventLoopGroup workLoopGroup = new NioEventLoopGroup();  // 2

try {
    ServerBootstrap serverBootstrap = new ServerBootstrap();
    serverBootstrap.group(bossLoopGroup, workLoopGroup)
            .channel(NioServerSocketChannel.class)          // 3
            .childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {    // 9
                        @Override
                        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                            if (msg instanceof ByteBuf) {
                                System.out.println("client: " + ((ByteBuf) msg).toString(StandardCharsets.UTF_8));
                            }
                        }
                    });
                }
            })
            .option(ChannelOption.SO_BACKLOG, 128)          // 4
            .childOption(ChannelOption.SO_KEEPALIVE, true);  // 5

    ChannelFuture channelFuture = serverBootstrap.bind(port).addListener(f -> {   // 6
        System.out.println("started.");
    }).sync();

    channelFuture.channel().closeFuture().addListener(future -> 
    System.out.println("DiscardServerApp.operationComplete")).sync();   // 7
} finally {
    bossLoopGroup.shutdownGracefully();       // 8
    workLoopGroup.shutdownGracefully();
}

啓動解析


上面這段代碼將客戶端發送信息打印出來,並沒有迴應任何消息,所以叫DiscardServer,因爲我們使用http客戶端發送,所以出現了超時現象,我們對比上一節將Netty 中使用的原生組件一一找出;首先是我們一眼就能看到的:

  1. 在原生Selector中,我們new了一個主線程(主Reactor線程) 用來一直循環Selector的select操作並且註冊ServerSocketChannel,在netty 中,我們稱這個線程爲boss線程,對應這裏的bossLoopGroup線程組,因爲我們只需要一個ServerSocketChannel,所以我們直接將該線程組數量設置爲1
  2. 在原生Selector中,爲了防止阻塞主線程,我們又使用了一個有8個線程的數組(子Reactor線程___)(爲什麼是8?),並且生成了同樣個數的的Selector ,這個線程組對應我們這裏的workLoopGroup線程組,在netty中,它默認的線程個數是cpu核數*2;

說到這裏,雖然我們還沒有介紹NioEventLoopGroup,估計大家已經知道了它就是一個線程池:

  1. 在netty中,不再直接使用ServerSocketChannel,而是netty封裝的NioServerSocketChannel(之後介紹),它會在boss線程中生成
  2. 在原生nio中,綁定端口之前可以給ServerSocketChannel配置一些參數,這些參數在java.net.StandardSocketOptions中可以找到,在netty 中使用ChannelOption進行配置
  3. 同4,它是netty給連接的客戶端socket配置參數使用
  4. 類比原生的bind方法,netty使用異步回調操作。
  5. 同樣給關閉服務設置回調,並且使用 sync同步方法阻塞main線程。
  6. 必須關閉兩個線程池

上面8點我們直接可以在main線程中觀察到,可是關鍵的ServerSocketChannel初始化,註冊Selector綁定到本地端口,accept接收客戶端這些代碼還沒有找出來;我們繼續;從serverBootstrap .bind(port)開始追蹤:看它們是如何在線程池中被初始化的;首先需要說明的是,我們已經知道,在原生jdk中一個連接的抽象代表是java.nio.channels.Channel,而在netty中,它被封裝成了io.netty.channel.Channel,後面的介紹全部默認爲netty的Channel。


從bind追蹤,我們可以看到兩個較爲關鍵的步驟:


initAndRegister:

final ChannelFuture initAndRegister() {  
Channel channel = null;  
channel = channelFactory.newChannel();   // 1  
init(channel);   // 2  
ChannelFuture regFuture = config().group().register(channel); // 3  
  • 使用channelFactory生成了NioServerSocketChannel,容易知道這個channelFactory是根據我們配置的NioServerSocketChannel 使用反射調用默認構造方法生成;


    ok,到這裏我們找到了原生jdk的ServerSocketChannel的生成,並且可通過javaChannel()方法獲取;

  • 生成channel之後開始初始化(設置ServerSocketChannel的參數等),這裏值得注意的是,每一個channel中有一個ChannelPipeline對象(後面介紹該對象,每一個channel對應一個pipeline,默認構造其中生成的),接着往該pipeline中添加了一個handler(在pipeline中有一個頭和尾已經確定的handler 的鏈表,加在鏈尾的前一個),這個handler是ServerBootstrapAcceptor,所以當有新連接進入時,繼續將新SocketChannel註冊到Selector上。

  • 最後會在boss線程中添加一個任務:


    這樣,就將ServerSocketChannel也主動註冊到了Selector(注意上面的步驟是將客戶端連接註冊到Selector,Selector怎麼得來的後面細講)

doBind0:
最終調用io.netty.channel.AbstractChannel.AbstractUnsafe的bind方法,最後在unsafe中調用ServerSocket的doBind方法:


這樣,服務端的ServerSocketChannel就綁定監聽端口成功了;


到現在,我們已經知道了原生jdk中的reactor主線程以及io子線程在netty中的對應之處,以及ServerSocketChannel是怎麼被生成並且註冊到Selector
上並且時如何綁定到端口上的;接下來還有一個關鍵地方我們沒有找出來,就是selector的循環select是在哪裏被調用了,畢竟我們再上一章就知道了操作channel全靠selector;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章