Netty中粘包和拆包如何解決

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

創(chuàng)新互聯(lián)專業(yè)為企業(yè)提供蔡甸網(wǎng)站建設(shè)、蔡甸做網(wǎng)站、蔡甸網(wǎng)站設(shè)計(jì)、蔡甸網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計(jì)與制作、蔡甸企業(yè)網(wǎng)站模板建站服務(wù),10多年蔡甸做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。

TCP粘包和拆包

TCP是個(gè)“流”協(xié)議,所謂流,就是沒有界限的一串?dāng)?shù)據(jù)。TCP底層并不了解上層業(yè)務(wù)數(shù)據(jù)的具體含義,它會根據(jù)TCP緩沖區(qū)的實(shí)際情況進(jìn)行包的劃分,所以在業(yè)務(wù)上認(rèn)為,一個(gè)完整的包可能會被TCP拆分成多個(gè)包進(jìn)行發(fā)送,也有可能把多個(gè)小的包封裝成一個(gè)大的數(shù)據(jù)包發(fā)送,這就是所謂的TCP粘包和拆包問題。

Netty中粘包和拆包如何解決

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

  1. 服務(wù)端分兩次讀取到了兩個(gè)獨(dú)立的數(shù)據(jù)包,分別是D1和D2,沒有粘包和拆包;

  2. 服務(wù)端一次接收到了兩個(gè)數(shù)據(jù)包,D1和D2粘合在一起,被稱為TCP粘包;

  3. 服務(wù)端分兩次讀取到了兩個(gè)數(shù)據(jù)包,第一次讀取到了完整的D1包和D2包的部分內(nèi)容,第二次讀取到了D2包的剩余內(nèi)容,這被稱為TCP拆包     

  4. 服務(wù)端分兩次讀取到了兩個(gè)數(shù)據(jù)包,第一次讀取到了D1包的部分內(nèi)容D1_1,第二次讀取到了D1包的剩余內(nèi)容D1_2和D2包的整包。

                  

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

TCP粘包和拆包產(chǎn)生的原因

數(shù)據(jù)從發(fā)送方到接收方需要經(jīng)過操作系統(tǒng)的緩沖區(qū),而造成粘包和拆包的主要原因就在這個(gè)緩沖區(qū)上。粘包可以理解為緩沖區(qū)數(shù)據(jù)堆積,導(dǎo)致多個(gè)請求數(shù)據(jù)粘在一起,而拆包可以理解為發(fā)送的數(shù)據(jù)大于緩沖區(qū),進(jìn)行拆分處理。

Netty中粘包和拆包如何解決

詳細(xì)來說,造成粘包和拆包的原因主要有以下三個(gè):

  1. 應(yīng)用程序write寫入的字節(jié)大小大于套接口發(fā)送緩沖區(qū)大小

  2. 進(jìn)行MSS大小的TCP分段

  3. 以太網(wǎng)幀的payload大于MTU進(jìn)行IP分片。                             

Netty中粘包和拆包如何解決

粘包和拆包的解決方法

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

  1. 消息長度固定,累計(jì)讀取到長度和為定長LEN的報(bào)文后,就認(rèn)為讀取到了一個(gè)完整的信息

  2. 將回車換行符作為消息結(jié)束符

  3. 將特殊的分隔符作為消息的結(jié)束標(biāo)志,回車換行符就是一種特殊的結(jié)束分隔符

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

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

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

  1. 固定長度的拆包器 FixedLengthFrameDecoder,每個(gè)應(yīng)用層數(shù)據(jù)包的都拆分成都是固定長度的大小

  2. 行拆包器 LineBasedFrameDecoder,每個(gè)應(yīng)用層數(shù)據(jù)包,都以換行符作為分隔符,進(jìn)行分割拆分

  3. 分隔符拆包器 DelimiterBasedFrameDecoder,每個(gè)應(yīng)用層數(shù)據(jù)包,都通過自定義的分隔符,進(jìn)行分割拆分

  4. 基于數(shù)據(jù)包長度的拆包器 LengthFieldBasedFrameDecoder,將應(yīng)用層數(shù)據(jù)包的長度,作為接收端應(yīng)用層數(shù)據(jù)包的拆分依據(jù)。

    按照應(yīng)用層數(shù)據(jù)包的大小,拆包。

    這個(gè)拆包器,有一個(gè)要求,就是應(yīng)用層協(xié)議中包含數(shù)據(jù)包的長度

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

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

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

   // read io 處理數(shù)據(jù)的線程池
   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());

                 // 打印出內(nèi)容 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;

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

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

   // 合并報(bào)文
   ByteBuf message = null;

   int tmpMsgSize = tempMsg.readableBytes();

   // 如果暫存有上一次余下的請求報(bào)文,則合并
   if (tmpMsgSize > 0) {
     message = Unpooled.buffer();
     message.writeBytes(tempMsg);
     message.writeBytes(in);
     System.out.println("合并:上一數(shù)據(jù)包余下的長度為:" + 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個(gè)字節(jié)的數(shù)據(jù)
     message.readBytes(request);
     // 將拆分后的結(jié)果放入out列表中,交由后面的業(yè)務(wù)邏輯去處理
     out.add(Unpooled.copiedBuffer(request));

   }

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

   if (size != 0) {
     System.out.println("多余的數(shù)據(jù)長度:" + size);
     // 剩下來的數(shù)據(jù)放到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();
 }

}

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

名稱欄目:Netty中粘包和拆包如何解決
文章源于:http://muchs.cn/article32/ihecpc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供響應(yīng)式網(wǎng)站、域名注冊、搜索引擎優(yōu)化定制開發(fā)、網(wǎng)站排名服務(wù)器托管

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

小程序開發(fā)