<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Netty中粘包/拆包處理

          共 1745字,需瀏覽 4分鐘

           ·

          2021-01-09 13:20

          Python實(shí)戰(zhàn)社群

          Java實(shí)戰(zhàn)社群

          長按識別下方二維碼,按需求添加

          掃碼關(guān)注添加客服

          進(jìn)Python社群▲

          掃碼關(guān)注添加客服

          進(jìn)Java社群


          作者丨ytao

          來源丨ytao

          TCP 是基于流傳輸?shù)膮f(xié)議,請求數(shù)據(jù)在其傳輸?shù)倪^程中是沒有界限區(qū)分,所以我們在讀取請求的時(shí)候,不一定能獲取到一個完整的數(shù)據(jù)包。如果一個包較大時(shí),可能會切分成多個包進(jìn)行多次傳輸。同時(shí),如果存在多個小包時(shí),可能會將其整合成一個大包進(jìn)行傳輸。這就是 TCP 協(xié)議的粘包/拆包概念。

          本文基于 Netty5 進(jìn)行分析

          粘包/拆包描述

          假設(shè)當(dāng)前有 123abc兩個數(shù)據(jù)包,那么他們傳輸情況示意圖如下:

          • I 為正常情況,兩次傳輸兩個獨(dú)立完整的包。

          • II 為粘包情況,?123和?abc封裝成了一個包。

          • III 為拆包情況,圖中的描述是將?123拆分成了?1和?23,并且?1和?abc一起傳輸。?123和?abc也可能是?abc進(jìn)行拆包。甚至?123和?abc進(jìn)行多次拆分也有可能。

          Netty 粘包/拆包問題

          為突出 Netty 的粘包/拆包問題,這里通過例子進(jìn)行重現(xiàn)問題,以下為突出問題的主要代碼:

          服務(wù)端:

          1. /**

          2. * 服務(wù)端網(wǎng)絡(luò)事件的讀寫操作類

          3. *

          4. * Created by YangTao.

          5. */

          6. publicclassServerHandlerextendsChannelHandlerAdapter{

          7. // 接收消息計(jì)數(shù)器

          8. privateint i = 0;


          9. // client端消息

          10. @Override

          11. publicvoid channelRead(ChannelHandlerContext ctx, Object msg) throwsException{

          12. i++;


          13. System.out.print(msg);


          14. // 對每條讀取到的消息進(jìn)行打數(shù)標(biāo)記

          15. System.out.println("================== ["+ i +"]");

          16. // 發(fā)送應(yīng)答消息給客戶端

          17. ByteBuf rmsg = Unpooled.copiedBuffer(String.valueOf(i).getBytes());

          18. ctx.write(rmsg);

          19. }


          20. // 其他操作 .......

          21. }

          客戶端:

          1. /**

          2. * 客戶端發(fā)送數(shù)據(jù)

          3. *

          4. * Created by YangTao.

          5. */

          6. publicclassNettyClient{


          7. publicvoid send() {

          8. Bootstrap bootstrap = newBootstrap();

          9. NioEventLoopGroup group = newNioEventLoopGroup();


          10. try{

          11. bootstrap.group(group)

          12. .channel(NioSocketChannel.class)

          13. .option(ChannelOption.TCP_NODELAY, true)

          14. .handler(newChannelInitializer<NioSocketChannel>() {

          15. @Override

          16. protectedvoid initChannel(NioSocketChannel ch) {

          17. ChannelPipeline pipeline = ch.pipeline();

          18. pipeline.addLast(newStringDecoder());

          19. pipeline.addLast("logger", newLoggingHandler(LogLevel.INFO));

          20. pipeline.addLast(newClientHandler());

          21. }

          22. });

          23. Channel channel = bootstrap.connect(HOST, PORT).channel();

          24. int i = 1;

          25. while(i <= 300){

          26. channel.writeAndFlush(String.format("【時(shí)間 %s: \t%s】", newDate(), i));

          27. // 打印發(fā)送請求的次數(shù)

          28. System.out.println(i);

          29. i++;

          30. }

          31. }catch(Exception e){

          32. e.printStackTrace();

          33. }finally{

          34. if(group != null)

          35. group.shutdownGracefully();

          36. }

          37. }

          38. }

          以上代碼中,我們第一反應(yīng)理解的是,如果非異常情況下客戶端所有數(shù)據(jù)發(fā)送成功,并且服務(wù)端全部接收到。那么從打印信息中可以看到客戶端的發(fā)送次數(shù) i和服務(wù)端的接收消息計(jì)數(shù) i應(yīng)該是相同的數(shù)。那么下面通過運(yùn)行程序,查看打印結(jié)果。

          如上圖所示, 【】中的最后一個數(shù)字與 []中數(shù)字對上的是已獨(dú)立完整的包接收到(粘包/拆包示意圖中的情況 I)。但是 【】中為 3738的出現(xiàn)了粘包情況(粘包/拆包示意圖中的情況 II),兩條數(shù)據(jù)粘合在一起。

          上圖中可以看到 【】167的數(shù)據(jù)被拆分為了兩部分(圖中畫綠線數(shù)據(jù)),該情況為拆包(粘包/拆包示意圖中的情況 III)

          上面程序沒有考慮到 TCP 的粘包/拆包問題,所以如果是我們實(shí)際應(yīng)用的程序的話,不能保證數(shù)據(jù)的正常情況,就會導(dǎo)致程序異常。

          Netty 解決粘包/拆包問題

          LineBasedFrameDecoder 換行符處理

          Netty 的強(qiáng)大,方便,簡單使用的優(yōu)勢,在粘包/拆包問題上也提供了多種編解碼解決方案,并且很容易理解和掌握。這里使用 LineBasedFrameDecoder 和 StringDecoder(將接收到的對象轉(zhuǎn)換成字符串) 來解決粘包/拆包問題。只需在服務(wù)端和客戶端分別添加 LineBasedFrameDecoder 和 StringDecoder解碼器,因?yàn)槭请p向會話,所以兩端都要添加,由于我一開始就添加 StringDecoder 編碼器,所以只需添加 LineBasedFrameDecoder 就夠了。服務(wù)端:

          客戶端:

          服務(wù)端網(wǎng)絡(luò)事件操作:

          1. /**

          2. * 服務(wù)端網(wǎng)絡(luò)事件的讀寫操作類

          3. *

          4. * Created by YangTao.

          5. */

          6. publicclassServerHandlerextendsChannelHandlerAdapter{

          7. // 接收消息計(jì)數(shù)器

          8. privateint i = 0;


          9. // client端消息

          10. @Override

          11. publicvoid channelRead(ChannelHandlerContext ctx, Object msg) throwsException{

          12. i++;


          13. System.out.print(msg);


          14. // 對每條讀取到的消息進(jìn)行打數(shù)標(biāo)記

          15. System.out.println("================== ["+ i +"]");

          16. // 發(fā)送應(yīng)答消息給客戶端

          17. ByteBuf rmsg = Unpooled.copiedBuffer(String.valueOf(i + System.getProperty("line.separator")).getBytes());

          18. ctx.write(rmsg);

          19. }


          20. // 其他操作 .......

          21. }

          客戶端發(fā)送數(shù)據(jù):

          1. /**

          2. * 客戶端發(fā)送數(shù)據(jù)

          3. *

          4. * Created by YangTao.

          5. */

          6. publicclassNettyClient{


          7. publicvoid send() {

          8. // 連接操作 .......


          9. try{

          10. // 獲取 channel

          11. Channel channel = channel();

          12. int i = 1;

          13. ByteBuf buf = null;

          14. while(i <= 300){

          15. String str = String.format("【時(shí)間 %s: \t%s】", newDate(), i) + System.getProperty("line.separator");

          16. byte[] bytes = str.getBytes();

          17. // 寫入緩沖區(qū)

          18. buf = Unpooled.buffer(bytes.length);

          19. buf.writeBytes(bytes);

          20. channel.writeAndFlush(buf);

          21. // 打印發(fā)送請求的次數(shù)

          22. System.out.println(i);

          23. i++;

          24. }

          25. }catch(Exception e){

          26. e.printStackTrace();

          27. }


          28. // 退出操作 .......

          29. }

          30. }

          細(xì)心觀察代碼的變化,應(yīng)該會發(fā)現(xiàn)現(xiàn)在的代碼每次在發(fā)送消息的時(shí)候,在消息末尾后加了換行分隔符。注意,使用 LineBasedFrameDecoder 時(shí),換行分隔符必須加,否則接收消息端收不到消息,如果手寫換行分割,要記得區(qū)分不同系統(tǒng)的適配

          經(jīng)過多次測試 3W 條請求,沒有再出現(xiàn)過粘包/拆包情況,看最后一條數(shù)據(jù)數(shù)字是否相同便知。

          DelimiterBasedFrameDecoder 自定義分隔符

          自定義分隔符和換行分隔符差不多,只需將發(fā)送的數(shù)據(jù)后換行符換成你自己設(shè)定的分割符即可。

          服務(wù)端和客戶端均在 pipeline 添加 DelimiterBasedFrameDecoder:

          1. // 指定的分隔符

          2. publicstaticfinalString DELIMITER = "$@$";


          3. // 如果當(dāng)前數(shù)據(jù)2048個字節(jié)中沒有分隔符,就會拋出異常,避免內(nèi)存溢出。也可以自定義預(yù)檢查當(dāng)前讀取的數(shù)據(jù),自定義這里超過的規(guī)則

          4. pipeline.addLast(newDelimiterBasedFrameDecoder(

          5. 2048,

          6. Unpooled.wrappedBuffer(DELIMITER.getBytes())) // 分割符緩沖對象

          7. );

          FixedLengthFrameDecoder 根據(jù)固定長度

          設(shè)定固定長度,進(jìn)行數(shù)據(jù)傳輸,如果不達(dá)固定長度,使用空格補(bǔ)全。

          服務(wù)端和客戶端均在 pipeline 添加 FixedLengthFrameDecoder:

          1. // 100為指定的固定長度

          2. ch.pipeline().addLast(newFixedLengthFrameDecoder(100));

          每次讀取數(shù)據(jù)時(shí)都會按照 FixedLengthFrameDecoder 中設(shè)置的固定長度進(jìn)行解碼,如果出現(xiàn)粘包,那么會進(jìn)行多次解碼,如果出現(xiàn)拆包的情況,那么 FixedLengthFrameDecoder 會先緩存當(dāng)前部分包的信息,當(dāng)接收下一個包時(shí),會與緩存的部分包進(jìn)行拼接,知道滿足規(guī)定的長度。

          動態(tài)指定長度

          動態(tài)指定長度就是說,每條消息的長度都是隨著消息頭進(jìn)行指定,這里使用的編碼器為 LengthFieldBasedFrameDecoder。

          1. pipeline().addLast(

          2. newLengthFieldBasedFrameDecoder(

          3. 2048, // 幀的最大長度,即每個數(shù)據(jù)包最大限度

          4. 0, // 長度字段偏移量

          5. 4, // 長度字段所占的字節(jié)數(shù)

          6. 0, // 消息頭的長度,可以為負(fù)數(shù)

          7. 4) // 需要忽略的字節(jié)數(shù),從消息頭開始,這里是指整個包

          8. );

          發(fā)送消息時(shí),創(chuàng)建自己的消息對象編碼器

          1. // 創(chuàng)建 byteBuf

          2. ByteBuf buf = getBuf();


          3. // .....


          4. // 設(shè)置該條消息內(nèi)容長度

          5. buf.writeInt(msg.length());

          6. // 設(shè)置消息內(nèi)容

          7. buf.writeBytes(msg.getBytes("UTF-8"));

          服務(wù)端讀取的時(shí)候就直接讀取即可,沒其他特殊操作。

          除了以上 Netty 提供的現(xiàn)成方案,還可以通過重寫 MessageToByteEncoder 編碼實(shí)現(xiàn)自定義協(xié)議。

          總結(jié)

          Netty 極大的為使用者提供了多種解決粘包/拆包方案,并且可以很愉快的對多種消息進(jìn)行自動解碼,在使用過程中也極容易掌握和理解,很大程度上提升開發(fā)效率和穩(wěn)定性。

          程序員專欄
          ?掃碼關(guān)注填加客服?
          長按識別下方二維碼進(jìn)群

          近期精彩內(nèi)容推薦:??

          ?肝了一晚上搞出來微信訂閱號鑒黃機(jī)器人

          ?不允許程序員透露薪資!!!憑啥?

          ?程序員帶娃有多“恐怖” ?!

          ?有個大神級女朋友是什么體驗(yàn)





          在看點(diǎn)這里好文分享給更多人↓↓

          瀏覽 32
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  美女性爱毛片 | 中文字幕+乱码+中文ktv | 色天使亚洲 | 神马午夜限制 | 天堂资源AV |