第一部分 Netty的概念及體系結(jié)構(gòu)
Netty是一款用于創(chuàng)建高性能網(wǎng)絡(luò)應(yīng)用程序的高級(jí)框架他宛。在第一部分船侧,我們將深入地探究它的能力,并且在3個(gè)主要的方面進(jìn)行示例:
- 使用Netty構(gòu)建應(yīng)用程序厅各,你不必是一名網(wǎng)絡(luò)編程專家镜撩;
- 使用Netty比直接使用底層的Java API容易得多;
- Netty推崇良好的設(shè)計(jì)實(shí)踐,例如袁梗,將你的應(yīng)用程序邏輯和網(wǎng)絡(luò)層解耦宜鸯。
在第1章中,我們將首先小結(jié)Java網(wǎng)絡(luò)編程的演化過程遮怜。在我們回顧了異步通信和事件驅(qū)動(dòng)的處理的基本概念之后淋袖,我們將首先看一看Netty的核心組件。在第2章中锯梁,你將能夠構(gòu)建自己的第一款基于Netty的應(yīng)用程序即碗!在第3章中,你將開啟對(duì)于Netty的細(xì)致探究之旅陌凳,從它的核心網(wǎng)絡(luò)協(xié)議(第4章)以及數(shù)據(jù)處理層(第5章和第6章)到它的并發(fā)模型(第7章)剥懒。
我們將把所有的這些細(xì)節(jié)組合在一起,對(duì)第一部分進(jìn)行總結(jié)合敦。你將看到:如何在運(yùn)行時(shí)配置基于Netty的應(yīng)用程序的各個(gè)組件初橘,以使它們協(xié)同工作(第8章),Netty是如何幫助你測(cè)試你的應(yīng)用程序的(第9章)充岛。
第1章 Netty——異步和事件驅(qū)動(dòng)
本章主要內(nèi)容
- Java網(wǎng)絡(luò)編程
- Netty簡(jiǎn)介
- Netty的核心組件
假設(shè)你正在為一個(gè)重要的大型公司開發(fā)一款全新的任務(wù)關(guān)鍵型的應(yīng)用程序壁却。在第一次會(huì)議上,你得知該系統(tǒng)必須要能夠擴(kuò)展到支撐150 000名并發(fā)用戶裸准,并且不能有任何的性能損失展东,這時(shí)所有的目光都投向了你。你會(huì)怎么說呢炒俱?
如果你可以自信地說:“當(dāng)然盐肃,沒問題∪ㄎ颍”那么大家都會(huì)向你脫帽致敬砸王。但是,我們大多數(shù)人可能會(huì)采取一個(gè)更加謹(jǐn)慎的立場(chǎng)峦阁,例如:“聽上去是可行的谦铃。”然后榔昔,一回到計(jì)算機(jī)旁驹闰,我們便開始搜索“high performance Java networking”(高性能Java網(wǎng)絡(luò)編程)。
如果你現(xiàn)在搜索它撒会,在第一頁結(jié)果中嘹朗,你將會(huì)看到下面的內(nèi)容:
Netty: Home
netty.io/
Netty是一款異步的事件驅(qū)動(dòng)的網(wǎng)絡(luò)應(yīng)用程序框架,支持快速地開發(fā)可維護(hù)的高性能的面向協(xié)議的服務(wù)器和客戶端诵肛。
如果你和大多數(shù)人一樣屹培,通過這樣的方式發(fā)現(xiàn)了Netty,那么你的下一步多半是:瀏覽該網(wǎng)站,下載源代碼褪秀,仔細(xì)閱讀Javadoc和一些相關(guān)的博客蓄诽,然后寫點(diǎn)兒代碼試試。如果你已經(jīng)有了扎實(shí)的網(wǎng)絡(luò)編程經(jīng)驗(yàn)媒吗,那么可能進(jìn)展還不錯(cuò)若专,不然則可能是一頭霧水。
這是為什么呢蝴猪?因?yàn)橄裎覀兝又心菢拥母咝阅芟到y(tǒng)不僅要求超一流的編程技巧,還需要幾個(gè)復(fù)雜領(lǐng)域(網(wǎng)絡(luò)編程膊爪、多線程處理和并發(fā))的專業(yè)知識(shí)自阱。Netty優(yōu)雅地處理了這些領(lǐng)域的知識(shí),使得即使是網(wǎng)絡(luò)編程新手也能使用米酬。但到目前為止沛豌,由于還缺乏一本全面的指南,使得對(duì)它的學(xué)習(xí)過程比實(shí)際需要的艱澀得多——因此便有了這本書赃额。
我們編寫這本書的主要目的是:使得Netty能夠盡可能多地被更加廣泛的開發(fā)者采用加派。這也包括那些擁有創(chuàng)新的內(nèi)容或者服務(wù),卻沒有時(shí)間或者興趣成為網(wǎng)絡(luò)編程專家的人跳芳。如果這適用于你芍锦,我們相信你將會(huì)非常驚訝自己這么快便可以開始創(chuàng)建你的第一款基于Netty的應(yīng)用程序了。當(dāng)然在另一個(gè)層面上講飞盆,我們也需要支持那些正在尋找工具來創(chuàng)建他們自己的網(wǎng)絡(luò)協(xié)議的高級(jí)從業(yè)人員娄琉。
Netty確實(shí)提供了極為豐富的網(wǎng)絡(luò)編程工具集,我們將花大部分的時(shí)間來探究它的能力吓歇。但是孽水,Netty終究是一個(gè)框架,它的架構(gòu)方法和設(shè)計(jì)原則是:每個(gè)小點(diǎn)都和它的技術(shù)性內(nèi)容一樣重要城看,窮其精妙女气。因此,我們也將探討很多其他方面的內(nèi)容测柠,例如:
- 關(guān)注點(diǎn)分離——業(yè)務(wù)和網(wǎng)絡(luò)邏輯解耦炼鞠;
- 模塊化和可復(fù)用性;
- 可測(cè)試性作為首要的要求轰胁。
在這第1章中簇搅,我們將從一些與高性能網(wǎng)絡(luò)編程相關(guān)的背景知識(shí)開始鋪陳,特別是它在Java開發(fā)工具包(JDK)中的實(shí)現(xiàn)软吐。有了這些背景知識(shí)后瘩将,我們將介紹Netty,它的核心概念以及構(gòu)建塊。在本章結(jié)束之后姿现,你就能夠編寫你的第一款基于Netty的客戶端和服務(wù)器應(yīng)用程序了肠仪。
1.1 Java網(wǎng)絡(luò)編程
早期的網(wǎng)絡(luò)編程開發(fā)人員,需要花費(fèi)大量的時(shí)間去學(xué)習(xí)復(fù)雜的C語言套接字庫备典,去處理它們?cè)诓煌牟僮飨到y(tǒng)上出現(xiàn)的古怪問題异旧。雖然最早的Java(1995—2002)引入了足夠多的面向?qū)ο骹a?ade(門面)來隱藏一些棘手的細(xì)節(jié)問題,但是創(chuàng)建一個(gè)復(fù)雜的客戶端/服務(wù)器協(xié)議仍然需要大量的樣板代碼(以及相當(dāng)多的底層研究才能使它整個(gè)流暢地運(yùn)行起來)提佣。
那些最早期的Java API(java.net
)只支持由本地系統(tǒng)套接字庫提供的所謂的阻塞函數(shù)吮蛹。代碼清單1-1展示了一個(gè)使用了這些函數(shù)調(diào)用的服務(wù)器代碼的普通示例。
代碼清單1-1 阻塞I/O示例
// 創(chuàng)建一個(gè)新的ServerSocket拌屏,用以監(jiān)聽指定端口上的連接請(qǐng)求
ServerSocket serverSocket = new ServerSocket(portNumber);
// ? 對(duì)accept()方法的調(diào)用將被阻塞潮针,直到一個(gè)連接建立
Socket clientSocket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
// ? 這些流對(duì)象都派生于該套接字的流對(duì)象
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
String request, response;
while ((request = in.readLine()) != null) { // ? 處理循環(huán)開始
if ("Done".equals(request)) {
break; // 如果客戶端發(fā)送了“Done”,則退出處理循環(huán)
}
response = processRequest(request); // ? 請(qǐng)求被傳遞給服 務(wù)器的處理方法
out.println(response); // 服務(wù)器的響應(yīng)被發(fā)送給了客戶端
}
44代碼清單1-1實(shí)現(xiàn)了Socket
API的基本模式之一倚喂。以下是最重要的幾點(diǎn)每篷。
-
ServerSocket
上的accept()
方法將會(huì)一直阻塞到一個(gè)連接建立?,隨后返回一個(gè)新的Socket
用于客戶端和服務(wù)器之間的通信端圈。該ServerSocket
將繼續(xù)監(jiān)聽傳入的連接焦读。 -
BufferedReader
和PrintWriter
都衍生自Socket
的輸入輸出流?。前者從一個(gè)字符輸入流中讀取文本舱权,后者打印對(duì)象的格式化的表示到文本輸出流矗晃。 -
readLine()
方法將會(huì)阻塞,直到在?處一個(gè)由換行符或者回車符結(jié)尾的字符串被讀取宴倍。 - 客戶端的請(qǐng)求已經(jīng)被處理?喧兄。
這段代碼片段將只能同時(shí)處理一個(gè)連接,要管理多個(gè)并發(fā)客戶端啊楚,需要為每個(gè)新的客戶端Socket
創(chuàng)建一個(gè)新的Thread
吠冤,如圖1-1所示。
[圖片上傳失敗...(image-b82b6f-1510108414573)]
圖1-1 使用阻塞I/O處理多個(gè)連接
讓我們考慮一下這種方案的影響恭理。第一拯辙,在任何時(shí)候都可能有大量的線程處于休眠狀態(tài),只是等待輸入或者輸出數(shù)據(jù)就緒颜价,這可能算是一種資源浪費(fèi)涯保。第二,需要為每個(gè)線程的調(diào)用棧都分配內(nèi)存周伦,其默認(rèn)值大小區(qū)間為64 KB到1 MB夕春,具體取決于操作系統(tǒng)。第三专挪,即使Java虛擬機(jī)(JVM)在物理上可以支持非常大數(shù)量的線程及志,但是遠(yuǎn)在到達(dá)該極限之前片排,上下文切換所帶來的開銷就會(huì)帶來麻煩,例如速侈,在達(dá)到10 000個(gè)連接的時(shí)候率寡。
雖然這種并發(fā)方案對(duì)于支撐中小數(shù)量的客戶端來說還算可以接受,但是為了支撐100 000或者更多的并發(fā)連接所需要的資源使得它很不理想倚搬。幸運(yùn)的是冶共,還有一種方案。
1.1.1 Java NIO
除了代碼清單1-1中代碼底層的阻塞系統(tǒng)調(diào)用之外每界,本地套接字庫很早就提供了非阻塞調(diào)用捅僵,其為網(wǎng)絡(luò)資源的利用率提供了相當(dāng)多的控制:
- 可以使用
setsockopt()
方法配置套接字
,以便讀/寫調(diào)用在沒有數(shù)據(jù)的時(shí)候立即返回眨层,也就是說庙楚,如果是一個(gè)阻塞調(diào)用應(yīng)該已經(jīng)被阻塞了[1]; - 可以使用操作系統(tǒng)的事件通知API[2]注冊(cè)一組非阻塞套接字谐岁,以確定它們中是否有任何的套接字已經(jīng)有數(shù)據(jù)可供讀寫。
Java對(duì)于非阻塞I/O的支持是在2002年引入的榛臼,位于JDK 1.4的java.nio
包中伊佃。
新的還是非阻塞的
NIO最開始是新的輸入/輸出(New Input/Output)的英文縮寫,但是沛善,該Java API已經(jīng)出現(xiàn)足夠長(zhǎng)的時(shí)間了航揉,不再是“新的”了,因此金刁,如今大多數(shù)的用戶認(rèn)為NIO代表非阻塞I/O(Non-blocking I/O)帅涂,而阻塞I/O(blocking I/O)是舊的輸入/輸出(old input/output,OIO)尤蛮。你也可能遇到它被稱為普通I/O(plain I/O)的時(shí)候媳友。
1.1.2 選擇器
圖1-2展示了一個(gè)非阻塞設(shè)計(jì),其實(shí)際上消除了上一節(jié)中所描述的那些弊端产捞。
[圖片上傳失敗...(image-6a8490-1510108414573)]
圖1-2 使用Selector
的非阻塞I/O
class java.nio.channels.Selector
是Java的非阻塞I/O實(shí)現(xiàn)的關(guān)鍵醇锚。它使用了事件通知API以確定在一組非阻塞套接字
中有哪些已經(jīng)就緒能夠進(jìn)行I/O相關(guān)的操作。因?yàn)榭梢栽谌魏蔚臅r(shí)間檢查任意的讀操作或者寫操作的完成狀態(tài)坯临,所以如圖1-2所示焊唬,一個(gè)單一的線程便可以處理多個(gè)并發(fā)的連接。
總體來看看靠,與阻塞I/O模型相比赶促,這種模型提供了更好的資源管理:
- 使用較少的線程便可以處理許多連接,因此也減少了內(nèi)存管理和上下文切換所帶來開銷挟炬;
- 當(dāng)沒有I/O操作需要處理的時(shí)候鸥滨,線程也可以被用于其他任務(wù)嗦哆。
盡管已經(jīng)有許多直接使用Java NIO API的應(yīng)用程序被構(gòu)建了,但是要做到如此正確和安全并不容易爵赵。特別是吝秕,在高負(fù)載下可靠和高效地處理和調(diào)度I/O操作是一項(xiàng)繁瑣而且容易出錯(cuò)的任務(wù),最好留給高性能的網(wǎng)絡(luò)編程專家——Netty空幻。
1.2 Netty簡(jiǎn)介
不久以前烁峭,我們?cè)诒菊乱婚_始所呈現(xiàn)的場(chǎng)景——支持成千上萬的并發(fā)客戶端——還被認(rèn)定為是不可能的。然而今天秕铛,作為系統(tǒng)用戶约郁,我們將這種能力視為理所當(dāng)然;同時(shí)作為開發(fā)人員但两,我們期望將水平線提得更高[3]鬓梅。因?yàn)槲覀冎溃倳?huì)有更高的吞吐量和可擴(kuò)展性的要求——在更低的成本的基礎(chǔ)上進(jìn)行交付谨湘。
不要低估了這最后一點(diǎn)的重要性绽快。我們已經(jīng)從漫長(zhǎng)的痛苦經(jīng)歷中學(xué)到:直接使用底層的API暴露了復(fù)雜性,并且引入了對(duì)往往供不應(yīng)求的技能的關(guān)鍵性依賴[4]紧阔。這也就是坊罢,面向?qū)ο蟮幕靖拍睿河幂^簡(jiǎn)單的抽象隱藏底層實(shí)現(xiàn)的復(fù)雜性。
這一原則也催生了大量框架的開發(fā)擅耽,它們?yōu)槌R姷木幊倘蝿?wù)封裝了解決方案活孩,其中的許多都和分布式系統(tǒng)的開發(fā)密切相關(guān)。我們可以確定地說:所有專業(yè)的Java開發(fā)人員都至少對(duì)它們熟知一二乖仇。[5]對(duì)于我們?cè)S多人來說憾儒,它們已經(jīng)變得不可或缺,因?yàn)樗鼈兗饶軡M足我們的技術(shù)需求乃沙,又能滿足我們的時(shí)間表起趾。
在網(wǎng)絡(luò)編程領(lǐng)域,Netty是Java的卓越框架警儒。[6]它駕馭了Java高級(jí)API的能力阳掐,并將其隱藏在一個(gè)易于使用的API之后。Netty使你可以專注于自己真正感興趣的——你的應(yīng)用程序的獨(dú)一無二的價(jià)值冷蚂。
在我們開始首次深入地了解Netty之前缭保,請(qǐng)仔細(xì)審視表1-1中所總結(jié)的關(guān)鍵特性。有些是技術(shù)性的蝙茶,而其他的更多的則是關(guān)于架構(gòu)或設(shè)計(jì)哲學(xué)的艺骂。在本書的學(xué)習(xí)過程中,我們將不止一次地重新審視它們隆夯。
表1-1 Netty的特性總結(jié)
分類 | Netty的特性 |
---|---|
設(shè)計(jì) | 統(tǒng)一的API钳恕,支持多種傳輸類型别伏,阻塞的和非阻塞的簡(jiǎn)單而強(qiáng)大的線程模型真正的無連接數(shù)據(jù)報(bào)套接字支持鏈接邏輯組件以支持復(fù)用 |
易于使用 | 詳實(shí)的Javadoc和大量的示例集不需要超過JDK 1.6+[7]的依賴。(一些可選的特性可能需要Java 1.7+和/或額外的依賴) |
性能 | 擁有比Java的核心API更高的吞吐量以及更低的延遲得益于池化和復(fù)用忧额,擁有更低的資源消耗最少的內(nèi)存復(fù)制 |
健壯性 | 不會(huì)因?yàn)槁倮灏埂⒖焖倩蛘叱d的連接而導(dǎo)致OutOfMemoryError消除在高速網(wǎng)絡(luò)中NIO應(yīng)用程序常見的不公平讀/寫比率 |
安全性 | 完整的SSL/TLS以及StartTLS支持可用于受限環(huán)境下,如Applet和OSGI |
社區(qū)驅(qū)動(dòng) | 發(fā)布快速而且頻繁 |
1.2.1 誰在使用Netty
Netty擁有一個(gè)充滿活力并且不斷壯大的用戶社區(qū)睦番,其中不乏大型公司类茂,如Apple、Twitter、Facebook、Google元咙、Square和Instagram,還有流行的開源項(xiàng)目兢哭,如Infinispan、HornetQ夫嗓、Vert.x迟螺、Apache Cassandra和Elasticsearch[8],它們所有的核心代碼都利用了Netty強(qiáng)大的網(wǎng)絡(luò)抽象[9]舍咖。在初創(chuàng)企業(yè)中矩父,F(xiàn)irebase和Urban Airship也在使用Netty,前者用來做HTTP長(zhǎng)連接谎仲,而后者用來支持各種各樣的推送通知浙垫。
每當(dāng)你使用Twitter刨仑,你便是在使用Finagle[10]郑诺,它們基于Netty的系統(tǒng)間通信框架。Facebook在Nifty中使用了Netty杉武,它們的Apache Thrift服務(wù)辙诞。可伸縮性和性能對(duì)這兩家公司來說至關(guān)重要轻抱,他們也經(jīng)常為Netty貢獻(xiàn)代碼[11]飞涂。
反過來,Netty也已從這些項(xiàng)目中受益祈搜,通過實(shí)現(xiàn)FTP较店、SMTP、HTTP和WebSocket以及其他的基于二進(jìn)制和基于文本的協(xié)議容燕,Netty擴(kuò)展了它的應(yīng)用范圍及靈活性梁呈。
1.2.2 異步和事件驅(qū)動(dòng)
因?yàn)槲覀円罅康厥褂谩爱惒健边@個(gè)詞,所以現(xiàn)在是一個(gè)澄清上下文的好時(shí)機(jī)蘸秘。異步(也就是非同步)事件肯定大家都熟悉官卡』茸拢考慮一下電子郵件:你可能會(huì)也可能不會(huì)收到你已經(jīng)發(fā)出去的電子郵件對(duì)應(yīng)的回復(fù),或者你也可能會(huì)在正在發(fā)送一封電子郵件的時(shí)候收到一個(gè)意外的消息寻咒。異步事件也可以具有某種有序的關(guān)系哮翘。通常,你只有在已經(jīng)問了一個(gè)問題之后才會(huì)得到一個(gè)和它對(duì)應(yīng)的答案毛秘,而在你等待它的同時(shí)你也可以做點(diǎn)別的事情饭寺。
在日常的生活中,異步自然而然地就發(fā)生了熔脂,所以你可能沒有對(duì)它考慮過多少佩研。但是讓一個(gè)計(jì)算機(jī)程序以相同的方式工作就會(huì)產(chǎn)生一些非常特殊的問題。本質(zhì)上霞揉,一個(gè)既是異步的又是事件驅(qū)動(dòng)的系統(tǒng)會(huì)表現(xiàn)出一種特殊的旬薯、對(duì)我們來說極具價(jià)值的行為:它可以以任意的順序響應(yīng)在任意的時(shí)間點(diǎn)產(chǎn)生的事件。
這種能力對(duì)于實(shí)現(xiàn)最高級(jí)別的可伸縮性至關(guān)重要适秩,定義為:“一種系統(tǒng)绊序、網(wǎng)絡(luò)或者進(jìn)程在需要處理的工作不斷增長(zhǎng)時(shí),可以通過某種可行的方式或者擴(kuò)大它的處理能力來適應(yīng)這種增長(zhǎng)的能力秽荞≈韫”[12]
異步和可伸縮性之間的聯(lián)系又是什么呢?
- 非阻塞網(wǎng)絡(luò)調(diào)用使得我們可以不必等待一個(gè)操作的完成扬跋。完全異步的I/O正是基于這個(gè)特性構(gòu)建的阶捆,并且更進(jìn)一步:異步方法會(huì)立即返回,并且在它完成時(shí)钦听,會(huì)直接或者在稍后的某個(gè)時(shí)間點(diǎn)通知用戶洒试。
- 選擇器使得我們能夠通過較少的線程便可監(jiān)視許多連接上的事件。
將這些元素結(jié)合在一起朴上,與使用阻塞I/O來處理大量事件相比垒棋,使用非阻塞I/O來處理更快速、更經(jīng)濟(jì)痪宰。從網(wǎng)絡(luò)編程的角度來看叼架,這是構(gòu)建我們理想系統(tǒng)的關(guān)鍵,而且你會(huì)看到衣撬,這也是Netty的設(shè)計(jì)底蘊(yùn)的關(guān)鍵乖订。
在1.3節(jié)中,我們將首先看一看Netty的核心組件【吡罚現(xiàn)在乍构,只需要將它們看作是域?qū)ο螅皇蔷唧w的Java類靠粪。隨著時(shí)間的推移蜡吧,我們將看到它們是如何協(xié)作毫蚓,來為在網(wǎng)絡(luò)上發(fā)生的事件提供通知,并使得它們可以被處理的昔善。
1.3 Netty的核心組件
在本節(jié)中我將要討論Netty的主要構(gòu)件塊:
-
Channel
元潘; - 回調(diào);
-
Future
君仆; - 事件和
ChannelHandler
翩概。
這些構(gòu)建塊代表了不同類型的構(gòu)造:資源、邏輯以及通知返咱。你的應(yīng)用程序?qū)⑹褂盟鼈儊碓L問網(wǎng)絡(luò)以及流經(jīng)網(wǎng)絡(luò)的數(shù)據(jù)钥庇。
對(duì)于每個(gè)組件來說,我們都將提供一個(gè)基本的定義咖摹,并且在適當(dāng)?shù)那闆r下评姨,還會(huì)提供一個(gè)簡(jiǎn)單的示例代碼來說明它的用法。
1.3.1 Channel
Channel是Java NIO的一個(gè)基本構(gòu)造萤晴。
它代表一個(gè)到實(shí)體(如一個(gè)硬件設(shè)備吐句、一個(gè)文件、一個(gè)網(wǎng)絡(luò)套接字或者一個(gè)能夠執(zhí)行一個(gè)或者多個(gè)不同的I/O操作的程序組件)的開放連接店读,如讀操作和寫操作[13]嗦枢。
目前,可以把Channel
看作是傳入(入站)或者傳出(出站)數(shù)據(jù)的載體屯断。因此文虏,它可以被打開或者被關(guān)閉,連接或者斷開連接殖演。
1.3.2 回調(diào)
一個(gè)回調(diào)其實(shí)就是一個(gè)方法氧秘,一個(gè)指向已經(jīng)被提供給另外一個(gè)方法的方法的引用。這使得后者[14]可以在適當(dāng)?shù)臅r(shí)候調(diào)用前者剃氧∶舸ⅲ回調(diào)在廣泛的編程場(chǎng)景中都有應(yīng)用阻星,而且也是在操作完成后通知相關(guān)方最常見的方式之一朋鞍。
Netty在內(nèi)部使用了回調(diào)來處理事件;當(dāng)一個(gè)回調(diào)被觸發(fā)時(shí)妥箕,相關(guān)的事件可以被一個(gè)interface-ChannelHandler
的實(shí)現(xiàn)處理滥酥。代碼清單1-2展示了一個(gè)例子:當(dāng)一個(gè)新的連接已經(jīng)被建立時(shí),ChannelHandler
的channelActive()
回調(diào)方法將會(huì)被調(diào)用畦幢,并將打印出一條信息坎吻。
代碼清單1-2 被回調(diào)觸發(fā)的ChannelHandler
public class ConnectHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx)
throws Exception { ? -- 當(dāng)一個(gè)新的連接已經(jīng)被建立時(shí),channelActive(ChannelHandlerContext)將會(huì)被調(diào)用
System.out.println(
"Client " + ctx.channel().remoteAddress() + " connected");
}
}
1.3.3 Future
Future
提供了另一種在操作完成時(shí)通知應(yīng)用程序的方式宇葱。這個(gè)對(duì)象可以看作是一個(gè)異步操作的結(jié)果的占位符瘦真;它將在未來的某個(gè)時(shí)刻完成刊头,并提供對(duì)其結(jié)果的訪問。
JDK預(yù)置了interface java.util.concurrent.Future
诸尽,但是其所提供的實(shí)現(xiàn)原杂,只允許手動(dòng)檢查對(duì)應(yīng)的操作是否已經(jīng)完成,或者一直阻塞直到它完成您机。這是非常繁瑣的穿肄,所以Netty提供了它自己的實(shí)現(xiàn)——ChannelFuture
,用于在執(zhí)行異步操作的時(shí)候使用际看。
ChannelFuture
提供了幾種額外的方法咸产,這些方法使得我們能夠注冊(cè)一個(gè)或者多個(gè)ChannelFutureListener
實(shí)例。監(jiān)聽器的回調(diào)方法operationComplete()
仲闽,將會(huì)在對(duì)應(yīng)的操作完成時(shí)被調(diào)用[15]脑溢。然后監(jiān)聽器可以判斷該操作是成功地完成了還是出錯(cuò)了。如果是后者赖欣,我們可以檢索產(chǎn)生的Throwable
焚志。簡(jiǎn)而言之,由ChannelFutureListener
提供的通知機(jī)制消除了手動(dòng)檢查對(duì)應(yīng)的操作是否完成的必要畏鼓。
每個(gè)Netty的出站I/O操作都將返回一個(gè)ChannelFuture
酱酬;也就是說,它們都不會(huì)阻塞云矫。正如我們前面所提到過的一樣膳沽,Netty完全是異步和事件驅(qū)動(dòng)的。
代碼清單1-3展示了一個(gè)ChannelFuture
作為一個(gè)I/O操作的一部分返回的例子让禀。這里挑社,connect()
方法將會(huì)直接返回,而不會(huì)阻塞巡揍,該調(diào)用將會(huì)在后臺(tái)完成痛阻。這究竟什么時(shí)候會(huì)發(fā)生則取決于若干的因素,但這個(gè)關(guān)注點(diǎn)已經(jīng)從代碼中抽象出來了腮敌。因?yàn)榫€程不用阻塞以等待對(duì)應(yīng)的操作完成阱当,所以它可以同時(shí)做其他的工作,從而更加有效地利用資源糜工。
代碼清單1-3 異步地建立連接
Channel channel = ...;
// Does not block
ChannelFuture future = channel.connect( ? -- 異步地連接到遠(yuǎn)程節(jié)點(diǎn)
new InetSocketAddress("192.168.0.1", 25));
代碼清單1-4顯示了如何利用ChannelFutureListener
弊添。首先,要連接到遠(yuǎn)程節(jié)點(diǎn)上捌木。然后油坝,要注冊(cè)一個(gè)新的ChannelFutureListener
到對(duì)connect()
方法的調(diào)用所返回的ChannelFuture
上。當(dāng)該監(jiān)聽器被通知連接已經(jīng)建立的時(shí)候,要檢查對(duì)應(yīng)的狀態(tài)?澈圈。如果該操作是成功的彬檀,那么將數(shù)據(jù)寫到該Channel
。否則瞬女,要從ChannelFuture
中檢索對(duì)應(yīng)的Throwable
凤覆。
代碼清單1-4 回調(diào)實(shí)戰(zhàn)
Channel channel = ...;
// Does not block
ChannelFuture future = channel.connect( ? -- 異步地連接到遠(yuǎn)程節(jié)點(diǎn)
new InetSocketAddress("192.168.0.1", 25));
future.addListener(new ChannelFutureListener() { ? -- 注冊(cè)一個(gè)ChannelFutureListener,以便在操作完成時(shí)獲得通知
@Override
public void operationComplete(ChannelFuture future) { ? -- ? 檢查操作
的狀態(tài)
if (future.isSuccess()){
ByteBuf buffer = Unpooled.copiedBuffer( ? -- 如果操作是成功的拆魏,則創(chuàng)建一個(gè)ByteBuf以持有數(shù)據(jù)
"Hello",Charset.defaultCharset());
ChannelFuture wf = future.channel()
.writeAndFlush(buffer); ? -- 將數(shù)據(jù)異步地發(fā)送到遠(yuǎn)程節(jié)點(diǎn)盯桦。
返回一個(gè)ChannelFuture
....
} else {
Throwable cause = future.cause(); ? -- 如果發(fā)生錯(cuò)誤,則訪問描述原因的Throwable
cause.printStackTrace();
}
}
});
需要注意的是渤刃,對(duì)錯(cuò)誤的處理完全取決于你拥峦、目標(biāo),當(dāng)然也包括目前任何對(duì)于特定類型的錯(cuò)誤加以的限制卖子。例如略号,如果連接失敗,你可以嘗試重新連接或者建立一個(gè)到另一個(gè)遠(yuǎn)程節(jié)點(diǎn)的連接洋闽。
如果你把ChannelFutureListener
看作是回調(diào)的一個(gè)更加精細(xì)的版本玄柠,那么你是對(duì)的。事實(shí)上诫舅,回調(diào)和Future
是相互補(bǔ)充的機(jī)制羽利;它們相互結(jié)合,構(gòu)成了Netty本身的關(guān)鍵構(gòu)件塊之一刊懈。
1.3.4 事件和ChannelHandler
Netty使用不同的事件來通知我們狀態(tài)的改變或者是操作的狀態(tài)这弧。這使得我們能夠基于已經(jīng)發(fā)生的事件來觸發(fā)適當(dāng)?shù)膭?dòng)作。這些動(dòng)作可能是:
- 記錄日志虚汛;
- 數(shù)據(jù)轉(zhuǎn)換匾浪;
- 流控制;
- 應(yīng)用程序邏輯卷哩。
Netty是一個(gè)網(wǎng)絡(luò)編程框架蛋辈,所以事件是按照它們與入站或出站數(shù)據(jù)流的相關(guān)性進(jìn)行分類的〗辏可能由入站數(shù)據(jù)或者相關(guān)的狀態(tài)更改而觸發(fā)的事件包括:
- 連接已被激活或者連接失活冷溶;
- 數(shù)據(jù)讀取瓢娜;
- 用戶事件挂洛;
- 錯(cuò)誤事件礼预。
出站事件是未來將會(huì)觸發(fā)的某個(gè)動(dòng)作的操作結(jié)果眠砾,這些動(dòng)作包括:
- 打開或者關(guān)閉到遠(yuǎn)程節(jié)點(diǎn)的連接;
- 將數(shù)據(jù)寫到或者沖刷到套接字。
每個(gè)事件都可以被分發(fā)給ChannelHandler
類中的某個(gè)用戶實(shí)現(xiàn)的方法褒颈。這是一個(gè)很好的將事件驅(qū)動(dòng)范式直接轉(zhuǎn)換為應(yīng)用程序構(gòu)件塊的例子柒巫。圖1-3展示了一個(gè)事件是如何被一個(gè)這樣的ChannelHandler
鏈處理的。
[圖片上傳失敗...(image-3aa2b0-1510108414573)]
圖1-3 流經(jīng)ChannelHandler鏈的入站事件和出站事件
Netty的ChannelHandler
為處理器提供了基本的抽象谷丸,如圖1-3所示的那些堡掏。我們會(huì)在適當(dāng)?shù)臅r(shí)候?qū)?code>ChannelHandler進(jìn)行更多的說明,但是目前你可以認(rèn)為每個(gè)Channel-Handler
的實(shí)例都類似于一種為了響應(yīng)特定事件而被執(zhí)行的回調(diào)刨疼。
Netty提供了大量預(yù)定義的可以開箱即用的ChannelHandler
實(shí)現(xiàn)泉唁,包括用于各種協(xié)議(如HTTP和SSL/TLS)的ChannelHandler
。在內(nèi)部揩慕,ChannelHandler
自己也使用了事件和Future
亭畜,使得它們也成為了你的應(yīng)用程序?qū)⑹褂玫南嗤橄蟮南M(fèi)者。
1.3.5 把它們放在一起
在本章中迎卤,我們介紹了Netty實(shí)現(xiàn)高性能網(wǎng)絡(luò)編程的方式拴鸵,以及它的實(shí)現(xiàn)中的一些主要的組件。讓我們大體回顧一下我們討論過的內(nèi)容吧蜗搔。
1.Future劲藐、回調(diào)和ChannelHandler
Netty的異步編程模型是建立在Future
和回調(diào)的概念之上的, 而將事件派發(fā)到ChannelHandler
的方法則發(fā)生在更深的層次上樟凄。結(jié)合在一起聘芜,這些元素就提供了一個(gè)處理環(huán)境,使你的應(yīng)用程序邏輯可以獨(dú)立于任何網(wǎng)絡(luò)操作相關(guān)的顧慮而獨(dú)立地演變缝龄。這也是Netty的設(shè)計(jì)方式的一個(gè)關(guān)鍵目標(biāo)厉膀。
攔截操作以及高速地轉(zhuǎn)換入站數(shù)據(jù)和出站數(shù)據(jù),都只需要你提供回調(diào)或者利用操作所返回的Future
二拐。這使得鏈接操作變得既簡(jiǎn)單又高效服鹅,并且促進(jìn)了可重用的通用代碼的編寫。
2.選擇器百新、事件和EventLoop
Netty通過觸發(fā)事件將Selector
從應(yīng)用程序中抽象出來企软,消除了所有本來將需要手動(dòng)編寫的派發(fā)代碼。在內(nèi)部饭望,將會(huì)為每個(gè)Channel
分配一個(gè)EventLoop
仗哨,用以處理所有事件,包括:
- 注冊(cè)感興趣的事件铅辞;
- 將事件派發(fā)給
ChannelHandler
厌漂; - 安排進(jìn)一步的動(dòng)作。
EventLoop
本身只由一個(gè)線程驅(qū)動(dòng)斟珊,其處理了一個(gè)Channel
的所有I/O事件苇倡,并且在該EventLoop
的整個(gè)生命周期內(nèi)都不會(huì)改變。這個(gè)簡(jiǎn)單而強(qiáng)大的設(shè)計(jì)消除了你可能有的在你的ChannelHandler
中需要進(jìn)行同步的任何顧慮,因此旨椒,你可以專注于提供正確的邏輯晓褪,用來在有感興趣的數(shù)據(jù)要處理的時(shí)候執(zhí)行。如同我們?cè)谠敿?xì)探討Netty的線程模型時(shí)將會(huì)看到的综慎,該API是簡(jiǎn)單而緊湊的涣仿。
1.4 小結(jié)
在這一章中,我們介紹了Netty框架的背景知識(shí)示惊,包括Java網(wǎng)絡(luò)編程API的演變過程好港,阻塞和非阻塞網(wǎng)絡(luò)操作之間的區(qū)別,以及異步I/O在高容量米罚、高性能的網(wǎng)絡(luò)編程中的優(yōu)勢(shì)媚狰。
然后,我們概述了Netty的特性阔拳、設(shè)計(jì)和優(yōu)點(diǎn)崭孤,其中包括Netty異步模型的底層機(jī)制,包括回調(diào)糊肠、Future
以及它們的結(jié)合使用辨宠。我們還談到了事件是如何產(chǎn)生的以及如何攔截和處理它們。
轉(zhuǎn)載自并發(fā)編程網(wǎng) – ifeve.com本文鏈接地址: 《Netty實(shí)戰(zhàn)》Netty In Action中文版 第1章——Netty——異步和事件驅(qū)動(dòng)