1. 程式人生 > >[netty原始碼分析]--服務端啟動的工作流程分析

[netty原始碼分析]--服務端啟動的工作流程分析

服務端

1.首先是例項化boss執行緒池和worker執行緒池

例項化的就是 NioEventLoopGroup;這裡我假設boss執行緒池初始化為1個執行緒,worker執行緒初始化為 2*CPU個數的執行緒數。

說一下主要做了什麼工作:

(1)指定了執行緒池中執行緒數、執行緒池的執行器是ThreadPerTaskExecutor;

(2)執行緒池中每個執行緒其實就是一個NioEventLoop,執行緒池指定了每個NioEventLoop都需要的引數:

SelectorProvider, SelectStrategyFactory, RejectedExecutionHandlers.reject()

並將這三個引數封裝成 args 引數傳入NioEventLoop 的構造器中。

(3)核心的例項化過程其實在NioEventLoopGroup的父類MultithreadEventExecutorGroup中,下面的就是介紹MultithreadEventExecutorGroup構造器中主要做了的工作:

(4)例項化執行緒池的執行緒執行器executor,預設是ThreadPerTaskExecutor;

(5)對於執行緒池中的執行緒陣列 children 進行初始化,初始化時傳入了 executor 和 args;初始化的函式核心就是new NioEventLoop物件,如下:

new
NioEventLoop(this, executor, (SelectorProvider) args[0], ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);

從上面可知,執行緒池中所有的執行緒公用executor, SelectorProvider, SelectStrategyFactory以及RejectedExecutionHandler。


這裡需要說明的一點是,執行緒池中所有的執行緒都公用的SelectorProvider, 而SelectorProvider是通過NioEventLoopGroup裡面的構造器裡面的
SelectorProvider.provider(); 方式獲取的, 而這個方法返回的是一個單例的SelectorProvider, 所以所有的NioEventLoop公用同一個單例SelectorProvider。

這個地方從一定的側面能夠說明一些問題,也就是說,對於boss執行緒池,如果在單機情況下,即時配置了多個執行緒,但是多個執行緒公用的是一個SelectorProvider。

(6)下面說明NioEventLoop 執行緒初始化時候做的核心工作:

  • 通過 SelectorProvider 給執行緒構造一個selector;
  • 給每個 NioEventLoop執行緒構造一個 任務佇列taskQueue,預設是一個無界佇列。
  • 指定當前NioEventLoop 所屬於的 NioEventLoopGroup

注意,自此還沒有啟動服務端的執行緒模型,僅僅只是初始化了執行緒池。

2.配置服務端ServerBootstrap

例項化ServerBootstrap時候並沒有做什麼工作,主要是通過ServerBootstrap這個引導類來配置netty的服務端。下面以netty的官方示例 EchoServer為例,來解釋一下:

try {
    ServerBootstrap b = new ServerBootstrap();
    b.group(bossGroup, workerGroup)
     .channel(NioServerSocketChannel.class)
     .option(ChannelOption.SO_BACKLOG, 100)
     .handler(new LoggingHandler(LogLevel.INFO))
     .childHandler(new ChannelInitializer<SocketChannel>() {
         @Override
         public void initChannel(SocketChannel ch) throws Exception {
             ChannelPipeline p = ch.pipeline();
             p.addLast(new EchoServerHandler());
         }
     });

    //呼叫sync()方法會一直阻塞等待channel的停止
    ChannelFuture f = b.bind(PORT).sync();

    // Wait until the server socket is closed
    f.channel().closeFuture().sync();
} finally {
    // Shut down all event loops to terminate all threads.
    bossGroup.shutdownGracefully();
    workerGroup.shutdownGracefully();
}

(1)呼叫group(bossGroup, workerGroup)設定服務端的執行緒模型:
這裡將boss執行緒池是設定給了 AbstractBootstrap 類裡面的group屬性;
將worker執行緒池設定給了 ServerBootstrap 類裡面的childGroup屬性,表示子執行緒池,也就是IO和業務處理執行緒池。

(2)呼叫channel(NioServerSocketChannel.class)方法:
這個方法裡面主要例項化了 AbstractBootstrap裡面的類屬性channelFactory,也就是建立Channel的工廠,預設是ReflectiveChannelFactory。

(3)呼叫了option(ChannelOption.SO_BACKLOG, 100),設定AbstractBootstrap類裡面的 option 屬性。

(4)呼叫handler(new LoggingHandler(LogLevel.INFO)) 設定AbstractBootstrap類裡面的 handler屬性

(5)呼叫childHandler(childHandler) 設定了ServerBootstrap 類裡面的 childrenHandler 屬性。

(6)服務端呼叫b.bind(PORT) 來繫結本地的埠,這一步就是最重要的步驟了,下面仔細說說這個函式主要做了哪些工作:

(1)呼叫 AbstractBootstrap.bind(int inetPort) –>

(2)呼叫 doBind(localAddress);這個類首先初始化服務端的通道,然後做更底層的繫結操作。下面解釋一下doBind()函式做了什麼:

(3)呼叫initAndRegister()函式,主要是功能如下:例項化服務端的NioServerSocketChannel 並對Channel進行初始化; 將channel 註冊到boss執行緒池的 NIOEventLoopGroup中。

NioServerSocketChannel例項化過程

建立NioServerSocketChannel是通過channelFactory.newChannel() 實現的,通過呼叫 NioServerSocketChannel 的無參構造器。 注意這裡就與 Java的NIO聯絡起來了, 在 NioServerSocketChannel 構造器中,通過NIO的SelectorProvider建立了java.nio.channels.ServerSocketChannel;

這裡先給出NioServerSocketChannel 的主要繼承關係:
NioServerSocketChannel –》
AbstractNioMessageChannel –》
AbstractNioChannel –》
AbstractChannel

在NioServerSocketChannel 類中主要是利用 SelectorProvider 來建立了建立了一個 ServerSocketChannel 並作為引數呼叫父類構造器:

public NioServerSocketChannel(ServerSocketChannel channel) {
    super(null, channel, SelectionKey.OP_ACCEPT);
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}

上面也設定了 當前通道感興趣的事件是OP_ACCEPT 接收連線的事件;

在AbstractNioMessageChannel類構造其中,直接呼叫父類構造器

protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent, ch, readInterestOp);
}

在 AbstractNioChannel 類構造器中,主要是將ServerSocketChannel賦值給AbstractNioChannel屬性,設定ServerSocketChannel為非阻塞;

在AbstractChannel類構造器中就做了相當重要的工作,這裡必須貼出原始碼:

protected AbstractChannel(Channel parent) {
    this.parent = parent;
    id = newId();
    unsafe = newUnsafe();
    pipeline = newChannelPipeline();
}

其中也就是一個非常重要的屬性pipeline,對於pipeline的初始化其實沒有太對內容,就是構建了一個由AbstractChannelHandlerContext 為結點的雙向連結串列,來容納ChannelHandler,這裡先說一句,AbstractChannelHandlerContext與 channelHandler也是一一對應的關係。所以pipeline可以理解成Channelhandler的容器。

pipeline = newChannelPipeline();

這裡也就說明了,每一個Channel與一個 Channelpipeline 是一一對應,也就是一一繫結的。


NioServerSocketChannel初始化

初始化過程其實也就是呼叫 ServerBootstrap.inti(channel),主要做了以下工作:
- 繫結TCP可選引數;
- 繫結附加引數;
- 對於worker執行緒池: 為Channel繫結的pipeline容器新增 各種handler

值得注意的是,在init()裡面第一次呼叫了 NioEventLoop的execute(Runnable())方法,在研究NioEventLoop的原始碼的時候我們就知道了,第一次呼叫NioEventLoop的execute()方法的時候,會啟動NioEventLoop執行緒。

將NioServerSocketChannel繫結到boss執行緒池中

ChannelFuture regFuture = config().group().register(channel);

其實就是呼叫SingleThreadEventLoop.register(final ChannelPromise promise)

呼叫:

promise.channel().unsafe().register(this, promise);

這裡實際上就是呼叫 AbstractChannel.AbstractUnsafe.register()方法了,將這個channel註冊到 一個 NioEventLoop 中,也可以理解成一個 Channel與一個NioEventLoop的繫結。

底層就是繼續將呼叫 pipeline的fireChannelRegistered();方法, 這裡也就是進入了pipeline 容器裡面了,前面我們就說過pipeline裡面有一個ChannelHandler雙鏈表,這時就是從head指標開始在handler中執行了。

到這裡doBind()函式裡面的 initAndRegister()函式已經分析完畢了。

接下來就是等待註冊完畢,當註冊完畢之後,就會呼叫:
doBind0(regFuture, channel, localAddress, promise);

在doBind0()中將由channel所屬於的NIoEventLoop來執行一個task,不過doBind0()將會在channelRegistered() 方法之前被呼叫,主要是為使用者handler提供在其channelRegistered()實現中設定管道的機會。在doBind0()中主要是為channel繫結一個Channel關閉失敗監聽器。

在bind()函式的最後就是同步等待關閉通道了。