[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()函式的最後就是同步等待關閉通道了。