本文開(kāi)始分析Netty的源碼,由于目標(biāo)是自頂向下分析,在這一節(jié)將分析Netty是如何構(gòu)建起如上圖所示的整體框架。首先將使用一個(gè)示例展示怎么使用Bootstarp構(gòu)建服務(wù)端應(yīng)用,然后將深入源碼了解底層機(jī)制和原理。
1.使用示例
首先使用Netty構(gòu)造如圖所示的框架,源碼如下:
// 指定mainReactor
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
// 指定subReactor
EventLoopGroup workerGroup = new NioEventLoopGroup();
// 用戶(hù)自定義的ThreadPool
EventExecutorGroup threadPool = new ThreadPool();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100) // 設(shè)置TCP參數(shù)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(threadPool,
new DecoderHandler(), // 解碼處理器
new ComputeHandler()); // 計(jì)算處理器
new EncoderHandler(), // 編碼處理器
}
});
// 綁定到本地端口等待客戶(hù)端連接
ChannelFuture f = b.bind(PORT).sync();
// 等待接受客戶(hù)端連接的Channel被關(guān)閉
f.channel().closeFuture().sync();
} finally {
// 關(guān)閉兩個(gè)線(xiàn)程組
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
threadPool.shutdown();
}
逐行分析代碼,EventLoopGroup
是Netty實(shí)現(xiàn)的線(xiàn)程池接口,兩個(gè)線(xiàn)程池:bossGroup和workerGroup分別對(duì)應(yīng)mainReactor和subReactor,其中boss專(zhuān)門(mén)用于接受客戶(hù)端連接,worker也就是常說(shuō)的IO線(xiàn)程專(zhuān)門(mén)用于處理IO事件。IO事件包括兩類(lèi),一類(lèi)如服務(wù)端接收到客戶(hù)端數(shù)據(jù)的Read事件,另一類(lèi)如用戶(hù)線(xiàn)程主動(dòng)向客戶(hù)端發(fā)送數(shù)據(jù)的Write事件。在4.0版本中,用戶(hù)自定義的業(yè)務(wù)線(xiàn)程池須實(shí)現(xiàn)EventExecutorGroup
接口,4.1版本則可以直接使用JAVA自帶的線(xiàn)程池。
為了幫助用戶(hù)快速構(gòu)建基于Netty的服務(wù),Netty提供了兩個(gè)啟動(dòng)器ServerBootstrap
和Bootstrap
,分別用于啟動(dòng)服務(wù)器端和客戶(hù)端程序。group(EventLoopGroup...)
方法用于指定一個(gè)或兩個(gè)Reactor,本例中指定為兩個(gè)。channel(Channel)
方法本質(zhì)用來(lái)指定一個(gè)Channel工廠(chǎng),本例中該工廠(chǎng)生產(chǎn)服務(wù)端用于accept客戶(hù)端連接的Channel,將默認(rèn)使用Channel的無(wú)參構(gòu)造方法。如果用戶(hù)需要自定義有參數(shù)的Channel,可自定義所需的工廠(chǎng)實(shí)現(xiàn)。option(Key, Value)
用于指定TCP相關(guān)的參數(shù)以及一些Netty自定義的參數(shù)。childHandler()
用于指定subReactor中的處理器,類(lèi)似的,handler()
用于指定mainReactor的處理器,只是默認(rèn)情況下mainReactor中已經(jīng)添加了acceptor處理器,所以無(wú)需再指定。需要注意的是:這兩個(gè)方法并不能累積調(diào)用而達(dá)到增加多個(gè)處理器的目的,所以引入了 ChannelInitializer
,它是一個(gè)特殊的Handler,功能是初始化多個(gè)Handler,如本例中的DecoderHandler
,ComputeHandler
,EncoderHandler
。完成初始化工作后,ChannelInitializer
會(huì)從Handler鏈中刪除。至此,如圖所示的框架已經(jīng)構(gòu)建完畢。
最后臨門(mén)一腳,bind(int)
方法將服務(wù)端Channel綁定到本地端口,成功后將accept客戶(hù)端的連接,從而是整個(gè)框架運(yùn)行起來(lái)。使用sync()
方法是由于Netty中的事件都是異步的,所以需要同步等待結(jié)果。準(zhǔn)確的說(shuō),這個(gè)方法在這里使用是有問(wèn)題的,sync()
完成后只能表明綁定事件運(yùn)行完畢,但并不能說(shuō)明綁定成功,雖然失敗的可能性微乎其微。
f.channel().closeFuture().sync()
方法僅僅是為了使當(dāng)前main線(xiàn)程阻塞而不立即執(zhí)行之后的各種shutdown()
方法,其語(yǔ)義是等到服務(wù)端接受客戶(hù)端連接的Channel被關(guān)閉時(shí),才執(zhí)行后面代碼的操作。在實(shí)際應(yīng)用中,這樣的代碼并不實(shí)用,我們可能需要接受諸如kill
命令后,優(yōu)雅關(guān)閉線(xiàn)程組。
一些情況下,我們并不使用如圖所示的結(jié)構(gòu),比如當(dāng)業(yè)務(wù)邏輯都很簡(jiǎn)單,也就是如圖所示的decode,compute,encode能在短時(shí)間完成(數(shù)十毫秒或更少),那么可以不使用業(yè)務(wù)線(xiàn)程池。代碼也很簡(jiǎn)單,只需要改動(dòng)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()); // 計(jì)算處理器
p.addLast(new EncoderHandler()); // 編碼處理器
}
});
事實(shí)上這是Netty的默認(rèn)方法,也就是說(shuō)不在addLast(Handler)
方法中指定線(xiàn)程池,那么將使用默認(rèn)的subReacor即woker線(xiàn)程池也即IO線(xiàn)程池執(zhí)行處理器中的業(yè)務(wù)邏輯代碼。
又比如,如開(kāi)始的例子只讓IO線(xiàn)程池處理read,write等IO事件會(huì)覺(jué)得有點(diǎn)大材小用,于是將decode和encode交給IO線(xiàn)程處理,如果此時(shí)的compute查詢(xún)需要數(shù)據(jù)庫(kù)中的數(shù)據(jù),那么代碼可改動(dòng)為如下:
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查詢(xún)的計(jì)算
}
});
最佳實(shí)踐
簡(jiǎn)單快速的業(yè)務(wù)邏輯可由IO線(xiàn)程池執(zhí)行,復(fù)雜耗時(shí)的業(yè)務(wù)(如查詢(xún)數(shù)據(jù)庫(kù),取得網(wǎng)絡(luò)連接等)使用新的業(yè)務(wù)邏輯線(xiàn)程池執(zhí)行。
本文介紹了Bootstrap的使用,如果還想知道背后的原理,可移步后續(xù)文章:自頂向下深入分析Netty(三)--Bootstrap源碼分析。