netty十八羅漢之——挖耳羅漢(Decoder)

佛教中除不聽各種淫邪聲音之外,更不可聽別人的秘密。因他論耳根最到家,故取挖耳之形,以示耳根清凈。

來看看netty的核心組件解碼器Decoder

  • ?Decoder的作用
  • 半包,粘包問題
  • 從模板和裝飾器模式看Decoder解碼原理

1.Decoder作用

?最根本的就是將網絡中的二進制數據,解析成為目標對象(Object)。

抽象出來本質就是上圖,其它任何動作都是在完成這個目的。

對吧,學習一定要抓住本質。

先來看看網絡數據再網絡和操作系統底層是怎么傳輸的

a.網絡數據是以二進制的數據包進行傳輸的,在netty中是以bytefuf數據包進行傳遞。這個概念之? ???后? 章節會講到。

b.會涉及tcp/ipc傳輸協議。

?先來看看TCP/IP協議的數據封裝和分用過程,大致如下圖所示:

?

可以看到沒往下傳輸一層,會加上一個每層的首部。這個可以理解為,唐僧西天取經的過程中,通關文牒都要蓋一個章一樣。不加這個人家不認你。用的時候在解析出來報文體。

這里就不得不提一個概念MSS(TCP[數據包]每次能夠傳輸的最大數據分段)

后面在傳輸過程中,超過這個值,數據包會進行拆分;小于這個值,數據包會進行合并。

c.dma復制

簡單講 就是繞過cpu,直接操作內存在設備間傳輸數據。

具體流程如下:

dam傳輸數據時,cpu不參與,dma處理完之后通知cpu進行后續處理。

理解這幾個點之后,我們再來看一看正真的數據傳播流程:

1.首先發送端,在用戶緩存區里的bytebuf數據包會進行一次cpu復制;

2.cpu復制之后數據會緩存在發送緩沖區的內核空間里,這時候數據是完整的;

3.內核緩沖區進行一次DMA復制,數據被寫入網卡設備中,此時網卡里面的數據包會進行重組;

4.寫入網卡的數據通過TCP/IP協議進行傳輸,組裝成新的二進制數據包,有最大數據限制;

5.接收端通過TCP/IP協議接收到二進制數據包,此時數據是完整的;

6.接收端在內核緩沖區進行一次DMA復制,形成新的bytebuf數據包;

7.接收端進行一次cpu復制,bytebuf數據包被寫入用戶緩沖區。

具體流程如下圖:

這個過程會涉及兩次分割二進制包:

a.傳輸過程中的分割;

b.系統復制過程中的分割;

所以粘包,和半包問題的出現就發生在這兩個過程中,數據包少了,多個合在一起就會造成粘包。數據包多了,就會進行分割,分割就會打破原來的數據結構,出現半包問題。

來看看netty Decoder是怎么解決這個問題的

1.通過ByteToMessageDecoder 將二進制數據轉換成對象

2.通過MessageToMessageDecoder將對象數據轉換成對象

先來看看ByteToMessageDecoder 它是一個基類,主要使用模板方法接受管理bytebuf,子類通過實現鉤子方法,處理業務邏輯,處理完業務邏輯將寫入List<Object>中,之后在流水線上發送到下一站。流水線概念會在之后一章單獨講解。

public class Byte2IntegerDecoder extends ByteToMessageDecoder {//鉤子實現@Overridepublic void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {while (in.readableBytes() >= 4) {Integer anInt = in.readInt();Logger.info("解碼出一個整數: " + anInt);out.add(anInt);}}
}

來看看它的具體使用,繼承ByteToMessageDecoder,鉤子方法里實現業務邏輯。

回顧一下什么是模板方法:

核心點:抽象類會實現一系列公共方法共子類使用。

1.抽象類會定義一系列的模板方法,子類自動繼承。

2.子類通過鉤子方法處理具體業務邏輯。

好,我們再看一個它的核心子類ReplayingDecoder(回放解碼器)。

什么是回放呢?

我們讀取數據的時候是在操作一個二進制數組,數組元素完整的話,指針移動沒有問題。但是數據元素不完整,再次讀取的時候記數指針會回到開頭重新執行。

netty通過checkpoint指針來完成指針回放

再來看看ReplayingDecoder怎么結合模板模式和裝飾器模式完成整個解碼操作的:

使用模板模式主要是它繼承ByteToMessageDecoder父類解碼器,實現decode方法。重點看看包裝器的使用:

回放解碼器在解碼的時候會對傳進來的bytebuf進行一次包裝:

就是這個?replayable.setCumulation(in);

netty 實現了一個?ReplayingDecoderByteBuf,它繼承了ByteBuf,所以可以對對傳進來的bytebuf進行操作,這個過程就是包裝器在起作用。

protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {replayable.setCumulation(in);try {while (in.isReadable()) {int oldReaderIndex = checkpoint = in.readerIndex();int outSize = out.size();if (outSize > 0) {fireChannelRead(ctx, out, outSize);out.clear();

簡化一下看個簡單的包裝器的例子

public class WrapperModelDemo {// Pojo -- 被包裝的類型static class Pojo implements Sth {public void a() {System.out.println("a");}public void b() {System.out.println("b");}public void c() {System.out.println("c");}public void X() {System.out.println("ALL - X");}}// Wrapper模式static class PojoWrapper implements Sth {private Sth inner;protected PojoWrapper() {}public void setInner(Sth inner) {this.inner = inner;}@Overridepublic void a() {System.out.println("PojoWrapper - a");inner.a();}@Overridepublic void b() {System.out.println("PojoWrapper - a");inner.b();}@Overridepublic void c() {throw new UnsupportedOperationException("... c  ");}@Overridepublic void X() {throw new UnsupportedOperationException("... X");}}@Testpublic void testWrapper() throws Exception {Sth pojo = new Pojo();pojo.a();pojo.b();pojo.c();PojoWrapper pojoWrapper = new PojoWrapper();pojoWrapper.setInner(pojo);// 可以嘗試注釋掉上面的某一行代碼, 查看輸出結果pojoWrapper.a();pojoWrapper.b();pojoWrapper.c();}}

運行結果:

核心是包裝類在實現sth接口時,又把它作為了一個屬性設置進來了。

這樣結合著看,對回放解碼器的解碼過程理解起來就會更加清楚。

現在來看看?MessageToMessageDecoder解碼器,它的傳入對象不再是一個bytebuf,而是一個具體的對象

public class Integer2StringDecoder extends MessageToMessageDecoder<Integer> {@Overridepublic void decode(ChannelHandlerContext ctx, Integer msg, List<Object> out) throws Exception {String target = String.valueOf(msg);out.add(target);}
}

它有幾個重要的子類:

LineBasedFrameDecoder:基于行的解碼器,通過換行符(\n 或者 \r\n)來作為幀的分隔符。當接收到數據時,它會在數據中查找換行符,一旦找到,就將從上次解碼位置到換行符之間的數據作為一個完整的幀進行解碼并返回。如果在接收到的數據中沒有找到換行符,那么數據會被緩存起來,直到換行符出現或者緩沖區滿了才進行處理。
DelimiterBasedFrameDecoder:基于分隔符的解碼器,它允許用戶自定義幀的分隔符。在接收到數據后,它會根據用戶提供的分隔符(一個或多個字節數組)在數據中進行查找。一旦找到分隔符,就將從上次解碼位置到分隔符之前的數據作為一個完整的幀返回。同樣,如果沒有找到分隔符,數據會被緩存,直到分隔符出現或緩沖區達到一定條件。
LengthFieldBasedFrameDecoder:通過消息中定義的長度字段來確定幀的長度。解碼器會首先讀取指定長度的字節,這些字節表示后續幀數據的長度。然后,根據這個長度值,從數據流中讀取相應長度的數據作為一個完整的幀。這種方式可以精確地定位每個幀的邊界,即使數據存在粘包和拆包的情況。

?下面是幾個具體實例,可以跑跑看看效果

public class NettyOpenBoxDecoder {public static final int MAGICCODE = 9999;public static final int VERSION = 100;static final String SPLITER = "\r\n";static final String SPLITER_3 = "\n";static final String SPLITER_2 = "\t";static final String CONTENT = "netty:18羅漢系列!";/*** LineBasedFrameDecoder 使用實例*/@Testpublic void testLineBasedFrameDecoder() {try {ChannelInitializer i = new ChannelInitializer<EmbeddedChannel>() {protected void initChannel(EmbeddedChannel ch) {ch.pipeline().addLast(new LineBasedFrameDecoder(1024));ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(new StringProcessHandler());}};EmbeddedChannel channel = new EmbeddedChannel(i);for (int j = 0; j < 100; j++) {//1-3之間的隨機數int random = RandomUtil.randInMod(3);ByteBuf buf = Unpooled.buffer();for (int k = 0; k < random; k++) {buf.writeBytes(CONTENT.getBytes("UTF-8"));}buf.writeBytes(SPLITER.getBytes("UTF-8"));channel.writeInbound(buf);}Thread.sleep(Integer.MAX_VALUE);} catch (InterruptedException e) {e.printStackTrace();} catch (UnsupportedEncodingException e) {e.printStackTrace();}}/*** LineBasedFrameDecoder 使用實例*/@Testpublic void testDelimiterBasedFrameDecoder() {try {final ByteBuf delimiter = Unpooled.copiedBuffer(SPLITER_2.getBytes("UTF-8"));ChannelInitializer i = new ChannelInitializer<EmbeddedChannel>() {protected void initChannel(EmbeddedChannel ch) {ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, true, delimiter));ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(new StringProcessHandler());}};EmbeddedChannel channel = new EmbeddedChannel(i);for (int j = 0; j < 100; j++) {//1-3之間的隨機數int random = RandomUtil.randInMod(3);ByteBuf buf = Unpooled.buffer();for (int k = 0; k < random; k++) {buf.writeBytes(CONTENT.getBytes("UTF-8"));}buf.writeBytes(SPLITER_2.getBytes("UTF-8"));channel.writeInbound(buf);}Thread.sleep(Integer.MAX_VALUE);} catch (InterruptedException e) {e.printStackTrace();} catch (UnsupportedEncodingException e) {e.printStackTrace();}}/*** LineBasedFrameDecoder 使用實例*/@Testpublic void testLengthFieldBasedFrameDecoder() {try {// 1、單字節(無符號):0到255;(有符號):-128到127。
//
//2、雙字節(無符號):0到65535;(有符號):-32768 到 32765。
//
//3、四字節(無符號):0到42 9496 7295;(有符號):-21 4748 3648到21 4748 3647。//定義一個 基于長度域解碼器final LengthFieldBasedFrameDecoder decoder =new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4);ChannelInitializer i = new ChannelInitializer<EmbeddedChannel>() {protected void initChannel(EmbeddedChannel ch) {ch.pipeline().addLast(decoder);ch.pipeline().addLast(new StringDecoder(Charset.forName("UTF-8")));ch.pipeline().addLast(new StringProcessHandler());}};EmbeddedChannel channel = new EmbeddedChannel(i);for (int j = 0; j < 100; j++) {//1-3之間的隨機數int random = RandomUtil.randInMod(3);ByteBuf buf = Unpooled.buffer();byte[] bytes = CONTENT.getBytes("UTF-8");//首先 寫入頭部  head,也就是后面的數據長度buf.writeInt(bytes.length * random);//然后 寫入contentfor (int k = 0; k < random; k++) {buf.writeBytes(bytes);}channel.writeInbound(buf);}Thread.sleep(Integer.MAX_VALUE);} catch (InterruptedException e) {e.printStackTrace();} catch (UnsupportedEncodingException e) {e.printStackTrace();}}/*** LengthFieldBasedFrameDecoder 使用實例*/@Testpublic void testLengthFieldBasedFrameDecoder1() {try {final LengthFieldBasedFrameDecoder spliter =new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4);ChannelInitializer i = new ChannelInitializer<EmbeddedChannel>() {protected void initChannel(EmbeddedChannel ch) {ch.pipeline().addLast(spliter);ch.pipeline().addLast(new StringDecoder(Charset.forName("UTF-8")));ch.pipeline().addLast(new StringProcessHandler());}};EmbeddedChannel channel = new EmbeddedChannel(i);for (int j = 1; j <= 100; j++) {ByteBuf buf = Unpooled.buffer();String s = j + "次發送->" + CONTENT;byte[] bytes = s.getBytes("UTF-8");buf.writeInt(bytes.length);System.out.println("bytes length = " + bytes.length);buf.writeBytes(bytes);channel.writeInbound(buf);}Thread.sleep(Integer.MAX_VALUE);} catch (InterruptedException e) {e.printStackTrace();} catch (UnsupportedEncodingException e) {e.printStackTrace();}}/*** LengthFieldBasedFrameDecoder 使用實例*/@Testpublic void testLengthFieldBasedFrameDecoder2() {try {final LengthFieldBasedFrameDecoder spliter =new LengthFieldBasedFrameDecoder(1024, 0, 4, 2, 6);ChannelInitializer i = new ChannelInitializer<EmbeddedChannel>() {protected void initChannel(EmbeddedChannel ch) {ch.pipeline().addLast(spliter);ch.pipeline().addLast(new StringDecoder(Charset.forName("UTF-8")));ch.pipeline().addLast(new StringProcessHandler());}};EmbeddedChannel channel = new EmbeddedChannel(i);for (int j = 1; j <= 100; j++) {// 分配一個bytebufByteBuf buf = Unpooled.buffer();String s = j + "次發送->" + CONTENT;byte[] bytes = s.getBytes("UTF-8");//首先 寫入頭部  head,包括 后面的數據長度buf.writeInt(bytes.length);buf.writeChar(VERSION);//然后 寫入  contentbuf.writeBytes(bytes);channel.writeInbound(buf);}Thread.sleep(Integer.MAX_VALUE);} catch (InterruptedException e) {e.printStackTrace();} catch (UnsupportedEncodingException e) {e.printStackTrace();}}/*** LengthFieldBasedFrameDecoder 使用實例 3*/@Testpublic void testLengthFieldBasedFrameDecoder3() {try {final LengthFieldBasedFrameDecoder spliter =new LengthFieldBasedFrameDecoder(1024, 2, 4, 4, 10);ChannelInitializer i = new ChannelInitializer<EmbeddedChannel>() {protected void initChannel(EmbeddedChannel ch) {ch.pipeline().addLast(spliter);ch.pipeline().addLast(new StringDecoder(Charset.forName("UTF-8")));ch.pipeline().addLast(new StringProcessHandler());}};EmbeddedChannel channel = new EmbeddedChannel(i);for (int j = 1; j <= 100; j++) {ByteBuf buf = Unpooled.buffer();String s = j + "次發送->" + CONTENT;byte[] bytes = s.getBytes("UTF-8");buf.writeChar(VERSION);buf.writeInt(bytes.length);buf.writeInt(MAGICCODE);buf.writeBytes(bytes);channel.writeInbound(buf);}Thread.sleep(Integer.MAX_VALUE);} catch (InterruptedException e) {e.printStackTrace();} catch (UnsupportedEncodingException e) {e.printStackTrace();}}}

最后總結一下:

本文主要是對netty解碼器的理解,核心點在 數據在網絡和操作系統間的傳播和netty結合模板模式裝飾器模式解決netty解碼過程中的數據問題。

預告一下,下一篇是18羅漢的第二章,netty編碼器。

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

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

相關文章

51單片機學習之旅——定時器

打開軟件 1與其它等于其它&#xff0c;0與其它等于0 1或其它等于1&#xff0c;0或其它等于其它 TMODTMOD&0xF0;//0xF01111 0000進行與操作&#xff0c;高四位保持&#xff0c;低四位清零&#xff0c;高四位定時器1&#xff0c;低四位定時器0 TMODTMOD|0x01;//0x010000 0…

內容中臺重構智能服務:人工智能技術驅動精準決策

內容概要 現代企業數字化轉型進程中&#xff0c;內容中臺與人工智能技術的深度融合正在重構智能服務的基礎架構。通過整合自然語言處理、知識圖譜構建與深度學習算法三大技術模塊&#xff0c;該架構實現了從數據采集到決策輸出的全鏈路智能化。在數據層&#xff0c;系統可對接…

【redis】redis內存管理,過期策略與淘汰策略

一&#xff1a;Redis 的過期刪除策略及處理流程如下&#xff1a; 1. 過期刪除策略 Redis 通過以下兩種策略刪除過期鍵&#xff1a; 1.1 惰性刪除 觸發時機&#xff1a;當客戶端訪問某個鍵時&#xff0c;Redis 會檢查該鍵是否過期。執行流程&#xff1a; 客戶端請求訪問鍵。…

tp6上傳文件大小超過了最大值+驗證文件上傳大小和格式函數

問題&#xff1a; 最近用tp6的文件上傳方法上傳文件時報文件過大錯誤。如下所示&#xff1a; $file $this->request->file(file);{"code": 1,"msg": "上傳文件大小超過了最大值&#xff01;","data": {"code": 1,&q…

Kreuzberg:本地OCR+多格式解析!Kreuzberg如何用Python暴力提取30+文檔格式?程序員看完直呼內行!

嗨&#xff0c;大家好&#xff0c;我是小華同學&#xff0c;關注我們獲得“最新、最全、最優質”開源項目和高效工作學習方法 我們經常需要從各種不同類型的文檔中提取文本內容&#xff0c;無論是辦公文檔、圖像還是PDF文件。而Kreuzberg這個Python庫的出現&#xff0c;為我們提…

Windows程序設計29:對話框之間的數據傳遞

文章目錄 前言一、父子對話框之間的數據傳遞1.父窗口獲取子窗口數據2.子窗口獲取父窗口數據 二、類外函數調用窗口的操作1.全局變量方式2.參數傳遞方式 總結 前言 Windows程序設計29&#xff1a;對話框之間的數據傳遞。 在Windows程序設計28&#xff1a;MFC模態與非模態對話框…

【C語言】第八期——指針

目錄 1 初始指針 2 獲取變量的地址 3 定義指針變量、取地址、取值 3.1 定義指針變量 3.2 取地址、取值 4 對指針變量進行讀寫操作 5 指針變量作為函數參數 6 數組與指針 6.1 指針元素指向數組 6.2 指針加減運算&#xff08;了解&#xff09; 6.2.1 指針加減具體數字…

為 Power Automate 注冊 Adobe PDF Services

前言 最近&#xff0c;再測試如何將HTML轉換成PDF&#xff0c;然后發現Adobe有一個免費的操作可以用&#xff0c;好開心&#xff0c;趕緊注冊一下。 正文 1.先注冊一個賬號&#xff0c;然后登錄到Adobe Developer 注冊鏈接&#xff1a;https://www.adobe.com/go/getstarted_pow…

BY組態:工業自動化的未來,觸手可及

1. BY組態軟件的核心優勢 簡單易用&#xff1a;圖形化界面&#xff0c;降低學習成本&#xff0c;快速上手。 高效靈活&#xff1a;支持多種設備協議&#xff0c;兼容性強&#xff0c;適用于多種行業。 實時監控&#xff1a;提供實時數據采集與可視化&#xff0c;助力高效決策…

有哪些開源大數據處理項目使用了大模型

以下是一些使用了大模型的開源大數據處理項目&#xff1a; 1. **RedPajama**&#xff1a;這是一個開源項目&#xff0c;使用了LLM大語言模型數據處理組件&#xff0c;對GitHub代碼數據進行清洗和處理。具體流程包括數據清洗、過濾低質量樣本、識別和刪除重復樣本等步驟。 2. …

網絡安全之攻防筆記--通用安全漏洞SQL注入sqlmapOraclemongodbDB2

通用安全漏洞SQL注入&sqlmap&Oracle&mongodb&DB2 數據庫類型 ACCESS 特性 沒數據庫用戶 沒數據庫權限 沒數據庫查詢參數 沒有高權限注入說法 暴力猜解&#xff0c;借助字典得到數據 注入方式 聯合注入 偏移注入 表名列名猜解不到 偏移注入 MySQL 低權限 常…

【信息系統項目管理師-案例真題】2022下半年案例分析答案和詳解

更多內容請見: 備考信息系統項目管理師-專欄介紹和目錄 文章目錄 試題一(24分)【問題1】(6分)【問題2】(10分)【問題3】(8分)試題二(26分)【問題1】(8分)【問題2】(8分)【問題3】(4分)【問題4】(6分)試題三(25分)【問題1】(12分)【問題2】(7分)【問題…

正點原子[第三期]Arm(iMX6U)Linux系統移植和根文件系統構建-5.3 xxx_defconfig過程

前言&#xff1a; 本文是根據嗶哩嗶哩網站上“arm(iMX6U)Linux系統移植和根文件系統構鍵篇”視頻的學習筆記&#xff0c;在這里會記錄下正點原子 I.MX6ULL 開發板的配套視頻教程所作的實驗和學習筆記內容。本文大量引用了正點原子教學視頻和鏈接中的內容。 引用&#xff1a; …

C++初階——簡單實現list

目錄 1、前言 2、List.h 3、Test.cpp 1、前言 1. 簡單實現std::list&#xff0c;重點&#xff1a;迭代器&#xff0c;模板類&#xff0c;運算符重載。 2. 并不是&#xff0c;所有的類&#xff0c;都需要深拷貝&#xff0c;像迭代器類模板&#xff0c;只是用別的類的資源&am…

conda環境中運行“python --version“所得的版本與環境中的python版本不一致----deepseek并非全能

conda環境中運行python —version所得python版本與conda環境中的python版本不一致------deepseek并非全能 問題 conda環境中運行python —version所得python版本與conda環境中的python版本不一致 我所做的探索 1 網頁搜索 2 求助于DeepSeek 可以用四個字來形容deepseek給出…

HarmonyOS學習第5天: Hello World的誕生之旅

鴻蒙初印象&#xff1a;開啟探索之門 在操作系統的廣袤天地中&#xff0c;HarmonyOS&#xff08;鴻蒙系統&#xff09;宛如一顆冉冉升起的新星&#xff0c;自誕生起便備受矚目。它由華為傾力打造&#xff0c;是一款基于微內核的全場景分布式操作系統&#xff0c;以其獨特的技術…

centos9安裝k8s集群

以下是基于CentOS Stream 9的Kubernetes 1.28.2完整安裝流程&#xff08;containerd版&#xff09;&#xff1a; 一、系統初始化&#xff08;所有節點執行&#xff09; # 關閉防火墻 systemctl disable --now firewalld# 關閉SELinux sed -i "s/SELINUXenforcing/SELINU…

CIG容器重量級監控系統

1.介紹 CAdvisorinfluxDBGranfana docker 原生命令 監控docker容器狀態 docker stats 2.CAdvicsor 3.InfluxDB 4.Granafana 5.搭建 volumes:grafana_data: services:influxdb:image: tutum/influxdbrestart: alwaysenvironment:- PRE_CREATE_DBcadvisorports:- "8083…

REACT學習DAY02(恨連接不上服務器)

受控表單綁定 概念&#xff1a;使用React組件的狀態&#xff08;useState&#xff09;控制表單的狀態 1. 準備一個React狀態值 const [value,setValue] useState() 2. 通過value屬性綁定狀態&#xff0c;通過onChange屬性綁定狀態同步的函數 <input type"text&quo…

python——GUI圖形用戶界面編程

GUI簡介 我們前面實現的都是基于控制臺的程序&#xff0c;程序和用戶的交互通過控制臺來完成 本章&#xff0c;我們來學習GUI圖形用戶界面編程&#xff0c;我們可以通過python提供的豐富的組件&#xff0c;快速的視線使用圖形界面和用戶交互 GUI變成類似于“搭積木”&#x…