現在我們已經基本瞭解了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 中使用的原生組件一一找出;首先是我們一眼就能看到的:- 在原生
Selector
中,我們new了一個主線程(主Reactor線程
) 用來一直循環Selector的select操作並且註冊ServerSocketChannel
,在netty 中,我們稱這個線程爲boss
線程,對應這裏的bossLoopGroup
線程組,因爲我們只需要一個ServerSocketChannel,所以我們直接將該線程組數量設置爲1 - 在原生Selector中,爲了防止阻塞主線程,我們又使用了一個有
8
個線程的數組(子Reactor線程___
)(爲什麼是8?
),並且生成了同樣個數的的Selector ,這個線程組對應我們這裏的workLoopGroup
線程組,在netty中,它默認的線程個數是cpu核數*2
;
說到這裏,雖然我們還沒有介紹NioEventLoopGroup
,估計大家已經知道了它就是一個線程池:
- 在netty中,不再直接使用
ServerSocketChannel
,而是netty封裝的NioServerSocketChannel
(之後介紹),它會在boss線程中生成 - 在原生nio中,綁定端口之前可以給ServerSocketChannel配置一些參數,這些參數在
java.net.StandardSocketOptions
中可以找到,在netty 中使用ChannelOption
進行配置 - 同4,它是netty給連接的客戶端socket配置參數使用
- 類比原生的bind方法,netty使用異步回調操作。
- 同樣給關閉服務設置回調,並且使用 sync同步方法阻塞main線程。
- 必須關閉兩個線程池
上面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;