自頂向下深入分析Netty(三)--Bootstrap
1.使用示例
首先使用Netty構造如圖所示的框架,原始碼如下:
// 指定mainReactor EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 指定subReactor EventLoopGroup workerGroup = new NioEventLoopGroup(); // 使用者自定義的ThreadPool EventExecutorGroup threadPool = new ThreadPool(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 100) // 設定TCP引數 .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast(threadPool, new DecoderHandler(), // 解碼處理器 new ComputeHandler()); // 計算處理器 new EncoderHandler(), // 編碼處理器 } }); // 繫結到本地埠等待客戶端連線 ChannelFuture f = b.bind(PORT).sync(); // 等待接受客戶端連線的Channel被關閉 f.channel().closeFuture().sync(); } finally { // 關閉兩個執行緒組 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); threadPool.shutdown(); }
逐行分析程式碼,EventLoopGroup
是Netty實現的執行緒池介面,兩個執行緒池:bossGroup和workerGroup分別對應mainReactor和subReactor,其中boss專門用於接受客戶端連線,worker也就是常說的IO執行緒專門用於處理IO事件。IO事件包括兩類,一類如服務端接收到客戶端資料的Read事件,另一類如使用者執行緒主動向客戶端傳送資料的Write事件。在4.0版本中,使用者自定義的業務執行緒池須實現EventExecutorGroup
介面,4.1版本則可以直接使用JAVA自帶的執行緒池
為了幫助使用者快速構建基於Netty的服務,Netty提供了兩個啟動器ServerBootstrap
Bootstrap
,分別用於啟動伺服器端和客戶端程式。group(EventLoopGroup...)
方法用於指定一個或兩個Reactor,本例中指定為兩個。channel(Channel)
方法本質用來指定一個Channel工廠,本例中該工廠生產服務端用於accept客戶端連線的Channel,將預設使用Channel的無參構造方法。如果使用者需要自定義有引數的Channel,可自定義所需的工廠實現。option(Key, Value)
用於指定TCP相關的引數以及一些Netty自定義的引數。childHandler()
用於指定subReactor中的處理器,類似的,handler()
用於指定mainReactor的處理器,只是預設情況下mainReactor中已經添加了acceptor處理器,所以無需再指定。需要注意的是:這兩個方法並不能累積呼叫而達到增加多個處理器的目的,所以引入了 ChannelInitializer
DecoderHandler
,ComputeHandler
,EncoderHandler
。完成初始化工作後,ChannelInitializer
會從Handler鏈中刪除。至此,如圖所示的框架已經構建完畢
最後臨門一腳,bind(int)
方法將服務端Channel繫結到本地埠,成功後將accept客戶端的連線,從而是整個框架執行起來。使用sync()
方法是由於Netty中的事件都是非同步的,所以需要同步等待結果。準確的說,這個方法在這裡使用是有問題的,sync()
完成後只能表明繫結事件執行完畢,但並不能說明繫結成功,雖然失敗的可能性微乎其微
f.channel().closeFuture().sync()
方法僅僅是為了使當前main執行緒阻塞而不立即執行之後的各種shutdown()
方法,其語義是等到服務端接受客戶端連線的Channel被關閉時,才執行後面程式碼的操作。在實際應用中,這樣的程式碼並不實用,我們可能需要接受諸如kill
命令後,優雅關閉執行緒組
一些情況下,我們並不使用如圖所示的結構,比如當業務邏輯都很簡單,也就是如圖所示的decode,compute,encode能在短時間完成(數十毫秒或更少),那麼可以不使用業務執行緒池。程式碼也很簡單,只需要改動ChannelInitializer
即可
b.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new DecoderHandler()); // 解碼處理器
p.addLast(new ComputeHandler()); // 計算處理器
p.addLast(new EncoderHandler()); // 編碼處理器
}
});
事實上這是Netty的預設方法,也就是說不在addLast(Handler)
方法中指定執行緒池,那麼將使用預設的subReacor即woker執行緒池也即IO執行緒池執行處理器中的業務邏輯程式碼
又比如,如開始的例子只讓IO執行緒池處理read,write等IO事件會覺得有點大材小用,於是將decode和encode交給IO執行緒處理,如果此時的compute查詢需要資料庫中的資料,那麼程式碼可改動為如下
b.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new DecoderHandler()); // 解碼處理器
p.addLast(new EncoderHandler()); // 編碼處理器
p.addLast(threadPool, new ComputeWithSqlHandler()); // 附帶SQL查詢的計算
}
});
最佳實踐
簡單快速的業務邏輯可由IO執行緒池執行,複雜耗時的業務(如查詢資料庫,取得網路連線等)使用新的業務邏輯執行緒池執行