Netty 系列四(ChannelHandler 和 ChannelPipeline).

来源:https://www.cnblogs.com/jmcui/archive/2018/07/08/9280733.html
-Advertisement-
Play Games

一、概念 先來整體的介紹一下這篇博文要介紹的幾個概念(Channel、ChannelHandler、ChannelPipeline、ChannelHandlerContext、ChannelPromise): Channel:Netty 中傳入或傳出數據的載體;ChannelHandler:Nett ...


一、概念

    先來整體的介紹一下這篇博文要介紹的幾個概念(Channel、ChannelHandler、ChannelPipeline、ChannelHandlerContext、ChannelPromise):

Channel:Netty 中傳入或傳出數據的載體;
ChannelHandler:Netty 中處理入站和出站數據的應用程式邏輯的容器;
ChannelPipeline:ChannelHandler鏈 的容器;
ChannelHandlerContext:代表了 ChannelHandler 和 ChannelPipeline 之間的關聯,每當有ChannelHandler 添加到 ChannelPipeline 中時,都會創建 ChannelHandlerContext;
ChannelPromise:ChannelPromise是ChannelFuture的一個子類,其定義了一些可寫的方法,如setSuccess()和setFailure(), 從而使ChannelFuture不可變。

    我們來舉一個例子描述這些概念之間的邏輯關係:服務端接收到客戶端的連接請求,創建一個Channel同客戶端進行綁定,新創建的 Channel 會都將會被分配一個新的ChannelPipeline(這項關聯是永久性的,Channel 既不會附加另外一個ChannelPipeline,也不能分離當前的)。而 ChannelPipeline 作為 ChannelHandler鏈 的容器,當Channel 生命周期中狀態發生改變時,將會生成對應的事件,這些事件將會被 ChannelPipeline 中 ChannelHandler 所響應,響應方法的參數一般都有一個 ChannelHandlerContext ,一個 ChannelHandler 對應一個 ChannelHandlerContext。

二、ChannelHandler

    Netty提供了大量預定義的可以開箱即用的ChannelHandler實現,包括用於各種協議的ChannelHandler。因此,我們在自定義ChannelHandler實現用於處理我們的程式邏輯時,只需要繼承Netty 的一些預設實現即可,主要有兩種:

1、繼承 ChannelHandlerAdapter (在4.0 中 處理入站事件繼承 ChannelInboundHandlerAdapter,處理出站事件繼承 ChannelOutboundHandlerAdapter ;在5.0 推薦直接繼承 ChannelHandlerAdapter)

2、繼承 SimpleChannelInboundHandler

    這兩種方式有什麼區別呢?  當我們處理 入站數據 和 出站數據時,都需要確保沒有任何的資源泄露。在入站方向,繼承 SimpleChannelInboundHandler 的實現類會在消息被處理之後自動處理消息,而繼承 ChannelHandlerAdapter 的實現類需要手動的釋放消息(ReferenceCountUtil.release(msg));在出站方向,不管繼承的是哪一種的實現類,當你處理了 write() 操作並丟棄了一個消息,那麼你就應該釋放它,不僅如此,還要通知 ChannelPromise。否則可能會出現 ChannelFutureListener 收不到某個消息已經被處理了的通知的情況。

@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
    ReferenceCountUtil.release(msg);
    //ChannelPromise 是ChannelFuture的一個子類,設置成 true 通知 ChannelFutureListener 消息已經被處理了
    //當一個 Promise 被完成之後,其對應的 Future 的值便不能再進行任何修改了
    promise.setSuccess();
}

    tips:總之,如果一個消息被消費或者丟棄了, 並且沒有傳遞給 ChannelPipeline 中的下一個ChannelOutboundHandler, 那麼用戶就有責任調用 ReferenceCountUtil.release()。

    來看看 ChannelHandler 的 API:

isSharable :如果其對應的實現被標註為 Sharable, 那麼這個方法將返回 true, 表示它可以被添加到多個 ChannelPipeline中

-- ChannelHandler 生命周期方法 --
handlerAdded :當把 ChannelHandler 添加到 ChannelPipeline 中時被調用
handlerRemoved :當從 ChannelPipeline 中移除 ChannelHandler 時被調用
exceptionCaught : 當處理過程中在 ChannelPipeline 中有錯誤產生時被調用

-- 處理入站數據以及各種狀態變化 --
channelRegistered : 當 Channel 已經註冊到它的 EventLoop 並且能夠處理 I/O 時被調用
channelUnregistered : 當 Channel 從它的 EventLoop 註銷並且無法處理任何 I/O 時被調用
channelActive : 當 Channel 處於活動狀態時被調用;Channel 已經連接/綁定並且已經就緒
channelInactive : 當 Channel 離開活動狀態並且不再連接它的遠程節點時被調用
channelReadComplete : 當Channel上的一個讀操作完成時被調用
channelRead : 當從 Channel 讀取數據時被調用
ChannelWritabilityChanged :當 Channel 的可寫狀態發生改變時被調用。
userEventTriggered : 當 ChannelnboundHandler.fireUserEventTriggered()方法被調用時被調用,因為一個 POJO 被傳經了 ChannelPipeline

-- 處理出站數據並且允許攔截所有的操作 --
bind : 當請求將 Channel 綁定到本地地址時被調用
connect : 當請求將 Channel 連接到遠程節點時被調用
disconnect : 當請求將 Channel 從遠程節點斷開時被調用
close : 當請求關閉 Channel 時被調用
deregister : 當請求將 Channel 從它的 EventLoop 註銷時被調用
read : 當請求從 Channel 讀取更多的數據時被調用
flush : 當請求通過 Channel 將入隊數據沖刷到遠程節點時被調用
write :當請求通過 Channel 將數據寫到遠程節點時被調用

三、ChannelPipeline

    ChannelPipeline 是一個攔截流經 Channel 的入站和出站事件的ChannelHandler 實例鏈,它和 ChannelHandler 之間的交互組成了應用程式數據和事件處理邏輯的核心,而它們之間的關聯交互就是通過 ChannelHandlerContext。

    如果一個入站事件被觸發,它將被從 ChannelPipeline 的頭部開始一直被傳播到 Channel Pipeline 的尾端。如圖,Netty 總是將 ChannelPipeline 的入站口作為頭部,而將出站口作為尾端,如圖,第一個被入站事件看到的 ChannelHandler 將是1,而第一個被出站事件看到的是 ChannelHandler 將是 5。

    既然 ChannelPipeline 是 ChannelHandler鏈 的容器,讓我們來看看ChannelPipeline 是如何管理 ChannelHandler的吧!

addFirst : 將一個 ChannelHandler 添加到 ChannelPipeline 最開始位置中
addBefore :將一個 ChannelHandler 添加到 ChannelPipeline 某個ChannelHandler前
addAfter:將一個 ChannelHandler 添加到 ChannelPipeline 某個ChannelHandler後
addLast : 將一個 ChannelHandler 添加到 ChannelPipeline 最末尾位置
remove :將一個 ChannelHandler 從 ChannelPipeline 中移除
replace :將 ChannelPipeline 中的一個 ChannelHandler 替換為另一個 ChannelHandler
get :通過類型或者名稱返回 ChannelHandler
context :返回和 ChannelHandler 綁定的 ChannelHandlerContext
names :返回 ChannelPipeline 中所有 ChannelHandler 的名稱

    ChannelPipeline 的API 用於調用入站操作的附加方法:

fireChannelRegistered: 調用 ChannelPipeline 中下一個 ChannelInboundHandler 的 channelRegistered(ChannelHandlerContext)方法
fireChannelUnregistered: 調用 ChannelPipeline 中下一個 ChannelInboundHandler 的channelUnregistered(ChannelHandlerContext)方法
fireChannelActive: 調用 ChannelPipeline 中下一個 ChannelInboundHandler 的channelActive(ChannelHandlerContext)方法
fireChannelInactive: 調用 ChannelPipeline 中下一個 ChannelInboundHandler 的channelInactive(ChannelHandlerContext)方法
fireExceptionCaught: 調用 ChannelPipeline 中下一個 ChannelInboundHandler 的exceptionCaught(ChannelHandlerContext, Throwable)方法
fireUserEventTriggered: 調用 ChannelPipeline 中下一個 ChannelInboundHandler 的userEventTriggered(ChannelHandlerContext, Object)方法
fireChannelRead: 調用 ChannelPipeline 中下一個 ChannelInboundHandler 的channelRead(ChannelHandlerContext, Object msg)方法
fireChannelReadComplete: 調用 ChannelPipeline 中下一個 ChannelInboundHandler 的channelReadComplete(ChannelHandlerContext)方法
fireChannelWritabilityChanged: 調用 ChannelPipeline 中下一個 ChannelInboundHandler 的channelWritabilityChanged(ChannelHandlerContext)方法

    ChannelPipeline 的API 用於調用出站操作的附加方法:

bind: 將 Channel 綁定到一個本地地址,這將調用 ChannelPipeline 中的下一個ChannelOutboundHandler 的 bind方法
connect: 將 Channel 連接到一個遠程地址,這將調用 ChannelPipeline 中的下一個ChannelOutboundHandler 的 connect方法
disconnect: 將 Channel 斷開連接。這將調用 ChannelPipeline 中的下一個 ChannelOutboundHandler 的 disconnect方法
close: 將 Channel 關閉。這將調用 ChannelPipeline 中的下一個 ChannelOutboundHandler 的 close方法
deregister: 將 Channel 從它先前所分配的 EventExecutor(即 EventLoop)中註銷。這將調用 ChannelPipeline 中的下一個 ChannelOutboundHandler 的 deregister方法
flush: 沖刷Channel所有掛起的寫入。這將調用ChannelPipeline中的下一個ChannelOutboundHandler 的 flush方法
write: 將消息寫入 Channel。這將調用 ChannelPipeline 中的下一個 ChannelOutboundHandler的write方法。註意:這並不會將消息寫入底層的 Socket,而只會將它放入隊列中。要將它寫入 Socket,需要調用 flush()或者 writeAndFlush()方法
writeAndFlush: 這是一個先調用 write()方法再接著調用 flush()方法的便利方法
read: 請求從 Channel 中讀取更多的數據。這將調用 ChannelPipeline 中的下一個ChannelOutboundHandler 的 read(ChannelHandlerContext)方法

四、ChannelHandlerContext

    ChannelHandlerContext 代表了 ChannelHandler 和 ChannelPipeline 之間的關聯。

    ChannelHandlerContext 有很多的方法,其中一些方法也存在於 Channel 和 ChannelPipeline 本身上,但是有一點重要的不同。如果調用 Channel 或者 ChannelPipeline 上的這些方法,它們將沿著整個 ChannelPipeline 進行傳播。而調用位於 ChannelHandlerContext上的相同方法,則將從當前所關聯的 ChannelHandler 開始,並且只會傳播給位於該ChannelPipeline 中的下一個能夠處理該事件的 ChannelHandler。因此,儘量使用 ChannelHandlerContext 的同名方法來處理邏輯,因為它將產生更短的事件流, 應該儘可能地利用這個特性來獲得最大的性能。

五、寄語

    有時候也會迷茫,身邊的人理論基礎差一些的的,代碼不一樣敲的好好的?而我花大量時間細細的去研究這麼理論真的值得嗎?仔細想想,人生很多事情本來就是徒勞無功的啊,沒必要急功近利,欲速則不達。要堅信,一切的付出總會在人生的某個時刻回報在我們身上。


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

-Advertisement-
Play Games
更多相關文章
  • Flask的g對象 g可以可以看作是單詞global的縮寫,使用“from flask import g”導入,g對象的作用是保存一些在一次請求中多個地方的都需要用到的數據,這些數據可能在用到的時候都需要去進行判斷或其他處理之後才能獲得,如果在第一次獲取的時候就存放到g對象中,就可以避免一些不必要的 ...
  • 中午吃飯時看了一下陸毅版的《三國》,剛好看的是蜀軍缺糧,諸葛亮讓王平去劫司馬懿的糧。司馬懿看蜀軍用木牛流馬運量很方便,就搶了蜀軍的木牛流馬仿製了一批,結果司馬懿用它運糧時,被王平冒充司馬懿的人在驗糧時,對木牛流馬動了手腳,結果木牛流馬不能動彈了,被蜀軍把幾十萬擔的糧食搶走了。看到這裡的時候,我想到了 ...
  • 下麵是EXE代碼 ...
  • 模型概述 有一DAG,問最少加多少條邊能夠使圖強連通。 題目描述 一些學校連入一個電腦網路。那些學校已訂立了協議:每個學校都會給其它的一些學校分發軟體(稱作“接受學校”)。註意即使 B 在 A 學校的分發列表中, A 也不一定在 B 學校的列表中。 你要寫一個程式計算,根據協議,為了讓網路中所有的學 ...
  • Description 給定一個含有n個數的序列a[1],a[2],a[3]……a[n],程式必須回答這樣的詢問:對於給定的i,j,k,在a[i],a[i+1 ],a[i+2]……a[j]中第k小的數是多少(1≤k≤j-i+1),並且,你可以改變一些a[i]的值,改變後,程式還能針對改 變後的a繼續 ...
  • 前言 微服務概念已經非常流行,這影響了現在架構的思想潮流。 如今,使用spring cloud體系搭建微服務架構的公司越來越多,成本低,出線上產品快,模塊全,開源等原因未來可能更加流行。 一般,我們需要一個監控系統來監控應用的數據,比如記憶體,磁碟,線程情況,資料庫連接池,配置信息,jvm信息等等。 ...
  • 大家好,上篇文章為大家介紹了線程間通信和協作的一些基本方式,那這篇文章就來介紹一下經典的wait-notify機制吧。 什麼是wait-notify機制? 想象一下有兩個線程A、B,如果業務場景中需要這兩個線程交替執行任務(比如A執行完一次任務後換B執行,B執行完後再換A執行這樣重覆交替),之前的基 ...
  • 轉自:https://www.cnblogs.com/Lynn-Zhang/p/5377024.html C/C++程式編譯流程(預處理->編譯->彙編->鏈接) 程式的基本流程如圖: 1. 預處理 預處理相當於根據預處理指令組裝新的C/C++程式。經過預處理,會產生一個沒有巨集定義,沒有條件編譯指令 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...