Netty源碼學習3——Channel ,ChannelHandler,ChannelPipeline

来源:https://www.cnblogs.com/cuzzz/archive/2023/08/27/17661053.html
-Advertisement-
Play Games

[系列文章目錄和關於我](https://www.cnblogs.com/cuzzz/p/16609728.html) ## 零丶引入 在[Netty源碼學習2——NioEventLoop的執行](https://www.cnblogs.com/cuzzz/p/17641482.html)中,我們學 ...


系列文章目錄和關於我

零丶引入

Netty源碼學習2——NioEventLoop的執行中,我們學習了NioEventLoop是如何進行事件迴圈以及如何修複NIO 空輪詢的bug的,但是沒有深入瞭解IO事件在netty中是如何被處理的,下麵我們以服務端demo代碼為例子,看下和IO事件處理密切的Channel

image-20230827172001638

如上在編寫netty 服務端的時候,我們一般只需要指定Channel類型,以及實現ChannelHandler在對應方法中編寫業務邏輯代碼即可。

在Netty中,NioEventLoop是事件的調度中心,它控制了Io事件和其他任務的調度,但是io事件的處理是依賴ChannelHandler的,多個ChannelHandler又由ChannelPipline組裝成流水線依次執行

這篇博客我們以此為切入點,看看Channel是如何初始化的,如何和EventLoop關聯起來的,後續看看ChannelPipline是如何組織ChannelHandler的。

一丶Channel概述

Channel是Netty抽象出來的對網路I/O進行讀寫的相關介面,與JDK NIO中的Channel介面類似。Channel的主要功能有網路I/O的讀寫綁定埠客戶端發起連接主動關閉連接獲取通信雙方網路地址等。

下麵是NioServerSocketChannel,和NioSocketChannel的類圖

image-20230827173532237
  • ChannelOutboundInvoker:定義了bind,connect,disconnect,close等方法。

  • Channel:Netty抽象出來的對網路I/O進行讀寫的相關介面,其中有兩個關鍵的方法

    image-20230827174534161

    可看出EventLoop和Channel的關係——Channel是註冊到EventLoop中的。

    ChannelPipeline和Channel的關係——每一個Channel會分配一個ChannelPipline。

  • AbstractChannel:Channel基本的實現,在其中有幾個重要的屬性:

    image-20230827175209476
    • unsafe:非jdk中的unsafe,這是netty定義的unsafe,其中定義了bind,connect等方法,在實際和網路打交道的時候會使用到
    • DefaultChannelPipeline:ChannelPipeline的預設實現,這就是Channel分配的ChannelPipline
    • EventLoop:此Channel註冊到的EventLoop
  • AbstractNioChannel:使用Selector進行IO多路復用的Channel,其中有兩個重要的屬性

    image-20230827175848808

    從此可與看出,Netty中的Nio Channel 和 jdk中Channel的關係,它們是一對一的

    AbstractNioChannel擁有NIO的Channel,具備NIO的註冊、連接等功能。但IO的讀寫交給了其子類。

  • AbstractNioByteChannel&AbstractNioMessageChannel

    AbstractNioByteChannel是面向位元組的,通常是客戶端進行使用,AbstractNioMessageChannel面向消息,通過是服務端進行使用。為什麼netty這樣設計昵?

    通常情況下,客戶端只需要發送消息,因此直接將消息內容轉換為位元組流進行輸出即可,這時候使用AbstractNioByteChannel更為合適。
    
    服務端在接收到客戶端的消息後,需要對消息進行解碼、反序列化、處理等操作,這時候使用AbstractNioMessageChannel更為合適。因為AbstractNioMessageChannel提供了方便的消息處理功能,可以對收到的位元組流進行解碼、轉換成特定的消息對象,並提供了方便的事件驅動機制,方便開發者對消息進行處理和管理。
    
    例如,在自定義的RPC協議中,服務端需要解碼和反序列化請求消息,調用相應的服務方法,並將響應消息轉換為位元組流輸出。使用AbstractNioMessageChannel可以方便地處理這些消息讀寫操作,使用自定義的解碼器和編碼器可以將位元組流轉換為特定的消息對象,並且在ChannelHandler中實現業務邏輯,通過事件驅動機制方便地管理消息的讀寫和處理。
    
    綜上所述,AbstractNioByteChannel適合用於處理位元組流數據,適合用於客戶端。而AbstractNioMessageChannel適合用於處理消息,適合用於服務端
    
  • NioSocketChannel

    NioSocketChannel是AbstractNioByteChannel的子類,NioSocketChannel封裝了NIO中的SocketChannel,實現了IO的讀寫連接操作。Netty服務的每個連接都會生成一個NioSocketChannel對象

  • NioServerSocketChannel

    NioServerSocektChannel是AbstractNioMessageChannel的子類,一般是服務端進行使用,並且只負責監聽Socket的接入,不關心IO讀寫。

二丶服務端啟動是NioServerSocketChannel是如何實例化並註冊到EventLoop中的

客戶端的NioSocketChannel實例化和註冊流程與服務端類似
image-20230827183358838

如上代碼中,服務端指定了Channel類型,然後調用bind方法,在bind方法中會進行Channel的初始化+註冊到EventLoop中

image-20230827183607939

1.NioServerSocketChannel的實例化

在不指定ChannelFactory的時候,這裡預設是ReflectiveChannelFactory,如同其名稱一樣,ReflectiveChannelFactory會使用反射的方式構建出Channel

image-20230827184150871

隨後會使用SelectorProvider#openServerSocketChannel創建出一個jdk原生的ServerSocketChannel。

然後調用父類構造器,設置Channel為非阻塞,並調用newUnsafe和newChannelPipeline實例化unsafe和channelPipeline

image-20230827184557990

對於服務端來說這裡newUnsafe產生的是NioMessageUnsafe,ChannelPipeline通常使用的是DefaultChannelPipeline

2.NioServerSocketChannel的初始化

image-20230827185312380

初始化會將我們在ServerBootStrap中設置的參數設置到NioServerSocketChannel中

並向ChannelPipeline添加一個ServerBootstrapAcceptor,ServerBootstrapAcceptor和Netty的reactor模式有關,此類的作用後續進行學習。

3.NioServerSocketChannel的註冊

隨後會使用ServerBootStrap中的EventLoopGroup#register方法進行註冊,這裡的使用的EventLoopGroup是demo中指定的bossGroup

image-20230827185813543

因為在Reactor模式中,bossGroup負責處理ACCEPT事件,單線程使用Selector監聽多路IO的Accept事件,然後將這些套接字交給上面的WorkerGroup,so這裡NioServerSocketChannel註冊到bossGroup中

bossGroup並不會自己進行註冊,而是使用next方法找到自己的小弟——EventLoop,進行註冊

image-20230827190208865

這裡會使用到EventExecutorChooser進行選擇EventLoop,Netty自帶兩種策略

  • 如果EventLoopGroup中的EventLoop是2的冪次個,那麼使用PowerOfTwoEventExecutorChooser

    image-20230827190446255

    它使用取模在眾多EventLoop中選擇一個

  • 如果EventLoopGroup中的EventLoop不是2的冪次個,那麼使用GenericEventExecutorChooser,直接對EventLoop個數進行取模

選擇完EventLoop後,會調用EventLoop的註冊方法,最終會使用AbstractUnsafe#register,其中會先判斷執行的線程是不是EventLoop線程,如果不是那麼會將任務提交到EventLoop中執行,源碼如下:

image-20230827190732474

註冊的即將當前Channel註冊到Selector,並且attachment指定為當前Channel,這樣NioEventLoop在進行IO多路復用的的時候,可通過attachment方法拿到當前Channel

image-20230827191127338

註冊結束後會使用ChannelPipeline觸發channelRegistered事件,關於ChannelPipeline下一篇博客中進行學習。

在這一步,還會觸發NioEventLoop線程的啟動,進行事件迴圈,在一個死迴圈中使用Selector監聽這個Channel的IO事件,並處理其他調度任務,非同步任務。(如何啟動NioEventLoop線程的——Netty源碼學習2——NioEventLoop的執行#NioEventLoop的啟動

三丶ChannelPipeline

image-20230827214728636

上面我們說到一個Channel的實例化會觸發ChannelPipeline的實例化。ChannelPipeline 和 ChannelHandler 也是我們在平時應用開發的過程中打交道最多的組件,通常程式員使用Netty進行開發只需要將自己定義的ChannelHandler加入到ChannelPipeline中。

ChannelPipeline即是ChannelHandler的流水線,ChannelPipeline 可以看作是 ChannelHandler 的容器載體,它是由一組 ChannelHandler 實例組成的,內部通過雙向鏈表將不同的 ChannelHandler 鏈接在一起,如下圖所示。當有 I/O 讀寫事件觸發時,ChannelPipeline 會依次調用 ChannelHandler 列表對 Channel 的數據進行攔截和處理。

image-20230827164029646

1.ChannelHandlerContext

ChannelHandlerContext 用於保存 ChannelHandler 上下文,其包含了 ChannelHandler 生命周期的所有事件,如 connect、bind、read、flush、write、close 等。

2.ChannelInboundHandler 和 ChannelOutboundHandler

在客戶端與服務端通信的過程中,數據從客戶端發向服務端的過程叫出站,反之稱為入站。數據先由一系列 InboundHandler 處理後入站,然後再由相反方向的 OutboundHandler 處理完成後出站。

3.DefaultChannelPipeline

image-20230827215052052

DefaultChannelPipeline是netty中ChannelPipeline的預設實現,內部保存了HeadContext,和TailContext分別作為鏈表的頭和尾

image-20230827223142192

可以看到HeadContext即是ChannelOutboundInvoker(出站處理器)也是ChannelInboundInvoker(出站處理器),這是因為網路數據寫入操作的入口就是由 HeadContext 節點完成的。HeadContext 作為 Pipeline 的頭結點負責讀取數據並開始傳遞 入站事件,當數據處理完成後,數據會反方向經過各個 ChannelOutboundInvoker的處理,最終傳遞到 HeadContext。

而TailContext只實現了ChannelInboundInvoker,它是最後一個ChannelInboundInvoker,用於結束入站事件的傳播。

4.ChannelInboundHandler&ChannelOutboundHandler

二者都是ChannelHandler的子介面,其方法的聲明對於了Netty中對事件的抽象

image-20230827224300434

4.1 ChannelInboundHandler

方法名&事件
channelRegistered 當Channel註冊到它的EventLoop並且能夠處理I/O時調用
channelUnregistered 當Channel從它的EventLoop中註銷並且無法處理任何I/O時調用
channelActive 當Channel處理於活動狀態時被調用
channelInactive 不再是活動狀態且不再連接它的遠程節點時被調用
channelReadComplete 當Channel上的一個讀操作完成時被調
channelRead 當從Channel讀取數據時被調用
channelWritabilityChanged 當Channel的可寫狀態發生改變時被調用
userEventTriggered 當ChannelInboundHandler.fireUserEventTriggered()方法被調用時觸發

4.2 ChannelOutBoundHandler

方法名&事件
bind 當請求將Channel綁定到本地地址時被調用
connet 當請求將Channel連接到遠程節點時被調用
disconnect 當請求將Channel從遠程節點斷開時調用
close 當請求關閉Channel時調用
deregister 當請求將Channel從它的EventLoop註銷時調用
read 當請求從Channel中讀取數據時調用
flush 當請求通過Channel將入隊數據沖刷到遠程節點時調用
write 當請求通過Channel將數據寫入遠程節點時被調用

四丶總結

此篇初探了Channel,ChannelPipeline,ChannelContext,ChannelHandler之間的關係,深入學習了Netty中的Nio Channel是怎麼和jdk中的Channel組織起來的。

上面我們說到在Netty中,NioEventLoop是事件的調度中心,它控制了Io事件和其他任務的調度,但是io事件的處理是依賴ChannelHandler的,多個ChannelHandler又由ChannelPipline組裝成流水線依次執行

那麼一個網路請求在Netty中是怎麼從NioEventLoop事件迴圈中交由ChannelPipline進行事件傳播與處理的昵?這個下篇中進行學習和總結。


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

-Advertisement-
Play Games
更多相關文章
  • 工具提示即 Tool Tip,當用戶把滑鼠移動到某個UI對象上並懸停片刻,就會出現一個“短小精悍”的視窗,顯示一些說明性文本。一般就是功能描述,讓用戶知道這個XX是幹啥用的。 在 Qt 中使用工具提示最方便的做法是直接用 QWidget 類的成員方法:setToolTip。從 QWidget 類派生 ...
  • # 什麼是主席樹 主席樹這個名字看上去很高級,其實不然,它還有另一個名字——可持久化線段樹。 ## 什麼是可持久化 可持久化顧名思義就是它可以變得~~**持久**~~,就是我們對他不斷進行單點修改後,突然查詢它的某一個歷史版本,這就叫可持久化。 # 引入例題 [洛谷3919:可持久化數組](http ...
  • ## 前言: 單例模式是創建型模式5種中的第1種,**關註對象的創建, 保證一個類僅有一個實例,並且提供一個全局訪問點**。在軟體系統中,經常有這樣一些特殊的類,必須保證它們在系統中只存在一個實例,才能確保它們的邏輯正確性、以及良好的效率。如何繞過常規的構造器,提供一種機制來保證一個類只創建一個實例 ...
  • ## 一、設計模式概述: ​ **設計模式(Design pattern)**代表了最佳的實踐,通常被有經驗的面向對象的軟體開發人員所採用。設計模式是軟體開發人員在軟體開發過程中面臨的一般問題的解決方案。這些解決方案是眾多軟體開發人員經過相當長的一段時間的試驗和錯誤總結出來的。是一套被反覆使用的、多 ...
  • 一、 ManualResetEvent ManualResetEvent是一個同步對象,可以使一個或多個線程等待另一個線程的信號,然後再同時繼續執行。它是通過兩個狀態來實現的:有信號和無信號。 以下是ManualResetEvent的各個方法的介紹: Set方法:將ManualResetEvent的 ...
  • S905L3A(M401A)拆解, 運行EmuELEC和Armbian. S905Lx系列屬於大客戶版本, 對外沒有公開資料, 最早的 S905L/S905LB 是 S905X 的馬甲, 而這個 S905L3A/S905L3AB 則是 S905X2 的馬甲, 因為在性能評測里這兩個U的得分幾乎一樣.... ...
  • 功能 設計一個傳送帶系統,能夠實現傳送帶的開始/停止,正轉/反轉,加減速,對傳送帶的物品計數。 按鈕/app功能控制,oled屏幕/app顯示。 設計框圖 原理圖 軟體構建階段 利用STM32CubeMX生成模板 MCU選型:STM32F103C8T6,雙擊打開。 Ststem Core->SYS: ...
  • # 背景 再很多場景中,我們可能想在子組件中修改父組件的數據,但事實上,vue不推薦我們這麼做,因為數據的修改不容易溯源。 ## Vue2寫法 在vue2中,我們使用`.sync`修飾符+自定義事件`'update:xxx'`,來使父子組件數據同步。 ```html // 父組件 我是父組件,我有{ ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...