Netty入門:傳統(tǒng)的BIO編程-Netty開發(fā)環(huán)境搭建
1.1 傳統(tǒng)的BIO編程
網(wǎng)絡(luò)編程的基本模型是 Client/Server 模型,也就是兩個(gè)進(jìn)程之間進(jìn)行相互通信,其中服務(wù)端提供位置信息(綁定的 IP 地址和監(jiān)聽端口),客戶端通過連接操作向服務(wù)端監(jiān)聽的地址發(fā)起連接請求,通過三次握手建立連接,如果連接建立成功,雙方就可以通過網(wǎng)絡(luò)套接字(Socket)進(jìn)行通信。
在基于傳統(tǒng)同步阻塞模型開發(fā)中,ServerSocket 負(fù)責(zé)綁定 IP 地址,啟動監(jiān)聽端口;Socket 負(fù)責(zé)發(fā)起連接操作。連接成功之后,雙方通過輸入和輸出流進(jìn)行同步阻塞式通信。
1.1.1 BIO 通信模型圖
首先,我們通過圖 2-1 所示的通信模型圖來熟悉下 BIO 的服務(wù)端通信模型:
采用 BIO 通信模型的服務(wù)端,通常由一個(gè)獨(dú)立的 Acceptor 線程負(fù)責(zé)監(jiān)聽客戶端的連接,它接收到客戶端連接請求之后為每個(gè)客戶端創(chuàng)建一個(gè)新的線程進(jìn)行鏈路處理,處理完成之后,通過輸出流返回應(yīng)答給客戶端,線程銷毀。這就是典型的一請求一應(yīng)答通信模型。

圖1-1同步阻塞 1/0服務(wù)端通信模型(1客戶端1線程)
該模型最大的問題就是缺乏彈性伸縮能力,當(dāng)客戶端并發(fā)訪問量增加后,服務(wù)端的線程個(gè)數(shù)和客戶端并發(fā)訪問數(shù)呈 1:1 的正比關(guān)系,由于線程是 Java 虛擬機(jī)非常寶貴的系統(tǒng)資源,當(dāng)線程數(shù)膨脹之后,系統(tǒng)的性能將急劇下降,隨著并發(fā)訪問量的繼續(xù)增大,系統(tǒng)會發(fā)生線程堆棧溢出、創(chuàng)建新線程失敗等問題,并最終導(dǎo)致進(jìn)程宕機(jī)或者僵死,不能對外提供服務(wù)。
1.2 偽異步I/O編程
為了解決同步阻塞 I/O 面臨的一個(gè)鏈路需要一個(gè)線程處理的問題,后來有人對它的線程模型進(jìn)行了優(yōu)化,后端通過一個(gè)線程池來處理多個(gè)客戶端的請求接入,形成客戶端個(gè)數(shù) M:線程池最大線程數(shù) N 的比例關(guān)系,其中 M 可以遠(yuǎn)遠(yuǎn)大于 N,通過線程池可以靈活的調(diào)配線程資源,設(shè)置線程的最大值,防止由于海量并發(fā)接入導(dǎo)致線程耗盡。
下面,我們結(jié)合連接模型圖和源碼,對偽異步 I/O 進(jìn)行分析,看它是否能夠解決同步阻塞 I/O 面臨的問題。
1.2.1 偽異步 I/O 模型圖
采用線程池和任務(wù)隊(duì)列可以實(shí)現(xiàn)一種叫做偽異步的 I/O 通信框架,它的模型圖如圖 1-2 所示。
當(dāng)有新的客戶端接入的時(shí)候,將客戶端的 Socket 封裝成一個(gè) Task(該任務(wù)實(shí)現(xiàn) java.lang.Runnable 接口)投遞到后端的線程池中進(jìn)行處理,JDK 的線程池維護(hù)一個(gè)消息隊(duì)列和 N 個(gè)活躍線程對消息隊(duì)列中的任務(wù)進(jìn)行處理。由于線程池可以設(shè)置消息隊(duì)列的大小和最大線程數(shù),因此,它的資源占用是可控的,無論多少個(gè)客戶端并發(fā)訪問,都不會導(dǎo)致資源的耗盡和宕機(jī)。

圖 1-2 偽異步I/0服務(wù)端通信模型(M: N)
偽異步 I/O 實(shí)際上僅僅只是對之前 I/O 線程模型的一個(gè)簡單優(yōu)化,它無法從根本上解決同步 I/O 導(dǎo)致的通信線程阻塞問題。下面我們就簡單分析下如果通信對方返回應(yīng)答時(shí)間過長,會引起的級聯(lián)故障。
1. 服務(wù)端處理緩慢,返回應(yīng)答消息耗費(fèi)60s,平時(shí)只需要10ms。
2. 采用偽異步I/O的線程正在讀取故障服務(wù)節(jié)點(diǎn)的響應(yīng),由于讀取輸入流是阻塞的,因此,它將會被同步阻塞60s。
3. 假如所有的可用線程都被故障服務(wù)器阻塞,那后續(xù)所有的I/O消息都將在隊(duì)列中排隊(duì)。
4. 由于線程池采用阻塞隊(duì)列實(shí)現(xiàn),當(dāng)隊(duì)列積滿之后,后續(xù)入隊(duì)列的操作將被阻塞。
5. 由于前端只有一個(gè)Accptor線程接收客戶端接入,它被阻塞在線程池的同步阻塞隊(duì)列之后,新的客戶端請求消息將被拒絕,客戶端會發(fā)生大量的連接超時(shí)。
6. 由于幾乎所有的連接都超時(shí),調(diào)用者會認(rèn)為系統(tǒng)已經(jīng)崩潰,無法接收新的請求消息。
1.3 NIO編程
在介紹 NIO 編程之前,我們首先需要澄清一個(gè)概念:NIO 到底是什么的簡稱?
有人稱之為 New I/O,因?yàn)樗鄬τ谥暗?I/O 類庫是新增的,所以被稱為 NewI/O,這是它的官方叫法。但是,由于之前老的 I/O 類庫是阻塞 I/O,New I/O 類庫的目標(biāo)就是要讓 Java 支持非阻塞 I/O,所以,更多的人喜歡稱之為非阻塞 I/O(Non-block I/O),由于非阻塞 I/O 更能夠體現(xiàn) NIO 的特點(diǎn),所以本文使用的NIO 都指的是非阻塞 I/O。
與 Socket 類和 ServerSocket 類相對應(yīng),NIO 也提供了 SocketChannel 和ServerSocketChannel 兩種不同的套接字通道實(shí)現(xiàn)。這兩種新增的通道都支持阻塞和非阻塞兩種模式。阻塞模式使用非常簡單,但是性能和可靠性都不好,非阻塞模式則正好相反。開發(fā)人員一般可以根據(jù)自己的需要來選擇合適的模式,一般來說,低負(fù)載、低并發(fā)的應(yīng)用程序可以選擇同步阻塞 I/O 以降低編程復(fù)雜度,但是對于高負(fù)載、高并發(fā)的網(wǎng)絡(luò)應(yīng)用,需要使用 NIO 的非阻塞模式進(jìn)行開發(fā)。
1.4 AIO編程
NIO2.0 引入了新的異步通道的概念,并提供了異步文件通道和異步套接字通道的實(shí)現(xiàn)。異步通道提供兩種方式獲取獲取操作結(jié)果:
- 通過java.util.concurrent.Future類來表示異步操作的結(jié)果;
- 在執(zhí)行異步操作的時(shí)候傳入一個(gè)java.nio.channels;
- CompletionHandler接口的實(shí)現(xiàn)類作為操作完成的回調(diào)。
NIO2.0 的異步套接字通道是真正的異步非阻塞 I/O,它對應(yīng) UNIX 網(wǎng)絡(luò)編程中的事件驅(qū)動 I/O(AIO),它不需要通過多路復(fù)用器(Selector)對注冊的通道進(jìn)行輪詢操作即可實(shí)現(xiàn)異步讀寫,從而簡化了 NIO 的編程模型。
1.5 幾種I/O模型對比
不同的 I/O 模型由于線程模型、API 等差別很大,所以用法的差異也非常大。
由于之前的幾個(gè)小節(jié)已經(jīng)集中對這幾種 I/O 的 API 和用法進(jìn)行了說明,本小節(jié)會重點(diǎn)對這幾種 I/O 進(jìn)行功能對比。如表 2-1 所示。

表 1-1 幾種 I/O 模型的功能和特性對比
1.6 業(yè)界主流的NIO框架介紹
隨著移動互聯(lián)網(wǎng)的發(fā)展和大數(shù)據(jù)時(shí)代的到來,大規(guī)模分布式服務(wù)框架、分布式流計(jì)算框架已經(jīng)成為架構(gòu)主流,分布式服務(wù)節(jié)點(diǎn)之間的通信形式往往是內(nèi)部長連接,例如 FaceBook 的 Thrift 協(xié)議,為了提升節(jié)點(diǎn)間的通信吞吐量、提升通信性能,目前主流的內(nèi)部通信框架均使用 NIO 框架,對于大公司、技術(shù)積累比較深的團(tuán)隊(duì)可能會使用自研的 NIO 框架來滿足個(gè)性化或者行業(yè)特殊的需求,但是大多數(shù)架構(gòu)師會選擇業(yè)界主流的 NIO 框架進(jìn)行異步通信開發(fā)。
目前,業(yè)界主流的 NIO 框架主要有兩款:Mina 和 Netty,兩者都使用 ApacheLICENSE-2.0 進(jìn)行開源。不同之處是 Mina 是 Apache 基金會的官方 NIO 框架,Netty 之前是 Jboss 的 NIO 框架,后來脫離 Jboss 獨(dú)立申請了 netty.io 域名,與 Jboss 脫離關(guān)系,并對版本進(jìn)行了重構(gòu),導(dǎo)致 API 無法向上兼容。
Mina 和 Netty 還 有一段 歷 史 淵 源,Mina 最 初 版 本 的 架 構(gòu) 師 是 TrustinLee,后來,由于種種原因,Trustin Lee 離開了 Mina 社區(qū)加入到了 Netty 團(tuán)隊(duì),重新設(shè)計(jì)并開發(fā)了 Netty。很多讀者會發(fā)現(xiàn) Netty 中透著 Mina 的影子,兩個(gè)框架的架構(gòu)理念也有很多相似之處,甚至一些代碼都非常相似,原因就在這里。
目前,Mina 和 Netty 的應(yīng)用已經(jīng)非常廣泛,很多開源框架都使用兩者做底層的 NIO 框架,例如 Hadoop 的通信組件 Avro 使用 Netty 做底層的通信框架,
Openfire 則使用 Mina 做底層通信框架,相比于 Mina,Netty 社區(qū)目前更活躍,版本應(yīng)用范圍也更廣。
1.7 為什么選擇Netty
1.7.1 不選擇 Java 原生 NIO 編程的原因
現(xiàn)在我們總結(jié)一下為什么不建議開發(fā)者直接使用 JDK 的 NIO 類庫進(jìn)行開發(fā),具體原因如下。
1. NIO的類庫和API繁雜,使用麻煩,你需要熟練掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等。
2. 需要具備其他的額外技能做鋪墊,例如熟悉Java多線程編程。這是因?yàn)镹IO編程涉及到Reactor模式,你必須對多線程和網(wǎng)路編程非常熟悉,才能編寫出高質(zhì)量的NIO程序。
3. 可靠性能力補(bǔ)齊,工作量和難度都非常大。例如客戶端面臨斷連重連、網(wǎng)絡(luò)閃斷、半包讀寫、失敗緩存、網(wǎng)絡(luò)擁塞和異常碼流的處理等問題,NIO編程的特點(diǎn)是功能開發(fā)相對容易,但是可靠性能力補(bǔ)齊的工作量和難度都非常大。
4. JDK NIO的BUG,例如臭名昭著的epoll bug,它會導(dǎo)致Selector空輪詢,最終導(dǎo)致CPU 100%。官方聲稱在JDK1.6版本的update18修復(fù)了該問題,但是直到JDK1.7版本該問題仍舊存在,只不過該BUG發(fā)生概率降低了一些而已,它并沒有被根本解決。該BUG以及與該BUG相關(guān)的問題單可以參見以下鏈接內(nèi)容。
- http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6403933
- http://bugs.java.com/bugdatabase/view_bug.do?bug_id=2147719
由于上述原因,在大多數(shù)場景下,不建議大家直接使用 JDK 的 NIO 類庫,除非你精通 NIO 編程或者有特殊的需求。在絕大多數(shù)的業(yè)務(wù)場景中,我們可以使用NIO 框架 Netty 來進(jìn)行 NIO 編程,它既可以作為客戶端也可以作為服務(wù)端,同時(shí)
支持 UDP 和異步文件傳輸,功能非常強(qiáng)大。
1.7.2 選擇 Netty 的理由
Netty 是業(yè)界最流行的 NIO 框架之一,它的健壯性、功能、性能、可定制性和可擴(kuò)展性在同類框架中都是首屈一指的,它已經(jīng)得到成百上千的商用項(xiàng)目驗(yàn)證,例如 Hadoop 的 RPC 框架 avro 使用 Netty 作為底層通信框架;很多其他業(yè)界主流的 RPC 框架,也使用 Netty 來構(gòu)建高性能的異步通信能力。
通過對 Netty 的分析,我們將它的優(yōu)點(diǎn)總結(jié)如下 :
- API使用簡單,開發(fā)門檻低;
- 功能強(qiáng)大,預(yù)置了多種編解碼功能,支持多種主流協(xié)議;
- 定制能力強(qiáng),可以通過ChannelHandler對通信框架進(jìn)行靈活地?cái)U(kuò)展;
- 性能高,通過與其他業(yè)界主流的NIO框架對比,Netty的綜合性能最優(yōu);
- 成熟、穩(wěn)定,Netty修復(fù)了已經(jīng)發(fā)現(xiàn)的所有JDK NIO BUG,業(yè)務(wù)開發(fā)人員不需要再為NIO的BUG而煩惱;
- 社區(qū)活躍,版本迭代周期短,發(fā)現(xiàn)的BUG可以被及時(shí)修復(fù),同時(shí),更多的新功能會加入;
- 經(jīng)歷了大規(guī)模的商業(yè)應(yīng)用考驗(yàn),質(zhì)量得到驗(yàn)證。在互聯(lián)網(wǎng)、大數(shù)據(jù)、網(wǎng)絡(luò)游戲、企業(yè)應(yīng)用、電信軟件等眾多行業(yè)得到成功商用,證明了它已經(jīng)完全能夠滿足不同行業(yè)的商業(yè)應(yīng)用了。
正是因?yàn)檫@些優(yōu)點(diǎn),Netty 逐漸成為 Java NIO 編程的首選框架。
Netty 的架構(gòu)圖如下所示。

1.8 Netty開發(fā)環(huán)境搭建
首先假設(shè)你已經(jīng)在本機(jī)安裝了 JDK1.7,配置了 JDK 的環(huán)境變量 path,同時(shí)下載并正確啟動了 IDE 工具 Eclipse。如果你是個(gè) Java 初學(xué)者,從來沒有在本機(jī)搭建過 Java 開發(fā)環(huán)境,建議你先選擇一本 Java 基礎(chǔ)入門的書籍或者課程學(xué)習(xí)。
假如你習(xí)慣于使用其他 IDE 工具進(jìn)行 Java 開發(fā),例如 NetBeans IDE,也可以運(yùn)行本節(jié)的入門例程。但是,你需要根據(jù)自己實(shí)際使用的 IDE 進(jìn)行對應(yīng)的配置修改和調(diào)整,本書統(tǒng)一使用 eclipse-jee-kepler-SR1-win32 作為 Java 開發(fā)工具。
1.8.1 下載 Netty 類庫
訪問 Netty 的官網(wǎng) http://netty.io/,從【Downloads】標(biāo)簽頁選擇下載4.1.5.Final 軟件包,包含了源碼、編譯類庫和 Java Doc,18.1M 左右,解壓之后的軟件包如下所示。
這時(shí)會發(fā)現(xiàn)里面包含了各個(gè)模塊的.jar 包和源碼,由于我們直接以二進(jìn)制類庫的方式使用 Netty,所以只需要獲取 netty-all-4.1.5.Final.jar 即可。
1.8.2 開發(fā)工程搭建
將 netty-all-4.1.5.Final.jar 導(dǎo)入到 Java 工程的 lib 目錄下(lib 目錄需要自建),右鍵單擊 netty
-all-4.1.5.Final.jar,在彈出的菜單中,選擇將.jar包添加到 Build Path 中,即可完成 Netty 開發(fā)環(huán)境的搭建。




























