中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Netty中粘包和拆包如何解決

發布時間:2021-07-29 17:36:47 來源:億速云 閱讀:147 作者:Leah 欄目:大數據

這篇文章將為大家詳細講解有關Netty中粘包和拆包如何解決,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。

TCP粘包和拆包

TCP是個“流”協議,所謂流,就是沒有界限的一串數據。TCP底層并不了解上層業務數據的具體含義,它會根據TCP緩沖區的實際情況進行包的劃分,所以在業務上認為,一個完整的包可能會被TCP拆分成多個包進行發送,也有可能把多個小的包封裝成一個大的數據包發送,這就是所謂的TCP粘包和拆包問題。

Netty中粘包和拆包如何解決

如圖所示,假設客戶端分別發送了兩個數據包D1和D2給服務端,由于服務端一次讀取到的字節數是不確定的,故可能存在以下4種情況。

  1. 服務端分兩次讀取到了兩個獨立的數據包,分別是D1和D2,沒有粘包和拆包;

  2. 服務端一次接收到了兩個數據包,D1和D2粘合在一起,被稱為TCP粘包;

  3. 服務端分兩次讀取到了兩個數據包,第一次讀取到了完整的D1包和D2包的部分內容,第二次讀取到了D2包的剩余內容,這被稱為TCP拆包     

  4. 服務端分兩次讀取到了兩個數據包,第一次讀取到了D1包的部分內容D1_1,第二次讀取到了D1包的剩余內容D1_2和D2包的整包。

                  

如果此時服務端TCP接收滑窗非常小,而數據包D1和D2比較大,很有可能會發生第五種可能,即服務端分多次才能將D1和D2包接收完全,期間發生多次拆包。

TCP粘包和拆包產生的原因

數據從發送方到接收方需要經過操作系統的緩沖區,而造成粘包和拆包的主要原因就在這個緩沖區上。粘包可以理解為緩沖區數據堆積,導致多個請求數據粘在一起,而拆包可以理解為發送的數據大于緩沖區,進行拆分處理。

Netty中粘包和拆包如何解決

詳細來說,造成粘包和拆包的原因主要有以下三個:

  1. 應用程序write寫入的字節大小大于套接口發送緩沖區大小

  2. 進行MSS大小的TCP分段

  3. 以太網幀的payload大于MTU進行IP分片。                             


Netty中粘包和拆包如何解決

粘包和拆包的解決方法

由于底層的TCP無法理解上層的業務數據,所以在底層是無法保證數據包不被拆分和重組的,這個問題只能通過上層的應用協議棧設計來解決,根據業界的主流協議的解決方案,可以歸納如下。

  1. 消息長度固定,累計讀取到長度和為定長LEN的報文后,就認為讀取到了一個完整的信息

  2. 將回車換行符作為消息結束符

  3. 將特殊的分隔符作為消息的結束標志,回車換行符就是一種特殊的結束分隔符

  4. 通過在消息頭中定義長度字段來標識消息的總長度

Netty中的粘包和拆包解決方案

針對上一小節描述的粘包和拆包的解決方案,對于拆包問題比較簡單,用戶可以自己定義自己的編碼器進行處理,Netty并沒有提供相應的組件。對于粘包的問題,由于拆包比較復雜,代碼比較處理比較繁瑣,Netty提供了4種解碼器來解決,分別如下:

  1. 固定長度的拆包器 FixedLengthFrameDecoder,每個應用層數據包的都拆分成都是固定長度的大小

  2. 行拆包器 LineBasedFrameDecoder,每個應用層數據包,都以換行符作為分隔符,進行分割拆分

  3. 分隔符拆包器 DelimiterBasedFrameDecoder,每個應用層數據包,都通過自定義的分隔符,進行分割拆分

  4. 基于數據包長度的拆包器 LengthFieldBasedFrameDecoder,將應用層數據包的長度,作為接收端應用層數據包的拆分依據。

    按照應用層數據包的大小,拆包。

    這個拆包器,有一個要求,就是應用層協議中包含數據包的長度

以上解碼器在使用時只需要添加到Netty的責任鏈中即可,大多數情況下這4種解碼器都可以滿足了,當然除了以上4種解碼器,用戶也可以自定義自己的解碼器進行處理。具體可以參考以下代碼示例:

// Server主程序
public class XNettyServer {
 public static void main(String[] args) throws Exception {

   // accept 處理連接的線程池
   NioEventLoopGroup acceptGroup = new NioEventLoopGroup();

   // read io 處理數據的線程池
   NioEventLoopGroup readGroup = new NioEventLoopGroup();

   try {
     ServerBootstrap serverBootstrap = new ServerBootstrap();
     serverBootstrap
         .group(acceptGroup, readGroup)
         .channel(NioServerSocketChannel.class)
         .childHandler(
             new ChannelInitializer<SocketChannel>() {
               @Override
               protected void initChannel(SocketChannel ch) throws Exception {
                 ChannelPipeline pipeline = ch.pipeline();

                 // 增加解碼器
                 pipeline.addLast(new XDecoder());

                 // 打印出內容 handdler
                 pipeline.addLast(new XHandler());
               }
             });
     System.out.println("啟動成功,端口 7777");
     serverBootstrap.bind(7777).sync().channel().closeFuture().sync();
   } finally {
     acceptGroup.shutdownGracefully();
     readGroup.shutdownGracefully();
   }
 }

}

// 解碼器
public class XDecoder extends ByteToMessageDecoder {

 static final int PACKET_SIZE = 220;

 // 用來臨時保留沒有處理過的請求報文
 ByteBuf tempMsg = Unpooled.buffer();

 /**
  * @param ctx
  * @param in 請求的數據
  * @param out 將粘在一起的報文拆分后的結果保留起來
  * @throws Exception
  */
 @Override
 protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
   System.out.println(Thread.currentThread() + "收到了一次數據包,長度是:" + in.readableBytes());

   // 合并報文
   ByteBuf message = null;

   int tmpMsgSize = tempMsg.readableBytes();

   // 如果暫存有上一次余下的請求報文,則合并
   if (tmpMsgSize > 0) {
     message = Unpooled.buffer();
     message.writeBytes(tempMsg);
     message.writeBytes(in);
     System.out.println("合并:上一數據包余下的長度為:" + tmpMsgSize + ",合并后長度為:" + message.readableBytes());
   } else {
     message = in;
   }
   int size = message.readableBytes();
   int counter = size / PACKET_SIZE;
   for (int i = 0; i < counter; i++) {
     byte[] request = new byte[PACKET_SIZE];
     // 每次從總的消息中讀取220個字節的數據
     message.readBytes(request);
     // 將拆分后的結果放入out列表中,交由后面的業務邏輯去處理
     out.add(Unpooled.copiedBuffer(request));

   }

   // 多余的報文存起來
   // 第一個報文:i+  暫存
   // 第二個報文:1 與第一次
   size = message.readableBytes();

   if (size != 0) {
     System.out.println("多余的數據長度:" + size);
     // 剩下來的數據放到tempMsg暫存
     tempMsg.clear();
     tempMsg.writeBytes(message.readBytes(size));
   }
 }

}

// 處理器
public class XHandler extends ChannelInboundHandlerAdapter {

 @Override
 public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
   ctx.flush();
 }
 @Override
 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
   ByteBuf byteBuf = (ByteBuf) msg;
   byte[] content = new byte[byteBuf.readableBytes()];
   byteBuf.readBytes(content);
   System.out.println(Thread.currentThread() + ": 最終打印" + new String(content));
   ((ByteBuf) msg).release();
 }
 @Override
 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
   cause.printStackTrace();
   ctx.close();
 }

}

關于Netty中粘包和拆包如何解決就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

咸丰县| 将乐县| 石阡县| 松潘县| 洛隆县| 玉树县| 达日县| 乌恰县| 三明市| 临潭县| 灵宝市| 孟连| 中方县| 东乡族自治县| 连城县| 府谷县| 高碑店市| 来安县| 南涧| 绩溪县| 商河县| 仙桃市| 惠来县| 北流市| 崇义县| 腾冲县| 太康县| 福安市| 沙河市| 台中县| 洪雅县| 宁陵县| 松桃| 罗田县| 门源| 新晃| 思南县| 高密市| 晋江市| 牡丹江市| 陕西省|