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

第一部分 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)聽傳入的連接焦读。
  • BufferedReaderPrintWriter都衍生自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í),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è)對(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 = ...;
![](/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)上捌木。然后油坝,要注冊(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)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末货裹,一起剝皮案震驚了整個(gè)濱河市嗤形,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌弧圆,老刑警劉巖赋兵,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異搔预,居然都是意外死亡霹期,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門拯田,熙熙樓的掌柜王于貴愁眉苦臉地迎上來历造,“玉大人,你說我怎么就攤上這事船庇】圆” “怎么了?”我有些...
    開封第一講書人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵鸭轮,是天一觀的道長(zhǎng)臣淤。 經(jīng)常有香客問我,道長(zhǎng)窃爷,這世上最難降的妖魔是什么邑蒋? 我笑而不...
    開封第一講書人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任姓蜂,我火速辦了婚禮,結(jié)果婚禮上寺董,老公的妹妹穿的比我還像新娘覆糟。我一直安慰自己刻剥,他們只是感情好遮咖,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著造虏,像睡著了一般御吞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上漓藕,一...
    開封第一講書人閱讀 51,775評(píng)論 1 307
  • 那天陶珠,我揣著相機(jī)與錄音,去河邊找鬼享钞。 笑死揍诽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的栗竖。 我是一名探鬼主播暑脆,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼狐肢!你這毒婦竟也來了添吗?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤份名,失蹤者是張志新(化名)和其女友劉穎碟联,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體僵腺,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鲤孵,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了辰如。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片裤纹。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖丧没,靈堂內(nèi)的尸體忽然破棺而出鹰椒,到底是詐尸還是另有隱情,我是刑警寧澤呕童,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布漆际,位于F島的核電站,受9級(jí)特大地震影響夺饲,放射性物質(zhì)發(fā)生泄漏奸汇。R本人自食惡果不足惜施符,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望擂找。 院中可真熱鬧戳吝,春花似錦、人聲如沸贯涎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽塘雳。三九已至陆盘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間败明,已是汗流浹背隘马。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留妻顶,地道東北人酸员。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像讳嘱,于是被迫代替她去往敵國和親幔嗦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

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