Netty之TCP粘包的原因與解決
1. TCP粘包的原因
TCP是基於資料流傳輸 的協議,訊息都是位元組流(byte[])。傳送方可能會為了傳送方便將多條比較短的訊息湊到一塊一次傳送,而接收方也可能因為處理不及時導致快取中堆積了多條訊息組成的byte[],訊息彼此黏連在一起,這導致接收方無法準確的區分訊息。
2. TCP粘包的解決
一般有兩種方式解決:
- 對資料的格式進行定義,協定一條資料的起始位和結束位標識
- 使用一段資料長度標識位,比如int(4位元組)來標識緊跟著它的訊息長度
這裡以第二種為例,在Netty中使用自定義的MessageToMessageEncoder/ByteToMessageDecoder來解決寫入時和接受時的粘包問題
接受方解碼訊息
public class MsgDecoder extends ByteToMessageDecoder { /** * @param ctx * @param inbyteBuf是一個可讀寫的緩衝區,這個緩衝區具有一定的byte容量,容量由讀寫決定,可讀的容量不能超過寫入的容量, *它內部持有讀的標識位和寫的標識位,在nio非阻塞特性中,IO操作不會阻塞,因為IO讀到的資料會全放到Buf中 * @param out * @throws Exception */ @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { //因為TCP的二進位制流傳輸特性,每次讀取的包不一定是一條完整的訊息,因此需要粘包,而in就是用來存放每次讀取的包的 while (true) { //使用4位元組(Int)標記一條訊息的長度,如果可讀長度不超過4位元組則等待下一個包 if (in.readableBytes() <= 4) { break; } //標記當前讀取的位數 in.markReaderIndex(); //讀取4位元組,即長度位,readerIndex會增加4 int length = in.readInt(); if (length <= 0) { throw new Exception("a negative length occurd while decode!"); } //如果剩餘可讀位元組數量不足訊息長度,則返回到標記的位數,則下一次又會讀取長度位 if (in.readableBytes() < length) { in.resetReaderIndex(); break; } //讀取訊息 byte[] msg = new byte[length]; in.readBytes(msg); out.add(new String(msg, "UTF-8")); } } }
傳送方編碼訊息
public class MsgEncoder extends MessageToMessageEncoder<String> { @Override protected void encode(ChannelHandlerContext ctx, String msg, List<Object> out) { if (msg == null) return; ByteBuf buffer = Unpooled.buffer(4); //string->byte[] byte[] msgBytes = msg.getBytes(); buffer.writeInt(msgBytes.length); buffer.writeBytes(msgBytes); out.add(buffer); } }