Netty實戰(一)

来源:https://www.cnblogs.com/kimi77/archive/2023/05/23/17425322.html
-Advertisement-
Play Games

[TOC](Nett的概念及體繫結構) # 第一章 Java網路編程 最早期的 Java API(java.net)只支持由本地系統套接字型檔提供的所謂的阻塞函數,像下麵的那樣 ```java //創建一個新的 ServerSocket,用以監聽指定埠上的連接請求 ServerSocket serv ...


目錄

第一章 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 鏈的入站事件和出站事件
目前暫時可以認為每個 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 是簡單而緊湊的。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • # xxxx爬蟲——伺服器渲染和客戶端渲染 [toc] ## 伺服器渲染 - 專業解釋 伺服器渲染(Server-Side Rendering,SSR)是一種在伺服器端完成頁面渲染的網頁處理技術。具體來說,就是伺服器在響應客戶端請求時,會生成頁面的HTML代碼,並將其返回給客戶端。這種方式的優點包括 ...
  • 題目傳送門: >[【洛谷】P4710 [物理]平拋運動](https://www.luogu.com.cn/problem/P4710 "【洛谷】P4710 [物理]平拋運動") ## Step 1:前置芝士 您需要知道並瞭解以下芝士: 1. 數學: - 三角函數; 2. 物理: - 加速度公式; ...
  • 本系列前面講解了Spring的bean定義、bean實例化、bean初始化等生命周期階段。這些步驟使我們能夠瞭解bean從創建到準備好使用所經歷的過程。但是,除了這些步驟,bean的銷毀也是非常重要的一步。在本系列的最後,我們將深入探討bean的銷毀過程,包括在什麼情況下會發生銷毀、銷毀的順序以及如... ...
  • PHP 獲取無限級下級ID 無層級 非遞歸 刪除會員處有誤,修複後上傳記錄, PHP 獲取無限級下級ID 無層級 非遞歸 刪除會員處有誤,修複後上傳記錄, PHP 獲取無限級下級ID 無層級 非遞歸 刪除會員處有誤,修複後上傳記錄, PHP 獲取無限級下級ID 無層級 非遞歸 刪除會員處有誤,修複後 ...
  • # Java IO流 ## 什麼是流? 概念:記憶體和存儲設備之間傳輸數據的通道。 數據藉助流傳輸。 流分類: - 按照方向:輸入流(將存儲設備中的內容讀入到記憶體中)和輸出流(將記憶體中的內容寫入到存儲設備中) - 按照單位:位元組流(以位元組為單位,可以讀寫所有數據)和字元流(以字元為單位,只能讀取文本數 ...
  • 本文使用的是巴法雲 你也可以使用其他的物聯網平臺 並且 也不一定是小愛 比如小度啊 等等其他的一下應該也是可以實現的 調到java裡面之後 剩下的事情大家就可以想幹嘛就幹嘛了 ...
  • ## 一、安裝laradock ### 1. 如果有laravel項目並使用git,可以用git submodule將laradock克隆到laravel根目錄,方便後續管理 ```git submodule add https://github.com/laradock/laradock.git` ...
  • #### 錯誤: 找不到或無法載入主類 jar ##### 問題描述: 在使用springboot框架對項目打包後,手動使用命令java -jar 包名啟動jar包,報錯:錯誤: 找不到或無法載入主類 jar。 網上找了各辦法,都是加maven插件,打成可執行jar包 ``` org.springf ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...