《Netty實(shí)戰(zhàn)》Netty In Action中文版 第1章 Netty異步和事件驅(qū)動(dòng)

第一部分 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)聽傳入的連接。
  • BufferedReaderPrintWriter都衍生自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í),ChannelHandlerchannelActive()回調(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 = ...;
![](/api/storage/getbykey/screenshow?key=1704e6378455b23f17f7)![](/api/storage/getbykey/screenshow?key=1704725cc4b464f4793a)![](/api/storage/getbykey/screenshow?key=17042eacf3010fc856d6)// 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)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,106評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,441評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,211評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,475評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,834評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,009評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,559評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,306評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,516評論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,728評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,249評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,484評論 2 379

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