netty 粘包問題處理
key words: netty 粘包 解包 半包 TCP
一般TCP粘包/拆包解決辦法
- 定長消息,例如每個報文長度固定,不夠補空格
- 使用回車換行符分割,在包尾加上分割符,例如Ftp協議
- 消息分割,頭為長度(消息總長度或消息體長度),通常頭用一個int32表示
- 復雜的應用層協議
netty的幾種解決方案
特殊分隔符解碼器:DelimiterBasedFrameDecoder
客戶端發送消息
String message = "netty is a nio server framework &"
+"which enables quick and easy development &"
+"of net applications such as protocol &"
+"servers and clients!";
服務端添加解碼器:DelimiterBasedFrameDecoder
ByteBuf delimiter = Unpooled.copiedBuffer("&".getBytes());
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,delimiter));
//1024表示單條消息的最大長度,解碼器在查找分隔符的時候,達到該長度還沒找到的話會拋異常
ch.pipeline().addLast(new StringDecoder());
....
ch.pipeline().addLast(new StringEncoder());
打印輸出:
接收消息:[netty is a nio server framework ]
接收消息:[which enables quick and easy development ]
接收消息:[of net applications such as protocol]
接收消息:[servers and clients!]
參數解釋:
public DelimiterBasedFrameDecoder(int maxFrameLength, ByteBuf delimiter) {
this(maxFrameLength, true, delimiter);
}
maxFrameLength:解碼的幀的最大長度
stripDelimiter:解碼時是否去掉分隔符
failFast:為true,當frame長度超過maxFrameLength時立即報TooLongFrameException異常,為false,讀取完整個幀再報異常
delimiter:分隔符
定長解碼器:FixedLengthFrameDecoder
參數說明:
- frameLength:幀的固定長度
服務端
ch.pipeline().addLast(new FixedLengthFrameDecoder(30));//設置定長解碼器 長度設置為30
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
System.out.println("接收客戶端msg:["+msg+"]");
ByteBuf echo=Unpooled.copiedBuffer(MESSAGE.getBytes());
ctx.writeAndFlush(echo);
}
客戶端
ch.pipeline().addLast(new FixedLengthFrameDecoder(30));//設置定長解碼器
基於包頭不固定長度的解碼器:LengthFieldBasedFrameDecoder
參數說明
- maxFrameLength:解碼的幀的最大長度
- lengthFieldOffset:長度屬性的起始位(偏移位),包中存放有整個大數據包長度的字節,這段字節的其實位置
- lengthFieldLength:長度屬性的長度,即存放整個大數據包長度的字節所占的長度
- lengthAdjustmen:長度調節值,在總長被定義為包含包頭長度時,修正信息長度。
- initialBytesToStrip:跳過的字節數,根據需要我們跳過lengthFieldLength個字節,以便接收端直接接受到不含“長度屬性”的內容
- failFast :為true,當frame長度超過maxFrameLength時立即報TooLongFrameException異常,為false,讀取完整個幀再報異常
備注:如果長度解析失誤,(過大,就直接丟棄這個包;過小,1、netty不拋出異常;2、校驗通不過)
源碼:
int frameLengthInt = (int) frameLength;
if (in.readableBytes() < frameLengthInt) {
return null;
}
封包時配合使用LengthFieldPrepender,很容易加上包長度
包頭添加總包長度字節:LengthFieldPrepender
在發布時,自動在幀的頭部加上長度
參數說明:
-
lengthFieldLength:長度屬性的字節長度
-
lengthIncludesLengthFieldLength:false,長度字節不算在總長度中,true,算到總長度中
應用:
pipeline.addLast("frameEncode", new LengthFieldPrepender(4, false));
官方說明:
編碼類,自動將
+----------------+
| "HELLO, WORLD" |
+----------------+
格式的數據轉換成如下格式的數據,
+--------+----------------+
+ 0x000C | "HELLO, WORLD" |
+--------+----------------+
如果lengthIncludesLengthFieldLength設置為true,則編碼為(多了兩個字節)
+--------+----------------+
+ 0x000E | "HELLO, WORLD" |
+--------+----------------+
備注
當時解決問題和記錄時,是查閱了官網和幾篇博客,如果里面內容有copy的地方,請留言url,我會把你的文章引用放到頂上去
