Netty框架淺析--內(nèi)附Android實(shí)現(xiàn)demo

一辐脖、Netty框架簡(jiǎn)介

(本文中部分圖片摘自Netty-In-Depth)
Netty是一款以異步事件為驅(qū)動(dòng)的網(wǎng)絡(luò)開發(fā)框架和工具影锈,能夠快速的幫助開發(fā)者開發(fā)出可維護(hù)的高性能计福,高擴(kuò)張性的服務(wù)器和客戶端。

二薄湿、Netty相較于其他I/O編程的優(yōu)點(diǎn)

1叫倍、BIO編程

在基于傳統(tǒng)同步阻塞模型開發(fā)中,ServerSocket 負(fù)責(zé)綁定 IP 地址豺瘤,啟動(dòng)監(jiān)聽端口段标;Socket 負(fù)責(zé)發(fā)起連接操作。連接成功之后炉奴,雙方通過輸入和輸出流進(jìn)行同步阻塞式通信。server端為每一個(gè)連上來的client端新建一個(gè)線程進(jìn)行鏈路處理蛇更,處理完成之后通過輸出流返回應(yīng)答到客戶端瞻赶,然后線程銷毀。也就是典型的一請(qǐng)求一應(yīng)答通信模型派任。但是這種模型在連接的客戶端數(shù)量龐大的時(shí)候砸逊,相應(yīng)的服務(wù)端線程數(shù)量也會(huì)劇增,這就會(huì)使服務(wù)端因?yàn)榫€程數(shù)量過多而宕機(jī)掌逛。


BIO編程(摘自Netty-In-Depth)

2师逸、偽異步IO編程

偽異步IO線程其實(shí)就是在BIO編程基礎(chǔ)上增加了線程池,將處理客戶端連接請(qǐng)求的操作交給線程池去處理豆混,這樣線程數(shù)量就處于可控狀態(tài)篓像,可以有效的防止線程耗盡动知,但是這種模型會(huì)出現(xiàn)通信時(shí)間過長(zhǎng)導(dǎo)致級(jí)聯(lián)故障:比如服務(wù)端處理時(shí)間過長(zhǎng),或者其他線程出現(xiàn)故障员辩,由于IO操作是阻塞的盒粮,因此假如當(dāng)前所有可用線程都被阻塞了,那么后續(xù)的所有連接都會(huì)在隊(duì)列中排隊(duì)等待奠滑,當(dāng)隊(duì)列達(dá)到最大可容納數(shù)量時(shí)丹皱,后續(xù)入隊(duì)列操作會(huì)被阻塞。這樣acceptor因?yàn)樽枞诰€程池的隊(duì)列中宋税,所以無法處理后續(xù)客戶端的連接請(qǐng)求摊崭,出現(xiàn)大量的連接超時(shí)。


偽異步IO(摘自Netty-In-Depth)

3杰赛、NIO編程

前面兩種編程模型出現(xiàn)的問題其實(shí)還是在于IO操作是同步阻塞的呢簸,所以要解決這些問題,最好的辦法就是從“同步阻塞”這方面入手淆攻,因此JAVA提供了NIO類庫阔墩,其實(shí)就是讓JAVA支持非阻塞IO,與傳統(tǒng)BIO編程中的Socket連接方式來說瓶珊,我們通過Socket跟ServerSocket來進(jìn)行連接啸箫、監(jiān)聽端口、獲取輸入輸出流等操作伞芹,而與之對(duì)應(yīng)的忘苛,NIO提供了SocketChannel和ServerSocketChannel,我們可以稱其為“通道”唱较,它們支持阻塞跟非阻塞兩種模式扎唾,阻塞方式會(huì)出現(xiàn)上面我們提到過的問題,而非阻塞模式則可以大大提高性能南缓。一般來說胸遇,低負(fù)載、低并發(fā)的應(yīng)用程序可以選擇同步阻塞 I/O 以降低編程復(fù)雜度汉形,但是對(duì)于高負(fù)載纸镊、高并發(fā)的網(wǎng)絡(luò)應(yīng)用,需要使用 NIO 的非阻塞模式進(jìn)行開發(fā)概疆。

4逗威、AIO編程

NIO編程雖然是非阻塞的,但是他依然采用的是同步IO(多路復(fù)用)岔冀。也就是說需要通過一個(gè)多路復(fù)用器(Selector)對(duì)注冊(cè)的通道進(jìn)行輪詢操作凯旭,這對(duì)性能也會(huì)有所影響,所以便有了NIO2.0,它引入了異步通道的概念罐呼,提供了異步文件通道和異步套接字通道的實(shí)現(xiàn)鞠柄。NIO2.0是異步非阻塞IO,不需要通過多路復(fù)用器(Selector)來對(duì)注冊(cè)的通道進(jìn)行輪詢操作即可實(shí)現(xiàn)異步讀寫弄贿。

IO模型對(duì)比(摘自Netty-In-Depth)

剛剛有提到異步IO是異步非阻塞的春锋,它與阻塞、非阻塞差凹、多路復(fù)用等IO的區(qū)別可以參考http://blog.csdn.net/zhangzeyuaaa/article/details/42609723 簡(jiǎn)單來說異步非阻塞就是應(yīng)用發(fā)起讀寫請(qǐng)求之后就交由系統(tǒng)去處理期奔,等操作完成之后,系統(tǒng)會(huì)通過回調(diào)來通知應(yīng)用操作結(jié)果危尿。

三呐萌、Netty架構(gòu)

Netty架構(gòu)(摘自Netty-In-Depth)

Reactor層的職責(zé)主要是負(fù)責(zé)監(jiān)聽網(wǎng)絡(luò)讀寫、客戶端連接等事件谊娇,將網(wǎng)絡(luò)數(shù)據(jù)讀到內(nèi)存緩存中肺孤,上層可通過ByteBuf類讀取數(shù)據(jù),Reactor還負(fù)責(zé)觸發(fā)事件济欢,產(chǎn)生的事件交由Pipeline處理赠堵。
Pipeline層是基于責(zé)任鏈模式實(shí)現(xiàn)的,用戶定制的各種Handler組成一個(gè)鏈?zhǔn)浇Y(jié)構(gòu)由Pipeline管理法褥,當(dāng)事件觸發(fā)時(shí)茫叭,Pipeline尋找最接近的Handler并執(zhí)行,處理完后繼續(xù)將事件傳給下一個(gè)Handler處理半等。如下圖所示:

ChannelPipeline.png

以下內(nèi)容根據(jù)官網(wǎng)的描述翻譯:
一個(gè)Inbound事件交由InboundHandler類處理揍愁,方向?yàn)樽缘紫蛏希魅氲臄?shù)據(jù)通常是通過實(shí)際的輸入操作從服務(wù)端讀取杀饵,如SocketChannel.read(ByteBuffer)
莽囤。當(dāng)一個(gè)Inbound事件流到最頂層的InboundHandler后將會(huì)被廢棄或者被記錄下來(當(dāng)你需要的時(shí)候)。
一個(gè)Outbound事件由OutboundHandler類處理切距,處理方向?yàn)橛缮现料滦喽校粋€(gè)OutboundHandler通常會(huì)生成或轉(zhuǎn)換Outbound數(shù)據(jù)流,如write請(qǐng)求谜悟。如果Outbound事件流過最底部的OutboundHandler饵沧,它將會(huì)交給關(guān)聯(lián)了一個(gè)Channel的I/O線程處理,I/O線程通常會(huì)執(zhí)行實(shí)際的輸出操作如SocketChannel.write(ByteBuffer)
赌躺。
例如,假設(shè)我們創(chuàng)建如下的ChannelPipeline:
ChannelPipeline p = ...;
p.addLast("1", new InboundHandlerA());
p.addLast("2", new InboundHandlerB());
p.addLast("3", new OutboundHandlerA());
p.addLast("4", new OutboundHandlerB());
p.addLast("5", new InboundOutboundHandlerX());
如上所示羡儿,開頭為Inbound的類意味著它是一個(gè)實(shí)現(xiàn)了ChannelInboundHandler接口的類礼患,以O(shè)utbound為開頭的類則是實(shí)現(xiàn)了ChannelOutboundHandler接口的類,當(dāng)一個(gè)Inbound事件觸發(fā),ChannelPipeline只會(huì)把Inbound事件交給實(shí)現(xiàn)了ChannelInboundHandler接口的類處理缅叠,而且執(zhí)行順序是1悄泥、2、5肤粱;同理弹囚,當(dāng)一個(gè)Outbound事件觸發(fā)則只會(huì)交給實(shí)現(xiàn)了ChannelOutboundHandler接口的類處理,執(zhí)行順序相反领曼,為5鸥鹉、4、3(因?yàn)椤?”號(hào)Handler兩種接口都實(shí)現(xiàn)了庶骄,所以當(dāng)然兩種事件發(fā)生時(shí)都會(huì)流入該類)毁渗。

四、Netty線程模型

Netty提供了多種線程模型的實(shí)現(xiàn)方式单刁,用戶可以根據(jù)自身應(yīng)用場(chǎng)景選擇相應(yīng)的線程模型灸异。

單線程模型.png

由于Netty使用的是異步非阻塞I/O,所有的I/O操作都不會(huì)導(dǎo)致線程被掛起羔飞,所以理論上一個(gè)線程是可以處理所有跟I/O有關(guān)的操作肺樟。通過 Acceptor 類接收客戶端的 TCP連接請(qǐng)求消息,當(dāng)鏈路建立成功之后逻淌,通過 Dispatch 將對(duì)應(yīng)的 ByteBuffer 派發(fā)到指定的 Handler 上么伯,進(jìn)行消息解碼。用戶線程消息編碼后通過 NIO 線程將消息發(fā)送給客戶端恍风。在一些小容量應(yīng)用場(chǎng)景下蹦狂,可以使用單線程模型。但是這對(duì)于高負(fù)載朋贬、大并發(fā)的應(yīng)用場(chǎng)景卻不合適凯楔,會(huì)出現(xiàn)如下問題:

  • 一個(gè)NIO線程同時(shí)處理成百上千的鏈路,性能上無法支撐锦募,即便NIO線程的CPU負(fù)荷達(dá)到100%摆屯,也無法滿足海量消息的編碼、解碼糠亩、讀取和發(fā)送虐骑。
  • 當(dāng)NIO線程負(fù)載過重之后,處理速度將變慢赎线,這會(huì)導(dǎo)致大量客戶端連接超時(shí)廷没,超時(shí)之后往往會(huì)進(jìn)行重發(fā),這更加重了NIO線程的負(fù)載垂寥,最終會(huì)導(dǎo)致大量消息積壓和處理超時(shí)颠黎,成為系統(tǒng)的性能瓶頸另锋。
  • 可靠性問題:一旦NIO線程意外跑飛,或者進(jìn)入死循環(huán)狭归,會(huì)導(dǎo)致整個(gè)系統(tǒng)通信模塊不可用夭坪,不能接收和處理外部消息,造成節(jié)點(diǎn)故障过椎。

為了處理這些問題室梅,就演進(jìn)出了Reactor多線程模型:

多線程模型.png

Rector 多線程模型與單線程模型最大的區(qū)別就是有一組 NIO 線程來處理 I/O操作。Reactor 多線程模型的特點(diǎn)如下疚宇。

  • 有專門一個(gè)NIO線程——Acceptor線程用于監(jiān)聽服務(wù)端亡鼠,接收客戶端的TCP連接請(qǐng)求。
  • 網(wǎng)絡(luò)I/O操作——讀灰嫉、寫等由一個(gè)NIO線程池負(fù)責(zé)拆宛,線程池可以采用標(biāo)準(zhǔn)的JDK線程池實(shí)現(xiàn),它包含一個(gè)任務(wù)隊(duì)列和N個(gè)可用的線程讼撒,由這些NIO線程負(fù)責(zé)消息的讀取浑厚、解碼、編碼和發(fā)送根盒。
  • 一個(gè)NIO線程可以同時(shí)處理N條鏈路钳幅,但是一個(gè)鏈路只對(duì)應(yīng)一個(gè)NIO線程,防止發(fā)生并發(fā)操作問題炎滞。

在絕大多數(shù)場(chǎng)景下敢艰,Reactor 多線程模型可以滿足性能需求。但是册赛,在個(gè)別特殊場(chǎng)景中钠导,一個(gè) NIO 線程負(fù)責(zé)監(jiān)聽和處理所有的客戶端連接可能會(huì)存在性能問題。例如并發(fā)百萬客戶端連接森瘪,或者服務(wù)端需要對(duì)客戶端握手進(jìn)行安全認(rèn)證牡属,但是認(rèn)證本身非常損耗性能。在這類場(chǎng)景下扼睬,單獨(dú)一個(gè) Acceptor 線程可能會(huì)存在性能不足的問題逮栅,為了解決性能問題,產(chǎn)生了第三種 Reactor 線程模型——主從Reactor 多線程模型窗宇。


主從Reactor模型.png

主從 Reactor 線程模型的特點(diǎn)是:服務(wù)端用于接收客戶端連接的不再是一個(gè)單獨(dú)的 NIO 線程措伐,而是一個(gè)獨(dú)立的 NIO 線程池。Acceptor 接收到客戶端 TCP連接請(qǐng)求并處理完成后(可能包含接入認(rèn)證等)军俊,將新創(chuàng)建的 SocketChannel注 冊(cè) 到 I/O 線 程 池(sub reactor 線 程 池) 的 某 個(gè) I/O 線 程 上侥加, 由 它 負(fù) 責(zé)SocketChannel 的讀寫和編解碼工作。Acceptor 線程池僅僅用于客戶端的登錄粪躬、握手和安全認(rèn)證担败,一旦鏈路建立成功矗蕊,就將鏈路注冊(cè)到后端 subReactor 線程池的 I/O 線程上,由 I/O 線程負(fù)責(zé)后續(xù)的 I/O 操作氢架。利用主從 NIO 線程模型,可以解決一個(gè)服務(wù)端監(jiān)聽線程無法有效處理所有客戶端連接的性能不足問題朋魔。因此岖研,在 Netty 的官方 demo 中,推薦使用該線程模型警检。

五孙援、Android端基于Netty實(shí)現(xiàn)的Socket通信Demo

Demo中后臺(tái)開啟一個(gè)Service作為服務(wù)端,客戶端輸入信息發(fā)送給服務(wù)端扇雕,服務(wù)端接收到信息后在客戶端發(fā)送過來的信息前加上“res”后返回給客戶端顯示拓售,數(shù)據(jù)傳輸格式使用的是Google使用的Protocol Buffer。Demo鏈接:https://github.com/qaz3366639/NettyDemo
服務(wù)端的配置代碼如下:

        mWorkerGroup = new NioEventLoopGroup();
        //服務(wù)端啟動(dòng)引導(dǎo)類,負(fù)責(zé)配置服務(wù)端信息
        mServerBootstrap = new ServerBootstrap();
        mServerBootstrap.group(mWorkerGroup)
                .channel(NioServerSocketChannel.class)
                .handler(new ChannelInitializer<NioServerSocketChannel>() {
                    @Override
                    protected void initChannel(NioServerSocketChannel nioServerSocketChannel) throws Exception {
                        ChannelPipeline pipeline = nioServerSocketChannel.pipeline();
                        pipeline.addLast("ServerSocketChannel out", new OutBoundHandler());
                        pipeline.addLast("ServerSocketChannel in", new InBoundHandler());
                    }
                })
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        //為連接上來的客戶端設(shè)置pipeline
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        pipeline.addLast("decoder", new ProtobufDecoder(Test.ProtoTest.getDefaultInstance()));
                        pipeline.addLast("encoder", new ProtobufEncoder());
                        pipeline.addLast("out1", new OutBoundHandler());
                        pipeline.addLast("out2", new OutBoundHandler());
                        pipeline.addLast("in1", new InBoundHandler());
                        pipeline.addLast("in2", new InBoundHandler());
                        pipeline.addLast("handler", new ServerChannelHandler());
                    }
                });

        channelFuture = mServerBootstrap.bind(PORT_NUMBER);```

客戶端配置如下:

if (mBootstrap == null) {
mWorkerGroup = new NioEventLoopGroup();
mBootstrap = new Bootstrap();
mBootstrap.group(mWorkerGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast("decoder", new ProtobufDecoder(Test.ProtoTest.getDefaultInstance()));
pipeline.addLast("encoder", new ProtobufEncoder());
pipeline.addLast("handler", mDispatcher);

                    }
                })
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);

}

ChannelFuture future = mBootstrap.connect(mServerAddress);
future.addListener(mConnectFutureListener);

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末哨苛,一起剝皮案震驚了整個(gè)濱河市鸽凶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌建峭,老刑警劉巖玻侥,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異亿蒸,居然都是意外死亡凑兰,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門边锁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來姑食,“玉大人,你說我怎么就攤上這事砚蓬∈该牛” “怎么了?”我有些...
    開封第一講書人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵灰蛙,是天一觀的道長(zhǎng)祟剔。 經(jīng)常有香客問我,道長(zhǎng)摩梧,這世上最難降的妖魔是什么物延? 我笑而不...
    開封第一講書人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮仅父,結(jié)果婚禮上叛薯,老公的妹妹穿的比我還像新娘浑吟。我一直安慰自己,他們只是感情好耗溜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開白布组力。 她就那樣靜靜地躺著,像睡著了一般抖拴。 火紅的嫁衣襯著肌膚如雪燎字。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,274評(píng)論 1 300
  • 那天阿宅,我揣著相機(jī)與錄音候衍,去河邊找鬼。 笑死洒放,一個(gè)胖子當(dāng)著我的面吹牛蛉鹿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播往湿,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼妖异,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了煌茴?” 一聲冷哼從身側(cè)響起随闺,我...
    開封第一講書人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蔓腐,沒想到半個(gè)月后矩乐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡回论,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年散罕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片傀蓉。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡欧漱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出葬燎,到底是詐尸還是另有隱情误甚,我是刑警寧澤,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布谱净,位于F島的核電站窑邦,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏壕探。R本人自食惡果不足惜冈钦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望李请。 院中可真熱鬧瞧筛,春花似錦厉熟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至乍炉,卻和暖如春月培,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背恩急。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留纪蜒,地道東北人衷恭。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像纯续,于是被迫代替她去往敵國和親随珠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容