目錄
- 第一章 Java網路編程
- 1.1 Java NIO
- 1.2 選擇器
- 第二章 Netty是什么
- 2.1 Netty簡介
- 2.2 Netty的特性
- 2.2.1 設計
- 2.2.2 易于使用
- 2.2.3 性能
- 2.2.4 健壯性
- 2.2.5 安全性
- 2.2.6 社區驅動
- 2.3 Netty的使用者
- 2.4 異步和事件驅動
- 2.4.1 異步
- 2.4.2 異步和伸縮性
- 第三章 Netty核心組件
- 3.1 Channel
- 3.2 回呼
- 3.3 Future
- 3.3.1 如何使用ChannelFutureListener
- 3.4 事件和ChannelHandler
- 3.5 Future、回呼和 ChannelHandler
- 3.6 選擇器、事件和 EventLoop
第一章 Java網路編程
最早期的 Java API(java.net)只支持由本地系統套接字庫提供的所謂的阻塞函式,像下面的那樣
//創建一個新的 ServerSocket,用以監聽指定埠上的連接請求
ServerSocket serverSocket = new ServerSocket(portNumber);
//對 accept()方法的呼叫將被阻塞,直到一個連接建立.隨后回傳一個新的 Socket 用于客戶端和服務器之間的通信,該 ServerSocket 將繼續監聽傳入的連接,
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) { //readLine()方法將會阻塞,直到一個由換行符或者回車符結尾的字串被讀取,
if ("Done".equals(request)) { //如果客戶端發送了“Done”,則退出處理回圈
break;
}
//請求被傳遞給服務器的處理方法
response = processRequest(request);//客戶端的請求已經被處理
out.println(response);//服務器的回應被發送給了客戶端
//繼續執行處理回圈
}
這樣有幾個不足之處:
1、這段代碼一次只能處理一個連接(如下圖),當有新的連接時就需要為新的連接添加一個執行緒,但每個執行緒都不可能時時刻刻在作業,所以這樣就造成了大量的資源浪費,

2、分配執行緒是需要占用記憶體的,每個執行緒占用64KB還是1MB取決于作業系統,
3、即使用戶有足夠的資源來支撐這種方案,但當連接數達到10000以上的時候背景關系的切換還是非常麻煩的,
1.1 Java NIO
由于阻塞IO的不便,我們想到了非阻塞的套接字呼叫——NIO,其為網路資源的利用率提供了相當多的控制:
-
可以使用 setsockopt()方法配置套接字,以便讀/寫呼叫在沒有資料的時候立即回傳
-
可以使用作業系統的事件通知 API注冊一組非阻塞套接字,以確定它們中是否有任何的套接字已經有資料可供讀寫,
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.2 選擇器
class java.nio.channels.Selector 是Java 的非阻塞 I/O 實作的關鍵,它使用了事件通知 API以確定在一組非阻塞套接字中有哪些已經就緒能夠進行 I/O 相關的操作,因為可以在任何的時間檢查任意的讀操作或者寫操作的完成狀態,所以一個單一的執行緒便可以處理多個并發的連接,

這種設計帶來更好的資源管理:
- 使用較少的執行緒便可以處理許多連接,因此也減少了記憶體管理和背景關系切換所帶來開銷,
- 當沒有 I/O 操作需要處理的時候,執行緒也可以被用于其他任務,
盡管已經有許多直接使用 Java NIO API 的應用程式被構建了,但是要做到如此正確和安全并
不容易,特別是,在高負載下可靠和高效地處理和調度 I/O 操作是一項繁瑣而且容易出錯的任務,這些Netty可以更好的幫我們來處理,
第二章 Netty是什么
2.1 Netty簡介
Netty是由JBOSS提供的一個java開源框架,它提供異步的、事件驅動的網路應用程式框架和工具,Netty相當簡化和流線化了網路應用的編程開發程序,例如,TCP和UDP的socket服務開發,
2.2 Netty的特性
2.2.1 設計
- 統一的 API,支持多種傳輸型別,阻塞的和非阻塞的,
- 簡單而強大的執行緒模型,
- 真正的無連接資料報套接字支持,
- 鏈接邏輯組件以支持復用,
2.2.2 易于使用
- 詳實的Javadoc和大量的示例集,
- 不需要超過JDK 1.6+的依賴,(一些可選的特性可能需要Java 1.7+和/或額外的依賴),
2.2.3 性能
- 擁有比 Java 的核心 API 更高的吞吐量以及更低的延遲,
- 得益于池化和復用,擁有更低的資源消耗,
- 最少的記憶體復制,
2.2.4 健壯性
- 不會因為慢速、快速或者超載的連接而導致 OutOfMemoryError,
- 消除在高速網路中 NIO 應用程式常見的不公平讀/寫比率,
2.2.5 安全性
- 完整的 SSL/TLS 以及 StartTLS 支持,
- 可用于受限環境下,如 Applet 和 OSGI,
2.2.6 社區驅動
- 發布快速而且頻繁,
2.3 Netty的使用者
Netty擁有一個充滿活力并且不斷壯大的用戶社區,其中不乏大型公司,如Apple、Twitter、Facebook、Google、Square和Instagram,還有流行的開源專案,如Infinispan、HornetQ、Vert.x、Apache Cassandra和Elasticsearch,它們所有的核心代碼都利用了Netty強大的網路抽象,
每當你使用Twitter,你便是在使用Finagle,它們基于Netty的系統間通信框架,Facebook在Nifty中使用了Netty,它們的Apache Thrift服務,可伸縮性和性能對這兩家公司來說至關重要,他們也經常為Netty貢獻代碼 ,反過來,Netty 也已從這些專案中受益,通過實作 FTP、SMTP、HTTP 和 WebSocket 以及其他的基于二進制和基于文本的協議,Netty 擴展了它的應用范圍及靈活性,
2.4 異步和事件驅動
2.4.1 異步
生活中我們可能遇到過很多異步的場景,比如:燒水的程序中你可以干點別的,等待水燒開,本質上我們可以認為:它可以以任意的順序回應在任意的時間點產生的事件,
異步在計算機程式中可以這樣這樣定義它:一種系統、網路或者行程在需要處理的作業不斷增長時,可以通過某種可行的方式或者擴大它的處理能力來適應這種增長的能力,
2.4.2 異步和伸縮性
異步和可伸縮性之間的聯系又是什么呢?
-
非阻塞網路呼叫使得我們可以不必等待一個操作的完成,完全異步的 I/O 正是基于這個特性構建的,并且更進一步:異步方法會立即回傳,并且在它完成時,會直接或者在稍后的某個時間點通知用戶,
-
選擇器使得我們能夠通過較少的執行緒便可監視許多連接上的事件,
將這些元素結合在一起,與使用阻塞 I/O 來處理大量事件相比,使用非阻塞 I/O 來處理更快速、更經濟,從網絡編程的角度來看,這是構建我們理想系統的關鍵,這也是Netty 的設計底蘊的關鍵,
第三章 Netty核心組件
3.1 Channel
Channel 是 Java NIO 的一個基本構造,它代表一個到物體(如一個硬體設備、一個檔案、一個網路套接字或者一個能夠執行一個或者多個不同的I/O操作的程式組件)的開放連接,如讀操作和寫操作,目前,可以把 Channel 看作是傳入(入站)或者傳出(出站)資料的載體,因此,它可以被打開或者被關閉,連接或者斷開連接,
3.2 回呼
一個回呼其實就是一個方法,一個指向已經被提供給另外一個方法的方法的參考,這使得后者可以在適當的時候呼叫前者,回呼在廣泛的編程場景中都有應用,而且也是在操作完成后通知相關方最常見的方式之一,
Netty 在內部使用了回呼來處理事件;當一個回呼被觸發時,相關的事件可以被一個interfaceChannelHandler 的實作處理,如下:
public class ConnectHandler extends ChannelInboundHandlerAdapter {
//當一個新的連接已經被建立時,channelActive(ChannelHandler Context)將會被呼叫
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("Client " + ctx.channel().remoteAddress() + " connected");
}
}
當一個新的連接已經被建立時,ChannelHandler 的 channelActive()回呼方法將會被呼叫,并將列印出一條資訊,
3.3 Future
Future 提供了另一種在操作完成時通知應用程式的方式,這個物件可以看作是一個異步操作的結果的占位符;它將在未來的某個時刻完成,并提供對其結果的訪問,
Java中也提供了Future的實作,但比較繁瑣,為此,Netty提供了它自己的實作——ChannelFuture,用于在執行異步操作的時候使用,
ChannelFuture提供了幾種額外的方法,這些方法使得我們能夠注冊一個或者多個ChannelFutureListener實體,
監聽器的回呼方法operationComplete(),將會在對應的操作完成時被呼叫 ,然后監聽器可以判斷該操作是成功地完成了還是出錯了,如果是后者,我們可以檢索產生的Throwable,
每個 Netty 的出站 I/O 操作都將回傳一個 ChannelFuture,它們都不會阻塞,
Channel channel = ...;
//異步地連接到遠程節點
ChannelFuture future = channel.connect(
new InetSocketAddress("192.168.0.1", 25));
像這樣connect()方法將會直接回傳,而不會阻塞,該呼叫將會在后臺完成,這究竟什么時候會發生
則取決于若干的因素,但這個關注點已經從代碼中抽象出來了,因為執行緒不用阻塞以等待對應的操作完成,所以它可以同時做其他的作業,從而更加有效地利用資源,
ps:如果在 ChannelFutureListener 添加到 ChannelFuture 的時候,ChannelFuture 已經完成,那么該 ChannelFutureListener 將會被直接地通知,
3.3.1 如何使用ChannelFutureListener
下面的代碼演示了如何使用ChannelFutureListener ,首先,要連接到遠程節點上,然后,要注冊一個新的 ChannelFutureListener 到對 connect()方法的呼叫所回傳的 ChannelFuture 上,當該監聽器被通知連接已經建立的時候,要檢查對應的狀態 ,如果該操作是成功的,那么將資料寫到該 Channel,否則,要從ChannelFuture 中檢索對應的 Throwable,
Channel channel = ...;
//異步連接到遠程節點
ChannelFuture future = channel.connect(new InetSocketAddress("192.168.0.1", 25));
//注冊一個 ChannelFutureListener,以便在操作完成時獲得通知
future.addListener(new ChannelFutureListener() {
//檢查操作的狀態
@Override
public void operationComplete(ChannelFuture future) {
//如果操作是成功的,則創建一個 ByteBuf 以持有資料
if (future.isSuccess()){
ByteBuf buffer = Unpooled.copiedBuffer("Hello",Charset.defaultCharset());
//將資料異步地發送到遠程節點,回傳一個 ChannelFuture
ChannelFuture wf = future.channel().writeAndFlush(buffer);
....
} else {
//如果發生錯誤,則訪問描述原因的 Throwable,接下來的處理可以根據具體業務來處理
Throwable cause = future.cause();
cause.printStackTrace();
}
}
});
我們可以把ChannelFutureListener 看作是回呼的一個更加精細的版本,
3.4 事件和ChannelHandler
Netty使用以下事件來通知我們狀態改變或者操作狀態,
- 記錄日志;
- 資料轉換;
- 流控制;
- 應用程式邏輯,
Netty 是一個網路編程框架,所以事件是按照它們與入站或出站資料流的相關性進行分類的,
可能由入站資料或者相關的狀態更改而觸發的事件包括:
- 連接已被激活或者連接失活,
- 資料讀取,
- 用戶事件,
- 錯誤事件,
出站事件是未來將會觸發的某個動作的操作結果,這些動作包括:
- 打開或者關閉到遠程節點的連接,
- 將資料寫到或者沖刷到套接字,
每個事件都可以被分發給 ChannelHandler 類中的某個用戶實作的方法,這是一個很好的將事件驅動范式直接轉換為應用程式構件塊的例子,下圖展示了一個事件是如何被一個這樣的ChannelHandler 鏈處理的,

目前暫時可以認為每個 ChannelHandler 的實體都類似于一種為了回應特定事件而被執行的回呼,
Netty 提供了大量預定義的可以開箱即用的 ChannelHandler 實作,包括用于各種協議(如 HTTP 和 SSL/TLS)的 ChannelHandler,在內部,ChannelHandler 自己也使用了事件和 Future,使得它們也成為了你的應用程式將使用的相同抽象的消費者,
3.5 Future、回呼和 ChannelHandler
Netty的異步編程模型是建立在Future和回呼的概念之上的,而將事件派發到ChannelHandler的方法則發生在更深的層次上,結合在一起,這些元素就提供了一個處理環境,使你的應用程式邏輯可以獨立于任何網路操作相關的顧慮而獨立地演變,這也是 Netty 的設計方式的一個關鍵目標,攔截操作以及高速地轉換入站資料和出站資料,都只需要你提供回呼或者利用操作所回傳的Future,這使得鏈接操作變得既簡單又高效,并且促進了可重用的通用代碼的撰寫,
3.6 選擇器、事件和 EventLoop
Netty 通過觸發事件將 Selector 從應用程式中抽象出來,消除了所有本來將需要手動撰寫的派發代碼,在內部,將會為每個 Channel 分配一個 EventLoop,用以處理所有事件,包括:
- 注冊感興趣的事件,
- 將事件派發給 ChannelHandler,
- 安排進一步的動作,
EventLoop 本身只由一個執行緒驅動,其處理了一個 Channel 的所有 I/O 事件,并且在該EventLoop 的整個生命周期內都不會改變,這個簡單而強大的設計消除了你可能有的在ChannelHandler 實作中需要進行同步的任何顧慮,因此,你可以專注于提供正確的邏輯,用來在有感興趣的資料要處理的時候執行,如同我們在詳細探討 Netty 的執行緒模型時將會看到的,該 API 是簡單而緊湊的,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/553207.html
標籤:其他
下一篇:返回列表
