Netty 框架學習 —— 初識 Netty


Netty 是一款異步的事件驅動的網絡應用程序框架,支持快速地開發可維護的高性能的面向協議的服務器和客戶端java


Java 網絡編程

早期的 Java API 只支持由本地系統套接字庫提供的所謂的阻塞函數,下面的代碼展現了一個使用傳統 Java API 的服務器代碼的普通示例編程

// 建立一個 ServerSocket 用以監聽指定端口上的鏈接請求
ServerSocket serverSocket = new ServerSocket(5000);
// 對 accept 方法的調用將被阻塞,直到一個鏈接創建
Socket clientSocket = serverSocket.accept();
// 這些流對象都派生於該套接字的流對象
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
String request, response;
// 客戶端發送了 "Done" 則退出循環
while ((request = in.readLine()) != null) {
    if ("Done".equals(request)) {
        break;
    }
    // 請求被傳遞給服務器的處理方法
    response = proce***equest(request);
    // 服務器的響應被髮送給客戶端
    out.println(response);
}

這段代碼只能同時處理一個鏈接,要管理多個客戶端,就要爲每一個新的客戶端 Socket 建立一個新的 Thread,讓咱們來考慮一下這種方案的影響:安全

  • 在任什麼時候候都會有大量線程處於休眠狀態,形成資源浪費
  • 須要爲每一個線程的調用棧都分配內存
  • 線程的上下文切換會帶來開銷

這種併發方案對於中小數量的客戶端還算理想,但不能很好地支持更大的併發,幸運的是,還有另外一種解決方案服務器


Java NIO

NIO(Non-blocking I/O,也稱 New I/O),是一種同步非阻塞的 I/O 模型,也是 I/O 多路複用的基礎。傳統的 IO 流是阻塞的,這意味着,當一個線程調用讀或寫操做時,線程將被阻塞,直至數據被徹底讀取或寫入。NIO 的非阻塞模式,使一個線程進行讀或寫操做時,若是目前無數據可用時,就不作操做,而不是保持線程阻塞,因此直至數據就緒之前,線程能夠繼續作其餘事情網絡

class java.nio.channels.Selector 是 Java 非阻塞 IO 實現的關鍵。它使用事件通知 API 以肯定在一組非阻塞套接字中有哪些已經就緒並能進行 IO 相關操做。由於能夠在任什麼時候間點任意檢查讀操做或寫操做的完成狀況,因此單一線程能夠處理多個併發的鏈接併發

與阻塞 IO 模型相比,這種模型提供了更好的資源管理:框架

  • 使用較少的線程即可以處理許多鏈接,減小內存管理和上下文切換所帶來的開銷
  • 當沒有 IO 操做須要處理時,線程也能夠用戶其餘任務

儘管 Java NIO 如此高效,但要作到正確和安全並不容易,在高負載下可靠和高效地處理和調度 IO 操做是一項煩瑣且容易出錯的任務,所幸,有 Netty 能幫助咱們解決問題異步


Netty

Netty 是一個普遍使用的 Java 網絡編程框架,它隱藏了 Java 高級 API 背後的複雜性,提供一個易於使用的 API 的客戶端/服務器框架。在這裏咱們將要討論 Netty 的主要構件:ide

1. Channel

Channel 是 Java NIO 的一個基本構造,能夠把 Channel 簡單看做是傳入(入站)或傳出(出站)數據的載體。所以,它能夠被打開或關閉,鏈接或斷開鏈接異步編程

2. 回調

一個回調其實就是一個方法,Netty 使用回調來處理事件,當一個回調被觸發時,相關的事件能夠被 ChannelHandler 的實現處理。下面的代碼展現了這樣一個例子:當一個新的鏈接已經創建,ChannelHandler 的 channelActive() 的回調方法將會被調用,並打印出一條信息

public class ConnectHandler extends ChannelInboundHandlerAdapter {
    
    @override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Client " + ctx.channel().remoteAddress() + " connected");
    }
}

3. Future

Future 提供了另外一種在操做完成時通知應用程序的方式,它將在將來的某個時刻完成,並提供對其結果的訪問。雖然 Java 提供了 Future 的一種實現,但須要手動檢查對應操做是否已經完成,或一直阻塞直到它完成,十分煩瑣。Netty 提供了本身的實現 ChannelFuture,用於執行異步操做的時候使用

ChannelFuture 提供了幾種額外的方法,這些方法使得咱們可以註冊一個或多個 ChannelFutureListener 實例。監聽器的回調方法 operationComplete() 將會在對應操做完成時被調用。每一個 Netty 的 IO 操做都會返回一個 ChannelFuture,它們都不會被阻塞,能夠同時作其餘工做,更加有效的利用資源

Channel channel = ...;
// 異步地鏈接到遠程結點
ChannelFuture future = channel.connect(new InetSocketAddress("192.168.0.1", 25));
// 註冊一個 ChannelFutureListener
future.addListener(new ChannelFutureListener() {
    
    @Override
    public void operationComplete(ChannelFuture future) {
        // 若是操做成功
        if(future.isSuccess()) {
            ...
        } else {
            // 發生異常
            ...
        }
    }
});

4. 事件和 ChannelHandler

Netty 使用不一樣的事件來觸發合適的動做,事件是按照與入站或出站數據流的相關性進行分類的,可能由入站數據或相關狀態更改而觸發的事件包括:

  • 鏈接已被激活或失效
  • 數據讀取
  • 用戶事件
  • 錯誤事件

出站事件是將來將會觸發的某個動做的操做結果,包括:

  • 打開或關閉到遠程結點的鏈接
  • 將數據寫到或沖刷到套接字

每一個事件均可分發給 ChannelHandler 類中的某個用戶實現的方法,下圖展現了一個事件如何被一個 ChannelHandler 鏈處理

Netty 提供了大量預約義的 ChannelHandler 實現,供開發者使用

5. 總結

Netty 的異步編程模型是創建在 Future 和回調的概念之上的,將事件派發到 ChannelHandler 攔截並高速地轉換入站數據和出站數據,開發者只須要提供回調或者利用返回的 Future 便可。Netty 經過觸發事件將 Selector 從應用程序中抽象出來,消除了本需手寫的派發代碼。在內部,將會爲每一個 Channel 分配一個 EventLoop,用於處理全部事件,包括:

  • 註冊感興趣的時間
  • 將事件派發給 ChannelHandler
  • 安排進一步的動做

EventLoop 自己只有一個線程驅動,處理了一個 Channel 的全部 IO 事件,這個簡單而強大的設計消除了可能在 ChannelHandler 實現中須要進行同步的任何顧慮,所以咱們只需專一於提供正確的邏輯便可

相關文章
相關標籤/搜索