《Netty實戰》Netty In Action中文版 第1章——Netty——異步和事件驅動(一)
《Netty實戰》樣章由人民郵電出版社授權並發編程網發布,本書的中文版已經由人民郵電出版社引進並出版。
京東預售鏈接(優先發貨):《Netty實戰》([美]諾曼·毛瑞爾(Norman Maurer),馬文·艾倫·沃爾夫泰爾(Marvin Allen Wolfthal))
第一部分 Netty的概念及體係結構
Netty是一款用於創建高性能網絡應用程序的高級框架。在第一部分,我們將深入地探究它的能力,並且在3個主要的方麵進行示例:
- 使用Netty構建應用程序,你不必是一名網絡編程專家;
- 使用Netty比直接使用底層的Java API容易得多;
- Netty推崇良好的設計實踐,例如,將你的應用程序邏輯和網絡層解耦。
在第1章中,我們將首先小結Java網絡編程的演化過程。在我們回顧了異步通信和事件驅動的處理的基本概念之後,我們將首先看一看Netty的核心組件。在第2章中,你將能夠構建自己的第一款基於Netty的應用程序!在第3章中,你將開啟對於Netty的細致探究之旅,從它的核心網絡協議(第4章)以及數據處理層(第5章和第6章)到它的並發模型(第7章)。
我們將把所有的這些細節組合在一起,對第一部分進行總結。你將看到:如何在運行時配置基於Netty的應用程序的各個組件,以使它們協同工作(第8章),Netty是如何幫助你測試你的應用程序的(第9章)。
第1章 Netty——異步和事件驅動
本章主要內容
- Java網絡編程
- Netty簡介
- Netty的核心組件
假設你正在為一個重要的大型公司開發一款全新的任務關鍵型的應用程序。在第一次會議上,你得知該係統必須要能夠擴展到支撐150 000名並發用戶,並且不能有任何的性能損失,這時所有的目光都投向了你。你會怎麼說呢?
如果你可以自信地說:“當然,沒問題。”那麼大家都會向你脫帽致敬。但是,我們大多數人可能會采取一個更加謹慎的立場,例如:“聽上去是可行的。”然後,一回到計算機旁,我們便開始搜索“high performance Java networking”(高性能Java網絡編程)。
如果你現在搜索它,在第一頁結果中,你將會看到下麵的內容:
Netty: Home
netty.io/
Netty是一款異步的事件驅動的網絡應用程序框架,支持快速地開發可維護的高性能的麵向協議的服務器和客戶端。
如果你和大多數人一樣,通過這樣的方式發現了Netty,那麼你的下一步多半是:瀏覽該網站,下載源代碼,仔細閱讀Javadoc和一些相關的博客,然後寫點兒代碼試試。如果你已經有了紮實的網絡編程經驗,那麼可能進展還不錯,不然則可能是一頭霧水。
這是為什麼呢?因為像我們例子中那樣的高性能係統不僅要求超一流的編程技巧,還需要幾個複雜領域(網絡編程、多線程處理和並發)的專業知識。Netty優雅地處理了這些領域的知識,使得即使是網絡編程新手也能使用。但到目前為止,由於還缺乏一本全麵的指南,使得對它的學習過程比實際需要的艱澀得多——因此便有了這本書。
我們編寫這本書的主要目的是:使得Netty能夠盡可能多地被更加廣泛的開發者采用。這也包括那些擁有創新的內容或者服務,卻沒有時間或者興趣成為網絡編程專家的人。如果這適用於你,我們相信你將會非常驚訝自己這麼快便可以開始創建你的第一款基於Netty的應用程序了。當然在另一個層麵上講,我們也需要支持那些正在尋找工具來創建他們自己的網絡協議的高級從業人員。
Netty確實提供了極為豐富的網絡編程工具集,我們將花大部分的時間來探究它的能力。但是,Netty終究是一個框架,它的架構方法和設計原則是:每個小點都和它的技術性內容一樣重要,窮其精妙。因此,我們也將探討很多其他方麵的內容,例如:
- 關注點分離——業務和網絡邏輯解耦;
- 模塊化和可複用性;
- 可測試性作為首要的要求。
在這第1章中,我們將從一些與高性能網絡編程相關的背景知識開始鋪陳,特別是它在Java開發工具包(JDK)中的實現。有了這些背景知識後,我們將介紹Netty,它的核心概念以及構建塊。在本章結束之後,你就能夠編寫你的第一款基於Netty的客戶端和服務器應用程序了。
1.1 Java網絡編程
早期的網絡編程開發人員,需要花費大量的時間去學習複雜的C語言套接字庫,去處理它們在不同的操作係統上出現的古怪問題。雖然最早的Java(1995—2002)引入了足夠多的麵向對象façade(門麵)來隱藏一些棘手的細節問題,但是創建一個複雜的客戶端/服務器協議仍然需要大量的樣板代碼(以及相當多的底層研究才能使它整個流暢地運行起來)。
那些最早期的Java API(java.net
)隻支持由本地係統套接字庫提供的所謂的阻塞函數。代碼清單1-1展示了一個使用了這些函數調用的服務器代碼的普通示例。
代碼清單1-1 阻塞I/O示例
ServerSocket serverSocket = new ServerSocket(portNumber); ⇽ -- 創建一個新的ServerSocket,用以監聽指定端口上的連接請求
Socket clientSocket = serverSocket.accept(); ⇽ -- 對accept()方法的調用將被阻塞,直到一個連接建立
BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out =
new PrintWriter(clientSocket.getOutputStream(), true); ⇽ -- 這些流對象都派生於該套接字的流對象
String request, response;
while ((request = in.readLine()) != null) { ⇽ -- 處理循環開始
if ("Done".equals(request)) {
break; ⇽ -- 如果客戶端發送了“Done”,則退出處理循環
}
response = processRequest(request); ⇽ -- 請求被傳遞給服
務器的處理方法
out.println(response); ⇽ -- 服務器的響應被發送給了客戶端
} ⇽ -- 繼續執行處理循環
44代碼清單1-1實現了Socket
API的基本模式之一。以下是最重要的幾點。
-
ServerSocket
上的accept()
方法將會一直阻塞到一個連接建立,隨後返回一個新的Socket
用於客戶端和服務器之間的通信。該ServerSocket
將繼續監聽傳入的連接。 -
BufferedReader
和PrintWriter
都衍生自Socket
的輸入輸出流。前者從一個字符輸入流中讀取文本,後者打印對象的格式化的表示到文本輸出流。 -
readLine()
方法將會阻塞,直到在處一個由換行符或者回車符結尾的字符串被讀取。 - 客戶端的請求已經被處理。
這段代碼片段將隻能同時處理一個連接,要管理多個並發客戶端,需要為每個新的客戶端Socket
創建一個新的Thread
,如圖1-1所示。
圖1-1 使用阻塞I/O處理多個連接
讓我們考慮一下這種方案的影響。第一,在任何時候都可能有大量的線程處於休眠狀態,隻是等待輸入或者輸出數據就緒,這可能算是一種資源浪費。第二,需要為每個線程的調用棧都分配內存,其默認值大小區間為64 KB到1 MB,具體取決於操作係統。第三,即使Java虛擬機(JVM)在物理上可以支持非常大數量的線程,但是遠在到達該極限之前,上下文切換所帶來的開銷就會帶來麻煩,例如,在達到10 000個連接的時候。
雖然這種並發方案對於支撐中小數量的客戶端來說還算可以接受,但是為了支撐100 000或者更多的並發連接所需要的資源使得它很不理想。幸運的是,還有一種方案。
1.1.1 Java NIO
除了代碼清單1-1中代碼底層的阻塞係統調用之外,本地套接字庫很早就提供了非阻塞調用,其為網絡資源的利用率提供了相當多的控製:
- 可以使用
setsockopt()
方法配置套接字
,以便讀/寫調用在沒有數據的時候立即返回,也就是說,如果是一個阻塞調用應該已經被阻塞了[1]; - 可以使用操作係統的事件通知API[2]注冊一組非阻塞套接字,以確定它們中是否有任何的套接字已經有數據可供讀寫。
Java對於非阻塞I/O的支持是在2002年引入的,位於JDK 1.4的java.nio
包中。
新的還是非阻塞的
NIO最開始是新的輸入/輸出(New Input/Output)的英文縮寫,但是,該Java API已經出現足夠長的時間了,不再是“新的”了,因此,如今大多數的用戶認為NIO代表非阻塞I/O(Non-blocking I/O),而阻塞I/O(blocking I/O)是舊的輸入/輸出(old input/output,OIO)。你也可能遇到它被稱為普通I/O(plain I/O)的時候。
1.1.2 選擇器
圖1-2展示了一個非阻塞設計,其實際上消除了上一節中所描述的那些弊端。
圖1-2 使用Selector
的非阻塞I/O
class java.nio.channels.Selector
是Java的非阻塞I/O實現的關鍵。它使用了事件通知API以確定在一組非阻塞套接字
中有哪些已經就緒能夠進行I/O相關的操作。因為可以在任何的時間檢查任意的讀操作或者寫操作的完成狀態,所以如圖1-2所示,一個單一的線程便可以處理多個並發的連接。
總體來看,與阻塞I/O模型相比,這種模型提供了更好的資源管理:
- 使用較少的線程便可以處理許多連接,因此也減少了內存管理和上下文切換所帶來開銷;
- 當沒有I/O操作需要處理的時候,線程也可以被用於其他任務。
盡管已經有許多直接使用Java NIO API的應用程序被構建了,但是要做到如此正確和安全並不容易。特別是,在高負載下可靠和高效地處理和調度I/O操作是一項繁瑣而且容易出錯的任務,最好留給高性能的網絡編程專家——Netty。
最後更新:2017-05-18 20:36:41