NIO與Netty篇-(1)-粘包拆包問題

1.1 什麼是粘包拆包

client 連續發送 server 的數據包,server 接收到數據會出現數據包粘在一塊兒的狀況java

好比 client發送了 數據"123456"和"78910"

server收到倒是: "12345" 和 "678910"app

1.2 爲什麼出現粘包?

TCP報文格式 以下, ide

image.png

在 TCP首部並未指明數據包的長度大數據

  1. TCP首部中有20bytes的固定長度;
  2. 可於第1個報文中指明: 最大報文段長度MSS(Maximum Segment Size); 可是它是選項 部分, 非必有的;

1.3 如何解決粘包問題

通常解決粘包問題的四種方案:ui

1.3.1 固定發包長度

客戶端發送數據包時, 固定長度, 好比 1024字節, 若是某次發包不足 1024字節, 空格補足;

1.3.2 使用固定分隔符

客戶端發包時, 每一個包末尾使用固定分隔符, 好比"rn";

若是數據包粘包了, 拆包時, 就等下一個數據包直到拿到"rn";this

拆後的頭部部分前一個包的剩餘部分合併; 這樣就獲得一個完整的包.編碼

1.3.3 消息頭部保存消息長度len字段

消息分爲 頭部 和 消息體; 而後在頭部增設一個 消息長度的字段; 接收時, 讀到夠len長度的數據, 纔算讀完完整數據!

1.3.4 自定義協議

本身定製本身的發包協議; 指定數據長度和分拆合併邏輯;

1.4 Netty如何解決粘包問題

1.4.1 固定發包長度

FixedLengthFrameDecoder:spa

public class FixedLengthFrameDecoder extends ByteToMessageDecoder {
    private final int frameLength;
    ...
}

將接收到的字節按固定字節數分割。例如,若是你收到如下四個片斷化的數據包:.net

第1個packet 第2個packet 第3個packet 第4個packet
A BC DEFG HI

FixedLengthFrameDecoder(3) 將其解碼爲如下3個固定長度的數據包:netty

第1個packet 第2個packet 第3個packet
ABC DEF GHI

發包前編碼:

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

public class FixedLengthFrameEncoder extends MessageToByteEncoder<String> {
    private int len;

    public FixedLengthFrameEncoder(int len) {
        this.len = len;
    }

    @Override
    protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception {
        // 超長直接拋出異常
        if (msg.length() > len) {
            throw new UnsupportedOperationException("message too large, limited " + len);
        }

        // 不足長補全
        if (msg.length() < len) {
            msg = appendSpace(msg);
        }

        ctx.writeAndFlush(Unpooled.wrappedBuffer(msg.getBytes()));
    }  // 進行空格補全


  /**
   * 補空格
   * @param msg
   * @return
   */
  private String appendSpace(String msg) {
        StringBuilder builder = new StringBuilder(msg);
        for (int i = 0; i < len - msg.length(); i++) {
            builder.append(" ");
        }

        return builder.toString();
    }
}

1.4.2 固定分隔符

1.4.2.1 回車換行符-LineBasedFrameDecoder

public class LineBasedFrameDecoder extends ByteToMessageDecoder {}

1.4.2.2 自定義分隔符-DelimiterBasedFrameDecoder

public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {}

1.4.3 消息保存長度len字段

LengthFieldBasedFrameDecoder & LengthFieldPrepender

增長長度字段, 標明數據長度;

maxFrameLength:指定包所傳遞的最大數據包大小;
lengthFieldOffset:指定length字段在字節碼中的偏移量;
lengthFieldLength:指定length字段所佔用的字節長度;
lengthAdjustment:對含消息頭和消息體的, 咱們有時需進行消息頭的長度調整,方便只取消息體: 此字段就是消息頭長;
initialBytesToStrip:對於length字段在消息頭中間的狀況,能夠經過此字段, 忽略消息頭及length字段所佔的字節。

收包後解碼:

public class LengthFieldBasedFrameDecoder extends ByteToMessageDecoder {

    private final ByteOrder byteOrder;
    private final int maxFrameLength;
    private final int lengthFieldOffset;
    private final int lengthFieldLength;
    private final int lengthFieldEndOffset;
    private final int lengthAdjustment;
    private final int initialBytesToStrip;
    private final boolean failFast;
    private boolean discardingTooLongFrame;
    private long tooLongFrameLength;
    private long bytesToDiscard;
    ...
}

發包前編碼:

public class LengthFieldPrepender extends MessageToMessageEncoder<ByteBuf> {

    private final ByteOrder byteOrder;
    private final int lengthFieldLength;
    private final boolean lengthIncludesLengthFieldLength;
    private final int lengthAdjustment;
    ...
}

1.4.4 自定義粘包拆包器

1.4.4.1 繼承 LengthFieldBasedFrameDecoder和LengthFieldPrepender

繼承這兩個類, 而後複寫本身的邏輯

1.4.4.2 本身擴展MessageToByteEncoder和ByteToMessageDecoder

public abstract class MessageToByteEncoder<I> extends ChannelOutboundHandlerAdapter{}

public abstract class ByteToMessageDecoder extends ChannelInboundHandlerAdapter {

1.5 小結

1. 發包固定長度

2. 發包指定分隔符

3. 發包頭部加數據長度

4. 自定義協議

相關文章
相關標籤/搜索