【Netty】ChannelHandler和ChannelPipeline

一、前言

  前面學習了Netty的ByteBuf,接著學習ChannelHandler和ChannelPipeline。

二、ChannelHandler和ChannelPipeline

  2.1 ChannelHandler

  在ChannelPipeline中,ChannelHandler可以被鏈在一起處理用戶邏輯。

  1. Channel生命周期

  Channel接口定義了一個簡單但是強大的狀態模型,該模型與ChannelInboundHandler API緊密聯系,Channel有如下四種狀態。

  

  Channel的生命周期如下圖所示。

  

  當狀態發生變化時,就會產生相應的事件。

  2. ChannelHandler的生命周期

  ChannelHandler定義的生命周期如下圖所示。

  

  Netty定義了ChannelHandler的兩個重要的子類

    · ChannelInboundHandler,處理各種入站的數據和狀態的變化。

    · ChannelOutboundHandler,處理出站數據并允許攔截的所有操作。

  3. ChannelInboundHandler接口

  下圖展示了ChannelInboundHandler接口生命周期中的方法,當接受到數據或者其對應的Channel的狀態發生變化則會調用方法

  

  當ChannelInboundHandler的實現覆蓋channelRead()方法時,它負責顯式釋放與池的ByteBuf實例相關聯的內存,可以使用ReferenceCountUtil.release() 方法進行釋放。如下代碼展示了該方法的使用。

public class DiscardHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {ReferenceCountUtil.release(msg);}
}

  上述顯式的釋放內存空間會顯得比較麻煩,而如下代碼則無需顯式釋放內存空間。  

@Sharable
public class SimpleDiscardHandlerextends SimpleChannelInboundHandler<Object> {@Overridepublic void channelRead0(ChannelHandlerContext ctx,Object msg) {// No need to do anything special
    }
}

  上述代碼中,SimpleChannelInboundHandler會自動釋放資源,因此無需顯式釋放。

  4. ChannelOutboundHandler接口

  ChannelOutboundHandler處理出站操作和數據,它的方法會被Channel、ChannelPipeline、ChannelHandlerContext觸發。ChannelOutboundHandler可按需推遲操作或事件。例如對遠程主機的寫入被暫停,你可以延遲刷新操作并在稍后重啟。

  5. ChannelHandler適配器

  可以使用ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter類作為自己的ChannelHandler程序的起點,這些適配器提供了ChannelInboundHandler和ChannelOutboundHandler的簡單實現,它們繼承了共同父類接口ChannelHandler的方法,其繼承結構如下圖所示

  

  ChannelHandlerAdapter還提供了isSharable方法,如果有Sharable注釋則會返回true,這也表明它可以被添加至多個ChannelPipiline中。ChannelInboundHandlerAdapter and ChannelOutboundHandlerAdapter的方法體中會調用ChannelHandlerContext對應的方法,因此可以將事件傳遞到管道的下個ChannelHandler中。

  6. 資源管理

  無論何時調用ChannelInboundHandler.channelRead()和ChannelOutboundHandler.write()方法,都需要保證沒有資源泄露,由于Netty使用引用計數來管理ByteBuf,因此當使用完ByteBuf后需要調整引用計數。

  為了診斷可能出現的問題,Netty提供了ResourceLeakDetector,它將抽取應用程序大約1%的緩沖區分配來檢查內存泄漏,其額外的開銷很小,內存檢測有如下四種級別

  

  可以通過java -Dio.netty.leakDetectionLevel=ADVANCED 設置內存檢測級別。

  當讀取數據時,可以在readChannel方法中調用ReferenceCountUtil.release(msg)方法釋放資源,或者實現SimpleChannelInboundHandler(會自動釋放資源);而當寫入數據時,可以在write方法中調用ReferenceCountUtil.release(msg)釋放資源,具體代碼如下 

@Sharable
public class DiscardOutboundHandlerextends ChannelOutboundHandlerAdapter {@Overridepublic void write(ChannelHandlerContext ctx,Object msg, ChannelPromise promise) {ReferenceCountUtil.release(msg);promise.setSuccess();}
}

  不僅需要釋放資源,并且需要通知ChannelPromise,否則ChannelFutureListener可能收不到事件已經被處理的通知。如果消息到達實際的傳輸層,就可以在寫操作完成或者關閉通道時會自動釋放資源。

  2.2?ChannelPipeline接口

  如果將ChannelPipeline視為ChannelHandler實例鏈,可攔截流經通道的入站和出站事件,即可明白ChannelHandler之間的交互是如何構成應用程序數據和事件處理邏輯的核心的。當創建一個新的Channel時,都會分配了一個新的ChannelPipeline,該關聯是永久的,該通道既不能附加另一個ChannelPipeline也不能分離當前的ChannelPipeline。

  一個事件要么被ChannelInboundHander處理,要么被ChannelOutboundHandler處理,隨后,它將通過調用ChannelHandlerContext的實現來將事件轉發至同一超類型的下一個處理器。ChannelHandlerContext允許ChannelHandler與其ChannelPipeline和其他ChannelHandler進行交互,一個處理器可以通知ChannelPipeline中的下一個處理器,甚至可以修改器隸屬于的ChannelPipeline。

  下圖展示了ChannelHandlerPipeline、ChannelInboundHandler和ChannelOutboundHandler之間的關系

  

  可以看到ChannelPipeline是由一系列ChannelHandlers組成,其還提供了通過自身傳播事件的方法,當進站事件觸發時,其從ChannelPipeline的頭部傳遞到尾部,而出站事件會從右邊傳遞到左邊。

  當管道傳播事件時,其會確定下一個ChannelHandler的類型是否與移動方向匹配,若不匹配,則會跳過并尋找下一個,直至找到相匹配的ChannelHandler(一個處理器可以會同時實現ChannelInboundHandler和ChannelOutboundHandler)。

  1. 修改ChannelPipeline

  ChannelHandler可實時修改ChannelPipeline的布局,如添加、刪除、替換其他ChannelHandler(其可從ChannelPipeline中移除自身),如如下圖所示的方法。

  

  通常,ChannelPipeline中的每個ChannelHandler通過其EventLoop(I / O線程)處理傳遞給它的事件,不要阻塞該線程,因為它會對I/O的整體處理產生負面影響。

  2.3?ChannelHandlerContext接口

  ChannelHandlerContext代表了ChannelHandler與ChannelPipeline之間的關聯,當ChannelHandler被添加至ChannelPipeline中時其被創建,ChannelHandlerContext的主要功能是管理相關ChannelHandler與同一ChannelPipeline中的其他ChannelHandler的交互。

  ChannelHandlerContext中存在很多方法,其中一些也存在于ChannelHandler和ChannelPipeline中,但是差別很大。如果在ChannelHandler或者ChannelPipeline中調用該方法,它們將在整個管道中傳播,而如果在ChannelHandlerContext中調用方法,那么會僅僅傳遞至下個能處理該事件的ChannelHandler。

  1. 使用ChannelHandlerContext

  ChannelHandlerContext、ChannelHandler、ChannelHandlerContext、Channel之間的關系如下圖所示

  

  可以通過ChannelHandlerContext來訪問Channel,并且當調用Channel的write方法時,寫事件會在管道中傳遞,代碼如下 

ChannelHandlerContext ctx = ..;
Channel channel = ctx.channel();
channel.write(Unpooled.copiedBuffer("Netty in Action",
CharsetUtil.UTF_8));

  除了使用Channel的write方法寫入數據外,還可以使用ChannelPipeline的write方法寫入數據,代碼如下 

ChannelHandlerContext ctx = ..;
ChannelPipeline pipeline = ctx.pipeline();
pipeline.write(Unpooled.copiedBuffer("Netty in Action",
CharsetUtil.UTF_8));

  上述兩段代碼在管道中產生的效果相同,如下圖所示。

  

  其中兩種方法的寫事件都會通過ChannelHandlerContext在管道中傳播。

  若想從指定的ChannelHandler開始傳遞事件,那么需要引用到指定ChannelHandler之前的一個ChannelHandlerContext,該ChannelHandlerContext將調用其關聯的ChannelHandler。

  如下圖所示,展示了從指定ChannelHandler開始處理事件。

  

  2.?ChannelHandler和ChannelHandlerContext的高級用法

  可以通過調用ChannelHandlerContext的pipeline方法獲得其對應的ChannelPipeline引用,這可以在運行中管理ChannelHandler,如添加一個ChannelHandler。

  另一種高級用法是緩存ChannelHandlerContext的引用,以供之后使用。如下代碼展示了用法 

public class WriteHandler extends ChannelHandlerAdapter {private ChannelHandlerContext ctx;@Overridepublic void handlerAdded(ChannelHandlerContext ctx) {this.ctx = ctx;}public void send(String msg) {ctx.writeAndFlush(msg);}
}        

  因為ChannelHandler可以屬于多個ChannelPipeline,所以它可以綁定到多個ChannelHandlerContext實例,當使用時必須使用@Sharable注釋,否則,當將其添加至多個ChannelPipeline時會拋出異常。如下代碼所示  

@Sharable
public class SharableHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {System.out.println("Channel read message: " + msg);ctx.fireChannelRead(msg);}
}

  @Sharable注釋沒有任何狀態,而如下代碼會出現錯誤。  

@Sharable
public class UnsharableHandler extends           ChannelInboundHandlerAdapter {private int count;@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {count++;System.out.println("channelRead(...) called the "+ count + " time");ctx.fireChannelRead(msg();}
}

  由于上述代碼包含了狀態,即count計數,將此類的實例添加到ChannelPipeline時,在并發訪問通道時很可能會產生錯誤。可通過在channelRead方法中進行同步來避免錯誤。

  2.4 異常處理

  在出站和進站時,可能會發生異常,Netty提供了多種方法處理異常。

  1. 處理進站異常

  當處理進站事件時發生異常,它將從ChannelInboundHandler中被觸發的位置開始流過ChannelPipeline,為處理異常,需要在實現ChannelInboundHandler接口是重寫exceptionCaught方法。如下示例所示。  

public class InboundExceptionHandler extends ChannelInboundHandlerAdapter {@Overridepublic void exceptionCaught(ChannelHandlerContext ctx,Throwable cause) {cause.printStackTrace();ctx.close();}
}

  由于異常會隨著進站事件在管道中傳遞,包含異常處理的ChannelHandler通常放在了管道的尾部,因此可以保證無論異常發生在哪個ChannelHandler中,其最終都會被處理。

  2. 處理出站異常

  在出站操作中處理的正常完成和處理異常都基于以下通知機制。

    · 每個出站操作返回一個ChannelFuture,在ChannelFuture注冊的ChannelFutureListeners在操作完成時通知成功或錯誤。

    · ChannelOutboundHandler的幾乎所有方法都會傳遞ChannelPromise實例,ChannelPromise是ChannelFuture的子類,其也可以為異步通知分配監聽器,并ChannelPromise還提供可寫的方法來提供即時通知。可通過調用ChannelFuture實例的addListener(ChannelFutureListener)方法添加一個ChannelFutureListener,最常用的是調用出站操作所返回的ChannelFuture的addListener方法,如write方法,具體代碼如下所示。  

ChannelFuture future = channel.write(someMessage);
future.addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture f) {if (!f.isSuccess()) {f.cause().printStackTrace();f.channel().close();}}
});

  第二種方法是將ChannelFutureListener添加到ChannelPromise中,并將其作為參數傳遞給ChannelOutboundHandler的方法,具體代碼如下所示  

public class OutboundExceptionHandler extends ChannelOutboundHandlerAdapter {@Overridepublic void write(ChannelHandlerContext ctx, Object msg,ChannelPromise promise) {promise.addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture f) {if (!f.isSuccess()) {f.cause().printStackTrace();f.channel().close();}}});}
}

三、總結

  本篇博文講解了ChannelHandler,以及其與ChannelPipeline、ChannelHandlerContext之間的關系,及其之間的交互,同時還了解了如何處理進站與出站時所拋出的異常。謝謝各位園友的觀看~

轉載于:https://www.cnblogs.com/leesf456/p/6901189.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/455590.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/455590.shtml
英文地址,請注明出處:http://en.pswp.cn/news/455590.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

TS流頭部的調整字段

見 http://hi.baidu.com/xumingxsh/blog/item/7b178903f1fa98014afb512f.html http://hi.baidu.com/xumingxsh/blog/item/ba50dba320a10da3caefd02f.html

electron 入坑記

最近有個想法,想寫個簡單的應用程序.平時在 Mac上開發,最終有可能運行在 Windows 上.看了一下,Electron 比較簡單,應該可以一試. 關于安裝 我機器上是有 Node 環境的,按著官方教程 直接 npm install electron 結果運行到 npm install.js就不到了..下午上班有事,也沒管他,結果一…

自動駕駛安全駕駛規則_自動駕駛知識科普 自動駕駛汽車的七大核心技術

自動駕駛技術的本質是用機器視角去模擬人類駕駛員的行為&#xff0c;其技術框架可以分為三個環節&#xff1a;感知層、決策層 和執行層&#xff0c;具體涉及傳感器、計算平臺、算法、高精度地圖、OS、HMI等 多個技術模塊。目前自動駕駛L3商業化技術已經成熟&#xff0c;L4級/L5…

orcal數據操作

1.將數據庫ZHSY完全導出,用戶名baseusernj密碼baseusernj導出到D:\daochu.dmp中 exp baseusernj/baseusernjZHSY filed:\daochu.dmp fully exp baseuserhf/baseuserhfZJCPDB fileC:\105hf.dmp ownerbaseuserhf 2.導入那個數據庫的用戶就寫那個&#xff0c;第一個是用戶名&#…

H264實時編碼及NALU,RTP傳輸(ZZ)

rfc3984 Standards Track [Page 2] RFC 3984 RTP Payload Format for H.264 Video February 2005 1. 按照RFC3984協議實現H264視頻流媒體nalu單元 包起始 0x 00 00 00 01H&#xff0e;264 NAL格式及分析器http://hi.baidu.com/zsw%5Fdavy/b ... c409cc7cd92ace.htmlhttp://hi.b…

學習具體計劃書

計劃書10大行動&#xff1a;1. 學習的時候不玩手機學習的時候把手機放在抽屜里&#xff0c;靜音2. 及時復習學完一個章節的知識及時復習覺得有做分享的價值就做分享錄視頻3. 不學習的時間要好好利用花時間做好吃的&#xff0c;把身體弄好多看看心理學的書&#xff0c;<接觸青…

初識python

課程介紹: python語言:python語言是一種計算機程序設計語言,實現人機交互的語言 python的課程設計python基礎 (python開發工程師)數據庫和SQL開發 (數據分析工程師)網絡爬蟲 (網絡爬蟲工程師)高數和數據分析 (數據分析工程師)人工智能和機器學習 …

photoshop最全快捷鍵列表

一、工具箱(多種工具共用一個快捷鍵的可同時按【Shift】加此快捷鍵選取) 矩形、橢圓選框工具 【M】 移動工具 【V】 套索、多邊形套索、磁性套索 【L】 魔棒工具 【W】 裁剪工具 【C】 切片工具、切片選擇工具 【K】 噴槍工具 【J】 畫筆工具、鉛筆工具 【B】 像皮圖章、圖案圖…

python實例化對象做實參_如何在Python中記住類實例化?

好的&#xff0c;這是真實的場景&#xff1a;我正在編寫一個應用程序&#xff0c;我有一個類&#xff0c;它表示某種類型的文件&#xff08;在我的例子中&#xff0c;這是照片&#xff0c;但細節與問題無關&#xff09;。照片類的每個實例對于照片的文件名都應該是唯一的。 問題…

bupt summer training for 16 #3 ——構造

https://vjudge.net/contest/172464 后來補題發現這場做的可真他媽傻逼 A.簽到傻逼題&#xff0c;自己分情況 1 #include <cstdio>2 #include <vector>3 #include <algorithm>4 5 using std::vector;6 using std::sort;7 8 typedef long long ll;9 10 int n…

Python02期(北京)課程筆記索引

day01 初始python關于使用notepad運行python程序注釋和語句分類 day02 命名方式和關鍵字數據類型數據類型轉換 day03 變量與數據類型運算和運算符進制轉換 day04 循環結構 day05 函數概述 day06 nonlocal和global 關鍵字詳解 day07 python核心,內建函數高階函數字…

python常用快捷鍵、寫代碼事半功倍_Pycharm常用快捷鍵總結及配置方法

工欲善其事必先利其器&#xff0c;Python開發利器Pycharm常用快捷鍵以及配置如下&#xff0c;相信有了這些快捷鍵&#xff0c;你的開發會事半功倍 一 常用快捷鍵 編輯類&#xff1a; Ctrl D 復制選定的區域或行 Ctrl Y 刪除選定的行 Ctrl Alt L 代碼格式化 Ctrl Alt O 優…

PHP中的魔術常量

魔術常量 PHP 向它運行的任何腳本提供了大量的預定義常量。不過很多常量都是由不同的擴展庫定義的&#xff0c;只有在加載了這些擴展庫時才會出現&#xff0c;或者動態加載后&#xff0c;或者在編譯時已經包括進去了。 有八個魔術常量它們的值隨著它們在代碼中的位置改變而改…

Java中的繼承性特性

繼承性是java中的第二特性之一。而繼承性最為關鍵的地方為&#xff1a;代碼重用性的問題&#xff0c;利用繼承性可以從已有的類中繼續派生出新的子類&#xff0c;也可以利用子類擴展出更多的操作功能。 繼承性的實現代碼為&#xff1a;class 子類 extends 父類{ } 有以下3點說…

10大html5前端框架

Bootstrap 首先說 Bootstrap&#xff0c;估計你也猜到會先說或者一定會有這個( 呵呵了 )&#xff0c;這是說明它的強大之處&#xff0c;擁有框架一壁江山的勢氣。自己剛入道的時候本著代碼任何一個字母都得自己敲出來擋我者廢的決心&#xff0c;來讓自己成長。結果受到周圍各 種…

多媒體技術復習匯總 收藏

多媒體技術復習匯總 收藏 1. 什么是媒體&#xff1a;媒體是信息表示和傳輸的載體。2. 媒體分類&#xff1a;感覺媒體&#xff0c;表示媒體&#xff0c;表現媒體&#xff0c;存儲媒體&#xff0c;傳輸媒體3. 多媒體技術的定義和特點&#xff1a;多媒體技…

PHP中的語法特點小結

PHP中的語法特點小結 1.PHP的變量開頭要加上$符號,見到$就知道這個是一個變量 2.PHP中的常量才是不用加$符號的 3.PHP中$可以用來嵌套使用,從而實現動態的變量名的層級調用 4.PHP程序<?php開頭,結尾可以加上?>,也可以不加 5.PHP中的常量有著魔術常量(系統自帶的) 6.PH…

滾動行為

new router({ scrollBehavior (to, from, savaPosition) { if(savePosition) { //歷史記錄的前進后退記住的之前滾動到的位置 return savePosition } else { return {x: 0, y: 0} } //history模式下 定位到某個元素失效的解決辦法 if(to.hash) { return { selector: to.h…

使用FFMPEG SDK解碼流數據獲得YUV數據及其大小

本文以H264視頻流為例&#xff0c;講解解碼流數據的步驟。 為突出重點&#xff0c;本文只專注于討論解碼視頻流數據&#xff0c;不涉及其它&#xff08;如開發環境的配置等&#xff09;。如果您需要這方面的信息&#xff0c;請和我聯系。 準備變量 定義AVCodecContext。如果您…