netty處理拆包

https://blog.csdn.net/u010853261/article/details/55803933java

[netty]--最通用TCP黏包解決方案:LengthFieldBasedFrameDecoder和LengthFieldPrepender服務器

 

前面已經說過: 
TCP以流的方式進行數據傳輸,上層應用協議爲了對消息進行區分,每每採用以下4種方式。 
(1)消息長度固定:累計讀取到固定長度爲LENGTH以後就認爲讀取到了一個完整的消息。而後將計數器復位,從新開始讀下一個數據報文。app

(2)回車換行符做爲消息結束符:在文本協議中應用比較普遍。socket

(3)將特殊的分隔符做爲消息的結束標誌,回車換行符就是一種特殊的結束分隔符。ide

(4)經過在消息頭中定義長度字段來標示消息的總長度。大數據

netty中針對這四種場景均有對應的解碼器做爲解決方案,好比:ui

(1)經過FixedLengthFrameDecoder 定長解碼器來解決定長消息的黏包問題;編碼

(2)經過LineBasedFrameDecoder和StringDecoder來解決以回車換行符做爲消息結束符的TCP黏包的問題;spa

(3)經過DelimiterBasedFrameDecoder 特殊分隔符解碼器來解決以特殊符號做爲消息結束符的TCP黏包問題;.net

(4)最後一種,也是本文的重點,經過LengthFieldBasedFrameDecoder 自定義長度解碼器解決TCP黏包問題。

大多數的協議在協議頭中都會攜帶長度字段,用於標識消息體或則整包消息的長度。LengthFieldBasedFrameDecoder經過指定長度來標識整包消息,這樣就能夠自動的處理黏包和半包消息,只要傳入正確的參數,就能夠輕鬆解決「讀半包」的問題。

 

 

https://blog.csdn.net/bestone0213/article/details/47108419

netty處理粘包問題——1

 

咱們知道經過TCP協議發送接收數據時,若是數據過大,接收到的數據會是分包的,好比:
                                    +-----+-----+-----+
         發送數據是: | ABC | DEF | GHI |
                            +-----+-----+-----+
         而咱們想接受到的數據是: | ABCDEFGHI |
                    
該如何處理這種狀況呢?Netty提供了一個專門處理TCP協議數據的Handler:LengthFieldBasedFrameDecoder ,它的原理是服務器端和客戶端約定一個協議格式:數據包=協議長度+協議體
 
      --------------------------------數據包------------------------------
 
     | 協議長度部分(接收數據長度) | 協議體部分(要接收的數據)|
 
舉個例子,假如咱們的TCP客戶端發送了10MB字節的數據,如何讓Netty服務器一次就接收到這10MB數據呢?那就須要客戶端告訴服務端我發送的數據大小是多少,即在發送的數據中加入一個「數據包長度」便可,上面提到的Handler就是用來和客戶端約定這個協議格式的,它有幾個參數,下面我介紹一下它的參數意義:
     int maxFrameLength:定義接收數據包的最大長度,若是發送的數據包超過此值,則拋出異常;
     int lengthFieldOffset:長度屬性部分的偏移值,0表示長度屬性位於數據包頭部;
     int lengthFieldLength:長度屬性的字節長度,若是設置爲4,就是咱們用4個字節存放數據包的長度;
     int lengthAdjustment:協議體長度調節值,修正信息長度,若是設置爲4,那麼解碼時再向後推4個字節;
     int initialBytesToStrip:跳過字節數,如咱們想跳過長度屬性部分。
 
2、實例-客戶端發送10MB字節的數據,Netty服務端一次接收到所有10MB數據
 
客戶端:定義一個消息體,用頭部四個字節存放數據包長度
 
[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
 
 
  1. public byte[] send(byte[] sendData) throws UnknownHostException, IOException {  
  2.         Socket socket = new Socket(serverIp, serverPort);  
  3.         OutputStream os = socket.getOutputStream();  
  4.         InputStream is = socket.getInputStream();  
  5.         byte resultArray[] = null;  
  6.         try {  
  7.             // 定義一個發送消息協議格式:|--header:4 byte--|--content:10MB--|  
  8.             // 獲取一個4字節長度的協議體頭  
  9.             byte[] dataLength = intToByteArray(4, sendData.length);  
  10.             // 和請求的數據組成一個請求數據包  
  11.             byte[] requestMessage = combineByteArray(dataLength, sendData);  
  12.             //發送數據-------------------------------  
  13.             os.write(requestMessage);  
  14.             os.flush();  
  15.             //接收數據-------------------------------  
  16.             resultArray = IOUtils.toByteArray(is);    
  17.         } catch (Exception e) {  
  18.             e.printStackTrace();  
  19.         } finally {  
  20.             os.close();  
  21.             is.close();  
  22.             socket.close();  
  23.         }  
  24.         return resultArray;  
  25.     }  
[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
 
 
  1. private static byte[] intToByteArray(int byteLength, int intValue) {  
  2.         return ByteBuffer.allocate(byteLength).putInt(intValue).array();  
  3.     }  
  4. private static byte[] combineByteArray(byte[] array1, byte[] array2) {  
  5.         byte[] combined = new byte[array1.length + array2.length];  
  6.         System.arraycopy(array1, 0, combined, 0, array1.length);  
  7.         System.arraycopy(array2, 0, combined, array1.length, array2.length);  
  8.         return combined;  
  9.     }  
 
Netty服務端:定義一個LengthFieldBasedFrameDecoder(1024*1024*1024, 0, 4,0,4)),最大數據量是1GB,長度屬性位於數據包頭部,佔4個字節,協議體調節值爲0,跳過頭部協議長度四個字節
[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
 
 
  1. @Override  
  2.             public void initChannel(SocketChannel ch) throws Exception {  
  3.                 ChannelPipeline pipeline = ch.pipeline();  
  4.   
  5.                 pipeline.addLast("framedecoder",new LengthFieldBasedFrameDecoder(1024*1024*1024, 0, 4,0,4));  
  6.                 pipeline.addLast(new TCPServiceHandler());// 處理業務Handler  
  7.                   
  8.   
  9.             }  


3、總結:客戶端和服務端定義消息格式必須一致

 

 

 

1.            frame包總體功能描述

 

此包主要做用於對TCP/IP數據包的分包和包重組,經常使用於數據的流傳輸,是擴展的解碼器。

包目錄結構以下:

netty 數據分包、組包、粘包處理機制(一) - 斷鴻零雁 - 斷鴻零雁的博客
 

 

2.            包中各種功能詳解

(1)  FrameDecoder

抽象類,將ChannelBuffers中的二進制數據轉換成有意義的數據幀(frame)對象,通常不直接調用,提供給此包中的FixedLengthFrameDecoder類、DelimiterBasedFrameDecoder類和LengthFieldBasedFrameDecoder類使用,也能夠提供給其餘類使用(暫不探討);

 

在數據傳輸中,咱們發送的數據包以下所示

 

+-----+-----+-----+

 | ABC | DEF | GHI |

 +-----+-----+-----+

 

而實際接收的包的格式爲:

 

+----+-------+---+---+
| AB | CDEFG | H | I |
+----+-------+---+---+

 

 

產生的緣由爲:數據在傳輸過程當中,產生數據包碎片(TCP/IP數據傳輸時大數據包沒法一次傳輸,被拆分紅小數據包,小數據包即爲數據包碎片),這就形成了實際接收的數據包和發送的數據包不一致的狀況。

 

而經過FrameDecoder便可實現對上述接收到的數據包的整理,從新還原成以下格式:

 

+-----+-----+-----+
| ABC | DEF | GHI |
+-----+-----+-----+

 

以下是一個自定義的Decoder類

 

public class MyFrameDecoder extends FrameDecoder {

 

         @Override

   protected Object decode(ChannelHandlerContext ctx,

                           channel,

                           ChannelBuffer buf) throws Exception {

 

     // Make sure if the length field was received.

     if (buf.readableBytes() < 4) {

        // The length field was not received yet - return null.

        // This method will be invoked again when more packets are

        // received and appended to the buffer.

        return null;

     }

 

     // The length field is in the buffer.

 

     // Mark the current buffer position before reading the length field

     // because the whole frame might not be in the buffer yet.

     // We will reset the buffer position to the marked position if

     // there's not enough bytes in the buffer.

     buf.markReaderIndex();
 

     // Read the length field.

     int length = buf.readInt();
 

     // Make sure if there's enough bytes in the buffer.

     if (buf.readableBytes() < length) {

        // The whole bytes were not received yet - return null.

        // This method will be invoked again when more packets are

        // received and appended to the buffer.

        // Reset to the marked position to read the length field again

        // next time.

        buf.resetReaderIndex();
 
        return null;

     }

 

     // There's enough bytes in the buffer. Read it.

     ChannelBuffer frame = buf.readBytes(length);
 

     // Successfully decoded a frame.  Return the decoded frame.

     return frame;

   }

 }

 

此時,咱們無需關注數據包是如何重組的,只須要作簡單的驗證(按照一個包驗證)就能夠了,FrameDecoder內部實現了組包的機制,不過,此時,需在數據的最前面封裝整個數據的長度,示例中數據長度佔了四個字節,即前四個字節是數據長度,後面的纔是真實的數據。

(2)  FixedLengthFrameDecoder

FixedLengthFrameDecoder主要是將諸如

+----+-------+---+---+
| AB | CDEFG | H | I |
+----+-------+---+---+

 

此類的數據包按照指定的frame長度從新組包,好比肯定長度爲3,則組包爲

 

+-----+-----+-----+
| ABC | DEF | GHI |
+-----+-----+-----+

 

構造方法爲:new FixedLengthFrameDecoder(int frameLength);

 

frameLength即修正後的幀長度

 

另外一個構造方法爲new FixedLengthFrameDecoder(int frameLength, boolean allocateFullBuffer);

 

allocateFullBuffer若是爲真,則表示初始化的ChannelBuffer大小爲frameLength。

 

 

(3)  Delimiters

分隔符類,DelimiterBasedFrameDecoder類的輔助類。

 

對Flash XML的socket通訊採用nulDelimiter()方法,對於通常的文本採用lineDelimiter()方法

 

(4)  DelimiterBasedFrameDecoder

對接收到的ChannelBuffers按照指定的分隔符Delimiter分隔,分隔符能夠是一個或者多個

 

如將如下數據包按照「\n」分隔:

 
+--------------+
| ABC\nDEF\r\n |
+--------------+

 

即爲:

 

+-----+-----+
| ABC | DEF |
+-----+-----+
 

而若是按照「\r\n」分隔,則爲:

 

+----------+
| ABC\nDEF |
+----------+

 

對於DelimiterBasedFrameDecoder中的構造方法,其中一些參數說明:

 

maxFrameLength:解碼的幀的最大長度

stripDelimiter:解碼時是否去掉分隔符

failFast:爲true,當frame長度超過maxFrameLength時當即報TooLongFrameException異常,爲false,讀取完整個幀再報異常

delimiter:分隔符

(5)  LengthFieldBasedFrameDecoder

經常使用的處理大數據分包傳輸問題的解決類,先對構造方法LengthFieldBasedFrameDecoder中的參數作如下解釋說明「

maxFrameLength:解碼的幀的最大長度

lengthFieldOffset :長度屬性的起始位(偏移位),包中存放有整個大數據包長度的字節,這段字節的其實位置

lengthFieldLength:長度屬性的長度,即存放整個大數據包長度的字節所佔的長度

lengthAdjustmen:長度調節值,在總長被定義爲包含包頭長度時,修正信息長度。initialBytesToStrip:跳過的字節數,根據須要咱們跳過lengthFieldLength個字節,以便接收端直接接受到不含「長度屬性」的內容

failFast :爲true,當frame長度超過maxFrameLength時當即報TooLongFrameException異常,爲false,讀取完整個幀再報異常

 

下面對各類狀況分別描述:

 

1. 2 bytes length field at offset 0, do not strip header

 

lengthFieldOffset   = 0

 lengthFieldLength   = 2

 lengthAdjustment    = 0

 initialBytesToStrip = 0 (= do not strip header)

 

 

 

 BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)

+--------+----------------+       +--------+----------------+

 | Length | Actual Content |---->| Length | Actual Content |

 | 0x000C | "HELLO, WORLD" |      | 0x000C | "HELLO, WORLD" |

 +--------+----------------+      +--------+----------------+

 

此時數據格式不作任何改變(沒有跳過任何字節)

 

2. 2 bytes length field at offset 0, strip header

 

lengthFieldOffset   = 0

 lengthFieldLength   = 2

 lengthAdjustment    = 0

 initialBytesToStrip = 2 (= the length of the Length field)

 

 BEFORE DECODE (14 bytes)         AFTER DECODE (12 bytes)
 +--------+----------------+      +----------------+
 | Length | Actual Content |---->| Actual Content |
 | 0x000C | "HELLO, WORLD" |      | "HELLO, WORLD" |
 +--------+----------------+      +----------------+

 

此時幀長度爲14個字節,但因爲前(lengthFieldOffset = 0)兩個(lengthFieldLength = 2)字節是表示幀長度的字節,不計入數據,故真實的數據長度爲12個字節。

 

3. 2 bytes length field at offset 0, do not strip header, the length field represents the length of the whole message

 

lengthFieldOffset   =  0

 lengthFieldLength   =  2

 lengthAdjustment    = -2 (= the length of the Length field)

 initialBytesToStrip =  0

 

 BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)
 +--------+----------------+     +--------+----------------+
 | Length | Actual Content |---->| Length | Actual Content |
 | 0x000E | "HELLO, WORLD" |      | 0x000E | "HELLO, WORLD" |
 +--------+----------------+     +--------+----------------+
 

此處定義的Length爲0x000E共佔了兩個字節,表示的幀長度爲14個字節,前(lengthFieldOffset = 0)兩個(lengthFieldLength = 2)字節爲Length,因爲設置的lengthAdjustment    = -2 (= the length of the Length field),故修正的信息實際長度補2,即解碼時往前推2個字節,解碼後仍是14個字節長度(此種狀況是把整個長度封裝,通常來說,咱們只封裝數據長度)

 

4. 3 bytes length field at the end of 5 bytes header, do not strip header

lengthFieldOffset   = 2 (= the length of Header 1)

 lengthFieldLength   = 3

 lengthAdjustment    = 0

 initialBytesToStrip = 0

 

BEFORE DECODE (17 bytes)                 AFTER DECODE (17 bytes)
 +---------+---------+--------------+    +---------+---------+------------+
 | Header 1| Length  |Actual Content|--->| Header 1| Length | Actual Content|
 |  0xCAFE | 0x00000C|"HELLO, WORLD"|    |  0xCAFE   |0x00000C| "HELLO, WORLD"|
 +---------+---------+--------------+    +----------+--------+-----------+

 

此處lengthFieldOffset   = 2,從第3個字節開始表示數據長度,長度佔3個字節,真實數據長度爲0x00000C 即12個字節,而lengthAdjustment=0,initialBytesToStrip = 0,故解碼後的數據與解碼前的數據相同。

4. 3 bytes length field at the beginning of 5 bytes header, do not strip header

lengthFieldOffset   = 0

 lengthFieldLength   = 3

 lengthAdjustment    = 2 (= the length of Header 1)

 initialBytesToStrip = 0

 

BEFORE DECODE (17 bytes)                          AFTER DECODE (17 bytes)
 +----------+----------+----------------+      +----------+----------+----------------+
 |  Length  | Header 1 | Actual Content |----->|  Length  | Header 1 | Actual Content |
 | 0x00000C |  0xCAFE  | "HELLO, WORLD" |      | 0x00000C |  0xCAFE  | "HELLO, WORLD" |
 +----------+----------+----------------+      +----------+----------+----------------+

此處因爲修正的字節數是2,且initialBytesToStrip = 0,故整個數據的解碼數據保持不變

總字節數是17,開始的三個字節表示字節長度:12,修正的字節是2,(即從第三個字節開始,再加兩個開始是真正的數據,其中跳過的字節數是0)

 

5. 2 bytes length field at offset 1 in the middle of 4 bytes header, strip the first header field and the length field

 

 

lengthFieldOffset   = 1 (= the length of HDR1)

 lengthFieldLength   = 2

 lengthAdjustment    = 1 (= the length of HDR2)

 initialBytesToStrip = 3 (= the length of HDR1 + LEN)

 

BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)

 +------+--------+------+----------------+      +------+----------------+

 | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |

 | 0xCA | 0x000C | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" |

 +------+--------+------+----------------+      +------+----------------+

 

從第2個字節開始解碼,取兩個字節做爲幀長度,爲12個字節,而後,修正一個字節,從第5個字節到最後表示幀數據,解碼時,因爲initialBytesToStrip=3,表示跳過前三個字節(去掉),故從第四個字節開始解析,解析出來後,如右圖所示。

 

6. 2 bytes length field at offset 1 in the middle of 4 bytes header, strip the first header field and the length field, the length field represents the length of the whole message

 

lengthFieldOffset   =  1

 lengthFieldLength   =  2

 lengthAdjustment    = -3 (= the length of HDR1 + LEN, negative)

 initialBytesToStrip =  3

 

BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)

 +------+--------+------+----------------+      +------+----------------+

 | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |

 | 0xCA | 0x0010 | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" |

 +------+--------+------+----------------+      +------+----------------+

 

從第二個字節開始,取兩個字節做爲幀長度,爲16個字節,而後補3個字節,故往前找三個字節,從HDP1開始解碼,而又由於initialBytesToStrip=3,解碼時忽略掉前三個字節,故從第四個字節開始解析,解析結果如右圖所示。

 

總結:通常來說,當lengthAdjustment 爲負數時,Length表示的是整個幀的長度,當lengthAdjustment爲正數或0時,表示真實數據長度。

(6)  LengthFieldPrepender

編碼類,自動將

+----------------+

| "HELLO, WORLD" |

+----------------+

格式的數據轉換成

+--------+----------------+

 + 0x000C | "HELLO, WORLD" |

 +--------+----------------+

格式的數據,

若是lengthIncludesLengthFieldLength設置爲true,則編碼爲

+--------+----------------+

+ 0x000E | "HELLO, WORLD" |

+--------+----------------+

格式的數據

 

應用場景:自定義pipelineFactory類: MyPipelineFactory implements ChannelPipelineFactory

pipeline.addLast("frameEncode", new LengthFieldPrepender(4, false));

(7)  TooLongFrameException

定義的數據包超過預約義大小異常類

(8)  CorruptedFrameException

定義的數據包損壞異常類

3.            frame包應用demo

解決分包問題,一般配置MyPipelineFactory中設置,示例以下:

 

public class MyPipelineFactory implements ChannelPipelineFactory {

 

    @Override

    public ChannelPipeline getPipeline() throws Exception {

       ChannelPipeline pipeline = Channels.pipeline();

       pipeline.addLast("decoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)); 

        pipeline.addLast("encoder", new LengthFieldPrepender(4, false));

       pipeline.addLast("handler", new MyHandler());

       return pipeline;

    }

 

}

 

 

在客戶端設置pipeline.addLast("encoder", new LengthFieldPrepender(4, false));

       pipeline.addLast("handler", new MyHandler());

 

前四個字節表示真實的發送的數據長度Length,編碼時會自動加上;

 

在服務器端設置pipeline.addLast("decoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));

真實數據最大字節數爲Integer.MAX_VALUE,解碼時自動去掉前面四個字節

相關文章
相關標籤/搜索