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
  • 下麵是一個標準的IDistributedCache用例: public class SomeService(IDistributedCache cache) { public async Task<SomeInformation> GetSomeInformationAsync (string na ...
  • 這個庫提供了在啟動期間實例化已註冊的單例,而不是在首次使用它時實例化。 單例通常在首次使用時創建,這可能會導致響應傳入請求的延遲高於平時。在註冊時創建實例有助於防止第一次Request請求的SLA 以往我們要在註冊的時候實例單例可能會這樣寫: //註冊: services.AddSingleton< ...
  • 最近公司的很多項目都要改單點登錄了,不過大部分都還沒敲定,目前立刻要做的就只有一個比較老的項目 先改一個試試手,主要目標就是最短最快實現功能 首先因為要保留原登錄方式,所以頁面上的改動就是在原來登錄頁面下加一個SSO登錄入口 用超鏈接寫的入口,頁面改造後如下圖: 其中超鏈接的 href="Staff ...
  • Like運算符很好用,特別是它所提供的其中*、?這兩種通配符,在Windows文件系統和各類項目中運用非常廣泛。 但Like運算符僅在VB中支持,在C#中,如何實現呢? 以下是關於LikeString的四種實現方式,其中第四種為Regex正則表達式實現,且在.NET Standard 2.0及以上平... ...
  • 一:背景 1. 講故事 前些天有位朋友找到我,說他們的程式記憶體會偶發性暴漲,自己分析了下是非托管記憶體問題,讓我幫忙看下怎麼回事?哈哈,看到這個dump我還是非常有興趣的,居然還有這種游戲幣自助機類型的程式,下次去大玩家看看他們出幣的機器後端是不是C#寫的?由於dump是linux上的程式,剛好win ...
  • 前言 大家好,我是老馬。很高興遇到你。 我們為 java 開發者實現了 java 版本的 nginx https://github.com/houbb/nginx4j 如果你想知道 servlet 如何處理的,可以參考我的另一個項目: 手寫從零實現簡易版 tomcat minicat 手寫 ngin ...
  • 上一次的介紹,主要圍繞如何統一去捕獲異常,以及為每一種異常添加自己的Mapper實現,並且我們知道,當在ExceptionMapper中返回非200的Response,不支持application/json的響應類型,而是寫死的text/plain類型。 Filter為二方包異常手動捕獲 參考:ht ...
  • 大家好,我是R哥。 今天分享一個爽飛了的面試輔導 case: 這個杭州兄弟空窗期 1 個月+,面試了 6 家公司 0 Offer,不知道問題出在哪,難道是杭州的 IT 崩盤了麽? 報名面試輔導後,經過一個多月的輔導打磨,現在成功入職某上市公司,漲薪 30%+,955 工作制,不咋加班,還不捲。 其他 ...
  • 引入依賴 <!--Freemarker wls--> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.30</version> </dependency> ...
  • 你應如何運行程式 互動式命令模式 開始一個互動式會話 一般是在操作系統命令行下輸入python,且不帶任何參數 系統路徑 如果沒有設置系統的PATH環境變數來包括Python的安裝路徑,可能需要機器上Python可執行文件的完整路徑來代替python 運行的位置:代碼位置 不要輸入的內容:提示符和註 ...