第一部分 Netty的概念及體系結(jié)構(gòu)
Netty是一款用于創(chuàng)建高性能網(wǎng)絡(luò)應(yīng)用程序的高級框架。在第一部分,我們將深入地探究它的能力,并且在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章中,你將開啟對于Netty的細(xì)致探究之旅,從它的核心網(wǎng)絡(luò)協(xié)議(第4章)以及數(shù)據(jù)處理層(第5章和第6章)到它的并發(fā)模型(第7章)。
我們將把所有的這些細(xì)節(jié)組合在一起,對第一部分進(jìn)行總結(jié)。你將看到:如何在運(yùn)行時(shí)配置基于Netty的應(yīng)用程序的各個(gè)組件,以使它們協(xié)同工作(第8章),Netty是如何幫助你測試你的應(yīng)用程序的(第9章)。
第1章 Netty——異步和事件驅(qū)動(dòng)
本章主要內(nèi)容
- Java網(wǎng)絡(luò)編程
- Netty簡介
- 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)慎的立場,例如:“聽上去是可行的。”然后,一回到計(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è)知識。Netty優(yōu)雅地處理了這些領(lǐng)域的知識,使得即使是網(wǎng)絡(luò)編程新手也能使用。但到目前為止,由于還缺乏一本全面的指南,使得對它的學(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é)議的高級從業(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ù)用性;
- 可測試性作為首要的要求。
在這第1章中,我們將從一些與高性能網(wǎng)絡(luò)編程相關(guān)的背景知識開始鋪陳,特別是它在Java開發(fā)工具包(JDK)中的實(shí)現(xiàn)。有了這些背景知識后,我們將介紹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語言套接字庫,去處理它們在不同的操作系統(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)聽指定端口上的連接請求
ServerSocket serverSocket = new ServerSocket(portNumber);
// ? 對accept()方法的調(diào)用將被阻塞,直到一個(gè)連接建立
Socket clientSocket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
// ? 這些流對象都派生于該套接字的流對象
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); // ? 請求被傳遞給服 務(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è)字符輸入流中讀取文本,后者打印對象的格式化的表示到文本輸出流。 -
readLine()
方法將會(huì)阻塞,直到在?處一個(gè)由換行符或者回車符結(jié)尾的字符串被讀取。 - 客戶端的請求已經(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ā)方案對于支撐中小數(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]注冊一組非阻塞套接字,以確定它們中是否有任何的套接字已經(jīng)有數(shù)據(jù)可供讀寫。
Java對于非阻塞I/O的支持是在2002年引入的,位于JDK 1.4的java.nio
包中。
新的還是非阻塞的
NIO最開始是新的輸入/輸出(New Input/Output)的英文縮寫,但是,該Java API已經(jīng)出現(xiàn)足夠長的時(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簡介
不久以前,我們在本章一開始所呈現(xiàn)的場景——支持成千上萬的并發(fā)客戶端——還被認(rèn)定為是不可能的。然而今天,作為系統(tǒng)用戶,我們將這種能力視為理所當(dāng)然;同時(shí)作為開發(fā)人員,我們期望將水平線提得更高[3]。因?yàn)槲覀冎溃倳?huì)有更高的吞吐量和可擴(kuò)展性的要求——在更低的成本的基礎(chǔ)上進(jìn)行交付。
不要低估了這最后一點(diǎn)的重要性。我們已經(jīng)從漫長的痛苦經(jīng)歷中學(xué)到:直接使用底層的API暴露了復(fù)雜性,并且引入了對往往供不應(yīng)求的技能的關(guān)鍵性依賴[4]。這也就是,面向?qū)ο蟮幕靖拍睿河幂^簡單的抽象隱藏底層實(shí)現(xiàn)的復(fù)雜性。
這一原則也催生了大量框架的開發(fā),它們?yōu)槌R姷木幊倘蝿?wù)封裝了解決方案,其中的許多都和分布式系統(tǒng)的開發(fā)密切相關(guān)。我們可以確定地說:所有專業(yè)的Java開發(fā)人員都至少對它們熟知一二。[5]對于我們許多人來說,它們已經(jīng)變得不可或缺,因?yàn)樗鼈兗饶軡M足我們的技術(shù)需求,又能滿足我們的時(shí)間表。
在網(wǎng)絡(luò)編程領(lǐng)域,Netty是Java的卓越框架。[6]它駕馭了Java高級API的能力,并將其隱藏在一個(gè)易于使用的API之后。Netty使你可以專注于自己真正感興趣的——你的應(yīng)用程序的獨(dú)一無二的價(jià)值。
在我們開始首次深入地了解Netty之前,請仔細(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,支持多種傳輸類型,阻塞的和非阻塞的簡單而強(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長連接,而后者用來支持各種各樣的推送通知。
每當(dāng)你使用Twitter,你便是在使用Finagle[10],它們基于Netty的系統(tǒng)間通信框架。Facebook在Nifty中使用了Netty,它們的Apache Thrift服務(wù)。可伸縮性和性能對這兩家公司來說至關(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ā)出去的電子郵件對應(yīng)的回復(fù),或者你也可能會(huì)在正在發(fā)送一封電子郵件的時(shí)候收到一個(gè)意外的消息。異步事件也可以具有某種有序的關(guān)系。通常,你只有在已經(jīng)問了一個(gè)問題之后才會(huì)得到一個(gè)和它對應(yīng)的答案,而在你等待它的同時(shí)你也可以做點(diǎn)別的事情。
在日常的生活中,異步自然而然地就發(fā)生了,所以你可能沒有對它考慮過多少。但是讓一個(gè)計(jì)算機(jī)程序以相同的方式工作就會(huì)產(chǎn)生一些非常特殊的問題。本質(zhì)上,一個(gè)既是異步的又是事件驅(qū)動(dòng)的系統(tǒng)會(huì)表現(xiàn)出一種特殊的、對我們來說極具價(jià)值的行為:它可以以任意的順序響應(yīng)在任意的時(shí)間點(diǎn)產(chǎn)生的事件。
這種能力對于實(shí)現(xiàn)最高級別的可伸縮性至關(guān)重要,定義為:“一種系統(tǒng)、網(wǎng)絡(luò)或者進(jìn)程在需要處理的工作不斷增長時(shí),可以通過某種可行的方式或者擴(kuò)大它的處理能力來適應(yī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ù)。
對于每個(gè)組件來說,我們都將提供一個(gè)基本的定義,并且在適當(dāng)?shù)那闆r下,還會(huì)提供一個(gè)簡單的示例代碼來說明它的用法。
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)在廣泛的編程場景中都有應(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è)對象可以看作是一個(gè)異步操作的結(jié)果的占位符;它將在未來的某個(gè)時(shí)刻完成,并提供對其結(jié)果的訪問。
JDK預(yù)置了interface java.util.concurrent.Future
,但是其所提供的實(shí)現(xiàn),只允許手動(dòng)檢查對應(yīng)的操作是否已經(jīng)完成,或者一直阻塞直到它完成。這是非常繁瑣的,所以Netty提供了它自己的實(shí)現(xiàn)——ChannelFuture
,用于在執(zhí)行異步操作的時(shí)候使用。
ChannelFuture
提供了幾種額外的方法,這些方法使得我們能夠注冊一個(gè)或者多個(gè)ChannelFutureListener
實(shí)例。監(jiān)聽器的回調(diào)方法operationComplete()
,將會(huì)在對應(yīng)的操作完成時(shí)被調(diào)用[15]。然后監(jiān)聽器可以判斷該操作是成功地完成了還是出錯(cuò)了。如果是后者,我們可以檢索產(chǎn)生的Throwable
。簡而言之,由ChannelFutureListener
提供的通知機(jī)制消除了手動(dòng)檢查對應(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ì)在后臺完成。這究竟什么時(shí)候會(huì)發(fā)生則取決于若干的因素,但這個(gè)關(guān)注點(diǎn)已經(jīng)從代碼中抽象出來了。因?yàn)榫€程不用阻塞以等待對應(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)上。然后,要注冊一個(gè)新的ChannelFutureListener
到對connect()
方法的調(diào)用所返回的ChannelFuture
上。當(dāng)該監(jiān)聽器被通知連接已經(jīng)建立的時(shí)候,要檢查對應(yīng)的狀態(tài)?。如果該操作是成功的,那么將數(shù)據(jù)寫到該Channel
。否則,要從ChannelFuture
中檢索對應(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() { ? -- 注冊一個(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();
}
}
});
需要注意的是,對錯(cuò)誤的處理完全取決于你、目標(biāo),當(dāng)然也包括目前任何對于特定類型的錯(cuò)誤加以的限制。例如,如果連接失敗,你可以嘗試重新連接或者建立一個(gè)到另一個(gè)遠(yuǎn)程節(jié)點(diǎn)的連接。
如果你把ChannelFutureListener
看作是回調(diào)的一個(gè)更加精細(xì)的版本,那么你是對的。事實(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
。這使得鏈接操作變得既簡單又高效,并且促進(jìn)了可重用的通用代碼的編寫。
2.選擇器、事件和EventLoop
Netty通過觸發(fā)事件將Selector
從應(yīng)用程序中抽象出來,消除了所有本來將需要手動(dòng)編寫的派發(fā)代碼。在內(nèi)部,將會(huì)為每個(gè)Channel
分配一個(gè)EventLoop
,用以處理所有事件,包括:
- 注冊感興趣的事件;
- 將事件派發(fā)給
ChannelHandler
; - 安排進(jìn)一步的動(dòng)作。
EventLoop
本身只由一個(gè)線程驅(qū)動(dòng),其處理了一個(gè)Channel
的所有I/O事件,并且在該EventLoop
的整個(gè)生命周期內(nèi)都不會(huì)改變。這個(gè)簡單而強(qiáng)大的設(shè)計(jì)消除了你可能有的在你的ChannelHandler
中需要進(jìn)行同步的任何顧慮,因此,你可以專注于提供正確的邏輯,用來在有感興趣的數(shù)據(jù)要處理的時(shí)候執(zhí)行。如同我們在詳細(xì)探討Netty的線程模型時(shí)將會(huì)看到的,該API是簡單而緊湊的。
1.4 小結(jié)
在這一章中,我們介紹了Netty框架的背景知識,包括Java網(wǎng)絡(luò)編程API的演變過程,阻塞和非阻塞網(wǎng)絡(luò)操作之間的區(qū)別,以及異步I/O在高容量、高性能的網(wǎng)絡(luò)編程中的優(yōu)勢。
然后,我們概述了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)