Netty中解碼器的作用及實(shí)現(xiàn)詳解
拆包和沾包
是典型的拆包和沾包問(wèn)題,俗話說(shuō)就是兩端通信,一端發(fā)送一端接收,接收的那一端怎么知道是否已經(jīng)完整的接收了數(shù)據(jù)?
假設(shè)服務(wù)端連續(xù)發(fā)送了兩條消息:hello world! / hello client!
由于客戶(hù)端不知道怎么才算一條消息,怎么才算兩條消息,所以讀取會(huì)有以下幾種情況:
1.分兩次讀取消息,第一次是hello world!,第二次是hello client! 這是正常情況
2.一次就讀取完成,hello world!hello client! 這種情況就叫沾包
3.分兩次讀取消息,第一次是hello ,第二次是world!hello client! 這第一次讀取就是拆包,第二次就是沾包
總之就是讀取到的信息不完整就是拆包,讀取到的信息有額外多的信息就是沾包
演示
我們接著上一章的代碼來(lái)演示,我們只需要讓客戶(hù)端發(fā)送消息的時(shí)候循環(huán)發(fā)送100次,服務(wù)端不變,看看服務(wù)端是不是接收到了100條消息
NettyClientTestHandler
結(jié)果如下:
這明顯就不對(duì)吧
解決方案
要怎么解決這種問(wèn)題呢?
- 消息定長(zhǎng),每條消息都固定長(zhǎng)度,不夠則補(bǔ)空格
- 添加分隔符,等于是為每條消息都增加一個(gè)結(jié)束標(biāo)識(shí)
- 將消息分為消息頭和消息體,消息頭固定長(zhǎng)度,里面包含整條消息的長(zhǎng)度或者消息體的長(zhǎng)度
- 更復(fù)雜的協(xié)議約定
下面我們通過(guò)Netty中幾種內(nèi)置的解碼器來(lái)解決這種問(wèn)題:
- LineBasedFrameDecoder:行分隔符解碼器(結(jié)尾根據(jù) “\n” 作為結(jié)束標(biāo)識(shí))
- DelimiterBasedFrameDecoder:自定義分割器解碼器,結(jié)尾根據(jù)什么作為結(jié)束標(biāo)識(shí)可以自定義
- FixedLengthFrameDecoder:固定長(zhǎng)度解碼器,發(fā)送的消息需要定長(zhǎng)
- LengthFieldBasedFrameDecoder:基于長(zhǎng)度的自定義解碼器,比較靈活
注意:所有編解碼在Netty中都是數(shù)據(jù)處理管道當(dāng)中的一個(gè)數(shù)據(jù)處理器而已
LineBasedFrameDecoder
這個(gè)是采用行分隔符來(lái)解決,所以我們需要改兩個(gè)地方
1.發(fā)送消息的時(shí)候,消息結(jié)尾要加上行分隔符(接著上面的例子來(lái))
2.服務(wù)端接收消息,需要在管道內(nèi)加入解碼器
LineBasedFrameDecoder:傳入的參數(shù)是消息最大長(zhǎng)度,發(fā)送消息的大小必須小于設(shè)置值
結(jié)果如下:
DelimiterBasedFrameDecoder
這個(gè)跟上面一樣,只不過(guò)分隔符我們可以自定義
1.發(fā)送消息的時(shí)候,消息結(jié)尾要加上分隔符(這里我們定義分隔符是 “$$”)
2.服務(wù)端接收消息,需要在管道內(nèi)加入解碼器
結(jié)果如下:
FixedLengthFrameDecoder
會(huì)按照設(shè)置的固定字節(jié)大小來(lái)切割消息
1.這里我們正常的發(fā)送消息就好了
2.服務(wù)端接收消息,需要在管道內(nèi)加入解碼器
結(jié)果如下:
LengthFieldBasedFrameDecoder
這個(gè)是需要重點(diǎn)介紹一下的,上面三個(gè)解碼器明顯的看到不夠靈活,太過(guò)于死板,我們看看這個(gè)怎么用
源代碼構(gòu)造如下
public LengthFieldBasedFrameDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip) { this(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, true); }
參數(shù)含義:
- maxFrameLength:最大幀長(zhǎng)度。也就是可以接收的數(shù)據(jù)的最大長(zhǎng)度。如果超過(guò),此次數(shù)據(jù)會(huì)被丟棄
- lengthFieldOffset:長(zhǎng)度域偏移量。存儲(chǔ)數(shù)據(jù)長(zhǎng)度的一個(gè)偏移量
- lengthFieldLength:長(zhǎng)度域字節(jié)數(shù)。存儲(chǔ)數(shù)據(jù)長(zhǎng)度的一個(gè)大小
- lengthAdjustment:數(shù)據(jù)長(zhǎng)度修正。因?yàn)殚L(zhǎng)度既可以代表data的長(zhǎng)度,也可以是整個(gè)消息的長(zhǎng)度
- initialBytesToStrip:跳過(guò)的字節(jié)數(shù)??梢赃x擇舍棄一部分?jǐn)?shù)據(jù)
這參數(shù)前三個(gè)可以比較好理解,后兩個(gè)是干嘛的?沒(méi)關(guān)系我們一步一步來(lái),后兩個(gè)先不用
假設(shè)我們要發(fā)送一個(gè)消息,結(jié)構(gòu)為:消息頭+數(shù)據(jù)長(zhǎng)度+數(shù)據(jù)(Hello Server)
看看我們要怎么設(shè)置:
整體消息長(zhǎng)度:20個(gè)字節(jié)、數(shù)據(jù)data長(zhǎng)度:12字節(jié)
首先我們要找到長(zhǎng)度域,所以要往右讀取4個(gè)字節(jié): lengthFieldOffset設(shè)置為4
然后需要讀取數(shù)據(jù)的長(zhǎng)度,所以需要再往右讀4個(gè)字節(jié): lengthFieldLength設(shè)置為4
之后會(huì)根據(jù)上面讀取到的數(shù)據(jù)長(zhǎng)度再往后讀取數(shù)據(jù):
假設(shè)這里數(shù)據(jù)長(zhǎng)度是12,則會(huì)繼續(xù)往后讀取12個(gè)字節(jié)
至此數(shù)據(jù)就全讀取完了(lengthAdjustment和initialBytesToStrip都設(shè)置成0的情況下)
下面我們演示一下:
NettyClientTestHandler
客戶(hù)端發(fā)送消息,結(jié)構(gòu)為:消息頭+數(shù)據(jù)長(zhǎng)度+數(shù)據(jù)
@Override public void channelActive(ChannelHandlerContext ctx) throws Exception { String data= "Hello Server"; ByteBuf buffer = Unpooled.buffer(); // 請(qǐng)求頭 4字節(jié) buffer.writeInt(666); // 數(shù)據(jù)長(zhǎng)度 4字節(jié) buffer.writeInt(data.getBytes().length); // 然后寫(xiě)入數(shù)據(jù) buffer.writeBytes(Unpooled.copiedBuffer(data, CharsetUtil.UTF_8)); // 寫(xiě)入并發(fā)送 完整的數(shù)據(jù)為 666+12+Hello Server ctx.writeAndFlush(buffer); SocketAddress socketAddress = ctx.channel().remoteAddress(); log.info(socketAddress + " 已連接"); }
NettyServerTestHandler
服務(wù)端按照結(jié)構(gòu)解析消息
@Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 和NIO一樣有緩沖區(qū) ByteBuf就是對(duì)ByteBuffer做了一層封裝 ByteBuf msg1 = (ByteBuf) msg; // 讀取請(qǐng)求頭 log.info("請(qǐng)求頭:" + msg1.readInt()); // 讀取長(zhǎng)度 int i = msg1.readInt(); log.info("數(shù)據(jù)長(zhǎng)度:" + i); // 讀取數(shù)據(jù) log.info("客戶(hù)端信息:" + msg1.readBytes(i).toString(CharsetUtil.UTF_8)); }
服務(wù)端編碼設(shè)置
結(jié)果如下:
initialBytesToStrip參數(shù)的作用
像上面這樣沾包拆包的問(wèn)題解決了,但是有一個(gè)點(diǎn)很麻煩,就是每次讀取消息都需要一步一步的拆解消息,能不能把消息體前面無(wú)用的數(shù)據(jù)直接舍棄掉,只保留有用的數(shù)據(jù)部分呢?
可以!initialBytesToStrip參數(shù)就是可以在數(shù)據(jù)讀取完后,可以選擇跳過(guò)多少字節(jié)(你可以理解為舍棄,這樣簡(jiǎn)單點(diǎn)),就像上面這個(gè)例子,我不需要請(qǐng)求頭和數(shù)據(jù)長(zhǎng)度的8個(gè)字節(jié),我只需要后面的數(shù)據(jù)體,所以我可以將initialBytesToStrip設(shè)置成8
改動(dòng)兩個(gè)地方
服務(wù)端就不需要再拆解了,直接讀取
lengthAdjustment參數(shù)的作用
上面我們知道了最后會(huì)根據(jù)長(zhǎng)度域里面的數(shù)據(jù)來(lái)決定再往后讀取多少個(gè)字節(jié),這里我們?cè)O(shè)置的數(shù)據(jù)長(zhǎng)度是12,所以剛剛好往后讀取了12個(gè)字節(jié),讀取完成了,要是我設(shè)置的數(shù)據(jù)長(zhǎng)度不是12呢?那往后讀取多少個(gè)字節(jié),這個(gè)是不是需要修正?
所以lengthAdjustment的作用就是來(lái)修正最終往后讀取多少個(gè)字節(jié)
假設(shè)我設(shè)置的數(shù)據(jù)長(zhǎng)度是20,代表了整個(gè)消息體的長(zhǎng)度,但是我數(shù)據(jù)卻只有12個(gè)字節(jié),這往后讀20個(gè)字節(jié)無(wú)疑是錯(cuò)的,所以我們需要修正,怎么修正? 減8唄,對(duì)吧
所以最終往后讀取多少個(gè)字節(jié)=數(shù)據(jù)長(zhǎng)度+lengthAdjustment像上面為例,我們就需要設(shè)置成-8
總結(jié)
這章主要介紹了解碼器的作用,沾包拆包的問(wèn)題,但是還是存在一些問(wèn)題:
每次發(fā)送消息都要寫(xiě)特定格式,是不是太麻煩了?(自定義編碼器)現(xiàn)在傳輸都是簡(jiǎn)單的字符串,實(shí)際都是實(shí)體類(lèi)對(duì)象,這咋搞?(序列化和反序列化)
之后再介紹怎么自定義協(xié)議解決這些問(wèn)題
到此這篇關(guān)于Netty中解碼器的作用及實(shí)現(xiàn)詳解的文章就介紹到這了,更多相關(guān)Netty解碼器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
idea 創(chuàng)建properties配置文件的步驟
這篇文章主要介紹了idea 創(chuàng)建properties配置文件的步驟,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-01-01spring boot整合netty的實(shí)現(xiàn)方法
這篇文章主要介紹了spring boot整合netty的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08通過(guò)實(shí)例解析spring bean之間的關(guān)系
這篇文章主要介紹了通過(guò)實(shí)例解析spring bean之間的關(guān)系,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01SpringBoot如何使用@Cacheable進(jìn)行緩存與取值
這篇文章主要介紹了SpringBoot如何使用@Cacheable進(jìn)行緩存與取值,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08windows下jar包開(kāi)機(jī)自動(dòng)重啟的步驟
這篇文章主要給大家介紹了關(guān)于windows下jar包開(kāi)機(jī)自動(dòng)重啟的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11