一、Netty粘包解決方案
當我們使用Netty進行數據傳輸時,可能會遇到粘包現象,這種現象在單個包大小很小時很常見。但還是有方法來解決這個問題。下面介紹兩種最常見的解決方案。
1、FixedLengthFrameDecoder
該解碼器固定讀取指定消息長度的數據,如果每條消息都是固定長度的,則適合使用此解碼器。
2、DelimiterBasedFrameDecoder
該解碼器依賴於分隔符對接收到的數據進行拆分。常用的分隔符有”\r\n”、“\n”、“$”等。當每條消息中都包含有分隔符時,適合使用此解碼器。
二、Netty自定義粘包拆包
有些情況下,我們的數據並不能依靠固定長度或分隔符區分消息邊界,這時候就需要自定義粘包解包。下面給出代碼示例。
public class MyDecoder extends MessageToMessageDecoder<ByteBuf> { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { while (in.readableBytes() >= 4) { //判斷是否有一個完整的消息 int length = in.readInt(); //讀取消息長度 if (in.readableBytes() < length) {//不足一個整包,重置讀指針 in.readerIndex(in.readerIndex() - 4); return; } out.add(in.readBytes(length)); //讀取完整的數據包 } } }
以上示例中,我們繼承MessageToMessageDecoder類,並重寫decode方法,實現自定義解碼。在這個例子中,我們讀取了前4個字節作為數據包長度,然後根據長度讀取完整的數據包。
三、Netty分包粘包處理
1、使用LengthFieldBasedFrameDecoder
該解碼器先從ByteBuf中讀取指定長度的整型值,該整型值表示實際數據的長度。基於長度解碼器,可以解決TCP協議中粘包和分包問題。下面給出代碼示例。
public class MyDecoder extends LengthFieldBasedFrameDecoder { public MyDecoder() { super(ByteOrder.LITTLE_ENDIAN, 1024, 0, 4, 0, 4, true); // 參數說明:LITTLE_ENDIAN: should length field byte order be littleEndian // 1024: maxFrameLength :表示數據幀最大長度 // 0: lengthFieldOffset :表示數據長度字段值的起始位置,因為我們在發送數據時在數據包的最前面添加了一個4字節的int數據,所以這裡為0 // 4:lengthFieldLength :表示長度字段所佔的字節數 // 0:lengthAdjustment:一個長度調整值,讓解碼器從第一個字節開始;這裡為0 // 4:initialBytesToStrip:去掉請求頭的幾個字段長度;這裡為4,因為前面我們在請求頭加上了4個字節的int數據表示真實數據的長度信息 // true:failFast :如果該參數設置為 true,則表示如果幀長度超過長度限制,就會立即拋出一個 TooLongFrameException,而不是等待後續字節到達,這樣可以防止太多的資源浪費在非預期包上 } @Override protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { ByteBuf frame = (ByteBuf) super.decode(ctx, in); if (frame == null) { return null; } int realLength = frame.readInt(); ByteBuf data = frame.slice(frame.readerIndex(), realLength); frame.release(); return data; } }
2、使用MessageToMessageCodec
該編碼器先編碼,再解碼,使用起來相對比較複雜。下面給出代碼示例。
public class MyMessageCodec extends MessageToMessageCodec<ByteBuf, Object> { @Override protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception { ByteBuf byteBuf = ctx.alloc().buffer(); byteBuf.writeInt(0); // 佔位長度 byteBuf.writeBytes(msg.toString().getBytes(CharsetUtil.UTF_8)); byteBuf.setInt(0, byteBuf.readableBytes() - 4); // 記錄實際長度 out.add(byteBuf); } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { int length = in.readInt(); byte[] bytes = new byte[length]; in.readBytes(bytes); out.add(new String(bytes, CharsetUtil.UTF_8)); } }
以上代碼實現了自定義編解碼器。在encode方法中,我們在寫入真實數據之前先寫入了4個字節的int類型數據,用於記錄真實數據的長度信息。在decode方法中,先讀取4個字節的int信息,再按照該長度讀取真實的數據。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/154762.html