1.1, 粘包/拆包的含義java
TCP是個「流」協議, 並不瞭解上層業務數據的具體含義, 它會根據TCP緩衝區的實際狀況進行包的劃分,因此在業務上認爲,一個完整的包可能會被TCP拆分紅多個包進行發送,也有可能把多個小的包封裝成一個大的數據包發送,這就是所謂的TCP粘包和拆包問題。數組
假設客戶端分別發送了兩個數據包D1和D2給服務端,可能存在如下4種狀況:框架
(a)服務端分兩次讀取到了兩個獨立的數據包,分別是D1和D2,沒有粘包和拆包;ide
(b)服務端一次接收到了兩個數據包,D1和D2粘合在一塊兒,被稱爲TCP粘包;this
(c)服務端分兩次讀取到了兩個數據包,第一次讀取到了完整的D1包和D2包的部份內容,第二次讀取到了D2包的剩餘內容,這被稱爲TCP拆包;編碼
(d)服務端分兩次讀取到了兩個數據包,第一次讀取到了D1包的部份內容D1_1,第二次讀取到了D1包的剩餘內容D1_2和D2包的整包。spa
1.2, 粘包/拆包的處理.net
粘包/拆包的解決方法都是在報文結構上作處理,通常有3種方式: 定長報文、報文分隔符 、報文長度域netty
在NIO下, 框架Netty對於以上3種方式分別有本身的實現: FixedLengthFrameDecoder、DelimiterBasedFrameDecoder 、LengthFieldBasedFrameDecodercode
對於「長度域」的值, 雖然底層都是以字節的形式傳輸, 可是在上層數據類型上, 長度域有‘字符串’和‘數字’類型兩種.
假設原報文內容是
x=1111,y=2222,z=3333
原報文20個字節, 在對其添加長度域時, Java開發者可能見過下面這種結構, 尤爲是在金融/銀行開發中
00000020x=1111,y=2222,z=3333
上面報文的長度域就是‘字符串’類型, 對應的整型值爲原報文的字節長度, 不足8位左邊補0.
對於‘字符串’類型的長度域, 發送方輸出流的方式以下:
out.write("00000020".getBytes());//字符串00000020轉化爲字節 out.write("x=1111,y=2222,z=3333".getBytes());
//後面再說NIO下咱們該如何去解碼字符串長度域的報文.
接着說NIO下Netty自帶的長度域解碼器LengthFieldBasedFrameDecoder, 它支持的長度域就是‘數字’類型.
對於‘數字’類型的長度域, 若是約定長度爲4, 則其報文結構大抵以下, 不太好刻畫, 前面4位是數字20轉化的字節.
[0,0,0,20]x=1111,y=2222,z=3333
發送方輸出流的方式以下:
out.write(new byte[]{0,0,0,20});//數字20轉換爲4位字節數組 out.write("x=1111,y=2222,z=3333".getBytes());
而後Netty接收端直接使用LengthFieldBasedFrameDecoder就能夠很方便解碼
new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)
若是客戶端和服務端都是Netty開發, 你們默認的就是‘數字’類型的長度域, 發送端直接使用Netty自帶的LengthFieldPrepender編碼器就好了.
可是若是你做爲數據接收方的NIO開發者, 而發送方是權威方, 它給的報文的長度域是‘字符串’類型時, 你該怎麼處理?
這個時候, 咱們能夠基於Netty自定義一個解碼器, 專門處理字符串類型的長度域, 實現代碼以下:
package com.test; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.util.CharsetUtil; import java.util.List; /** * 字符串類型的長度域報文解碼器 * * @Author:tt * @Description: * @CreateTime:2019/6/26 下午11:30 */ public class StringLengthFieldDecoder extends ByteToMessageDecoder { //長度域的字符串長度,好比:長度字段的長度爲8,則報文有100個字節時,長度域值爲:00000100 private int lengthFieldSize; public StringLengthFieldDecoder(int lengthFieldSize) { this.lengthFieldSize = lengthFieldSize; } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { Object decoded = this.decode(ctx, in); if (decoded != null) { out.add(decoded); } } protected Object decode(ChannelHandlerContext ctx, ByteBuf in) { if (in.readableBytes() < lengthFieldSize) { return null; } //長度域的字符串值 String lengthFieldValStr = in.readBytes(lengthFieldSize).toString(CharsetUtil.UTF_8); //原報文長度 int frameLength = Integer.parseInt(lengthFieldValStr); if (in.readableBytes() < frameLength) { //這一步很重要,回退讀索引 in.readerIndex(in.readerIndex() - lengthFieldSize); return null; } return in.readBytes(frameLength); } }