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/IP數據包的分包和包重組,經常使用於數據的流傳輸,是擴展的解碼器。
包目錄結構以下:
抽象類,將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,
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內部實現了組包的機制,不過,此時,需在數據的最前面封裝整個數據的長度,示例中數據長度佔了四個字節,即前四個字節是數據長度,後面的纔是真實的數據。
FixedLengthFrameDecoder主要是將諸如
+----+-------+---+---+
| AB | CDEFG | H | I |
+----+-------+---+---+
此類的數據包按照指定的frame長度從新組包,好比肯定長度爲3,則組包爲
+-----+-----+-----+
| ABC | DEF | GHI |
+-----+-----+-----+
構造方法爲:new FixedLengthFrameDecoder(int frameLength);
frameLength即修正後的幀長度
另外一個構造方法爲new FixedLengthFrameDecoder(int frameLength, boolean allocateFullBuffer);
allocateFullBuffer若是爲真,則表示初始化的ChannelBuffer大小爲frameLength。
分隔符類,DelimiterBasedFrameDecoder類的輔助類。
對Flash XML的socket通訊採用nulDelimiter()方法,對於通常的文本採用lineDelimiter()方法
對接收到的ChannelBuffers按照指定的分隔符Delimiter分隔,分隔符能夠是一個或者多個
如將如下數據包按照「\n」分隔:
+--------------+
| ABC\nDEF\r\n |
+--------------+
即爲:
+-----+-----+
| ABC | DEF |
+-----+-----+
而若是按照「\r\n」分隔,則爲:
+----------+
| ABC\nDEF |
+----------+
對於DelimiterBasedFrameDecoder中的構造方法,其中一些參數說明:
maxFrameLength:解碼的幀的最大長度
stripDelimiter:解碼時是否去掉分隔符
failFast:爲true,當frame長度超過maxFrameLength時當即報TooLongFrameException異常,爲false,讀取完整個幀再報異常
delimiter:分隔符
經常使用的處理大數據分包傳輸問題的解決類,先對構造方法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時,表示真實數據長度。
編碼類,自動將
+----------------+
| "HELLO, WORLD" |
+----------------+
格式的數據轉換成
+--------+----------------+
+ 0x000C | "HELLO, WORLD" |
+--------+----------------+
格式的數據,
若是lengthIncludesLengthFieldLength設置爲true,則編碼爲
+--------+----------------+
+ 0x000E | "HELLO, WORLD" |
+--------+----------------+
格式的數據
應用場景:自定義pipelineFactory類: MyPipelineFactory implements ChannelPipelineFactory
中
pipeline.addLast("frameEncode", new LengthFieldPrepender(4, false));
定義的數據包超過預約義大小異常類
定義的數據包損壞異常類
解決分包問題,一般配置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,解碼時自動去掉前面四個字節