Netty拆包黏包、TCP粘包、拆包以及粘包、拆包產(chǎn)生原因

1. TCP粘包、拆包圖解

image

假設(shè)客戶端分別發(fā)送了兩個(gè)數(shù)據(jù)包D1和D2給服務(wù)端,由于服務(wù)端一次讀取到字節(jié)數(shù)是不確定的,故可能存在以下四種情況:

1.服務(wù)端分兩次讀取到了兩個(gè)獨(dú)立的數(shù)據(jù)包,分別是D1和D2,沒有粘包和拆包
2.服務(wù)端一次接受到了兩個(gè)數(shù)據(jù)包,D1和D2粘合在一起,稱之為TCP粘包
3.服務(wù)端分兩次讀取到了數(shù)據(jù)包,第一次讀取到了完整的D1包和D2包的部分內(nèi)容,第二次讀取到了D2包的剩余內(nèi)容,這稱之為TCP拆包
4.服務(wù)端分兩次讀取到了數(shù)據(jù)包,第一次讀取到了D1包的部分內(nèi)容D1_1,第二次讀取到了D1包的剩余部分內(nèi)容D1_2和完整的D2包。

特別要注意的是,如果TCP的接受滑窗非常小,而數(shù)據(jù)包D1和D2比較大,很有可能會(huì)發(fā)生第五種情況,即服務(wù)端分多次才能將D1和D2包完全接受,期間發(fā)生多次拆包。

2. 粘包、拆包產(chǎn)生原因

2.1 滑動(dòng)窗口

TCP流量控制主要使用滑動(dòng)窗口協(xié)議,滑動(dòng)窗口是接受數(shù)據(jù)端使用的窗口大小,用來告訴發(fā)送端接口端的緩存大小,以此可以控制發(fā)送端發(fā)送數(shù)據(jù)的大小,從而達(dá)到流量控制的目的。這個(gè)窗口的大小就是我們以此傳輸幾個(gè)數(shù)據(jù)。對所有數(shù)據(jù)幀按順序賦予編號(hào)。發(fā)送方在發(fā)送過程中始終保持著一個(gè)發(fā)送窗口,只有落在發(fā)送方窗口的幀才允許被發(fā)送;同時(shí)接受方也維護(hù)一個(gè)窗口,只有落在接受窗口內(nèi)的幀才允許接收。

滑動(dòng)窗口是如何造成粘包、拆包的

粘包:假設(shè)發(fā)送方的每256 bytes表示一個(gè)完整的報(bào)文,接收方由于數(shù)據(jù)處理不及時(shí),這256個(gè)字節(jié)的數(shù)據(jù)都會(huì)被緩存到SO_RCVBUF(接收緩存區(qū))中。如果接收方的SO_RCVBUF中緩存了多個(gè)報(bào)文,那么對于接收方而言,這就是粘包。

拆包:考慮另外一種情況,假設(shè)接收方的窗口只剩了128,意味著發(fā)送方最多還可以發(fā)送128字節(jié),而由于發(fā)送方的數(shù)據(jù)大小是256字節(jié),因此只能發(fā)送前128字節(jié),等到接收方ack后,才能發(fā)送剩余字節(jié)。這就造成了拆包。

2.2 MSS和MTU分片

MSS: 是Maximum Segement Size縮寫,表示TCP報(bào)文中data部分的最大長度,是TCP協(xié)議在OSI五層網(wǎng)絡(luò)
模型中傳輸層對一次可以發(fā)送的最大數(shù)據(jù)的限制。

MTU: 最大傳輸單元是,Maxitum Transmission Unit的簡寫,是OSI五層網(wǎng)絡(luò)模型中鏈路層(datalink layer)對一次可以發(fā)送的最大數(shù)據(jù)的限制。

image

對于應(yīng)用層來說,只關(guān)心發(fā)送的數(shù)據(jù)DATA,將數(shù)據(jù)寫入socket在內(nèi)核中的發(fā)送緩沖區(qū)SO_SNDBUF即返回,操作系統(tǒng)會(huì)將SO_SNDBUF中的數(shù)據(jù)取出來進(jìn)行發(fā)送。傳輸層會(huì)在DATA前面加上TCP Header,構(gòu)成一個(gè)完整的TCP報(bào)文。

當(dāng)數(shù)據(jù)到達(dá)網(wǎng)絡(luò)層(network layer)時(shí),網(wǎng)絡(luò)層會(huì)在TCP報(bào)文的基礎(chǔ)上再添加一個(gè)IP Header,也就是將自己的網(wǎng)絡(luò)地址加入到報(bào)文中。到數(shù)據(jù)鏈路層時(shí),還會(huì)加上Datalink Header和CRC。

當(dāng)?shù)竭_(dá)物理層時(shí),會(huì)將SMAC(Source Machine,數(shù)據(jù)發(fā)送方的MAC地址),DMAC(Destination Machine,數(shù)據(jù)接受方的MAC地址 )和Type域加入。

可以發(fā)現(xiàn)數(shù)據(jù)在發(fā)送前,每一層都會(huì)在上一層的基礎(chǔ)上增加一些內(nèi)容,下圖演示了MSS、MTU在這個(gè)過程中的作用。
[圖片上傳失敗...(image-7a5058-1618037626721)]

MTU是以太網(wǎng)傳輸數(shù)據(jù)方面的限制,每個(gè)以太網(wǎng)幀都有最小的大小64bytes最大不能超過1518bytes。刨去以太網(wǎng)幀的幀頭 (DMAC目的MAC地址48bit=6Bytes+SMAC源MAC地址48bit=6Bytes+Type域2bytes)14Bytes和幀尾 CRC校驗(yàn)部分4Bytes(這個(gè)部分有時(shí)候大家也把它叫做FCS),那么剩下承載上層協(xié)議的地方也就是Data域最大就只能有1500Bytes這個(gè)值 我們就把它稱之為MTU。

由于MTU限制了一次最多可以發(fā)送1500個(gè)字節(jié),而TCP協(xié)議在發(fā)送DATA時(shí),還會(huì)加上額外的TCP Header和Ip Header,因此刨去這兩個(gè)部分,就是TCP協(xié)議一次可以發(fā)送的實(shí)際應(yīng)用數(shù)據(jù)的最大大小,也就是MSS

MSS長度 = MTU長度 - IP Header - TCP Header

TCP Header的長度是20字節(jié),IPv4中IP Header長度是20字節(jié),IPV6中IP Header長度是40字節(jié),因此:在IPV4中,以太網(wǎng)MSS可以達(dá)到1460byte;在IPV6中,以太網(wǎng)MSS可以達(dá)到1440byte。

需要注意的是MSS表示的一次可以發(fā)送的DATA的最大長度,而不是DATA的真實(shí)長度。發(fā)送方發(fā)送數(shù)據(jù)時(shí),當(dāng)SO_SNDBUF中的數(shù)據(jù)量大于MSS時(shí),操作系統(tǒng)會(huì)將數(shù)據(jù)進(jìn)行拆分,使得每一部分都小于MSS,這就是拆包,然后每一部分都加上TCP Header,構(gòu)成多個(gè)完整的TCP報(bào)文進(jìn)行發(fā)送,當(dāng)然經(jīng)過網(wǎng)絡(luò)層和數(shù)據(jù)鏈路層的時(shí)候,還會(huì)分別加上相應(yīng)的內(nèi)容。

需要注意: 默認(rèn)情況下,與外部通信的網(wǎng)卡的MTU大小是1500個(gè)字節(jié)。而本地回環(huán)地址的MTU大小為65535,這是因?yàn)楸镜販y試時(shí)數(shù)據(jù)不需要走網(wǎng)卡,所以不受到1500的限制。

3. Nagle算法

TCP/IP協(xié)議中,無論發(fā)送多少數(shù)據(jù),總是要在數(shù)據(jù)(DATA)前面加上協(xié)議頭(TCP Header+IP Header),同時(shí),對方接收到數(shù)據(jù),也需要發(fā)送ACK表示確認(rèn)。

即使從鍵盤輸入的一個(gè)字符,占用一個(gè)字節(jié),可能在傳輸上造成41字節(jié)的包,其中包括1字節(jié)的有用信息和40字節(jié)的首部數(shù)據(jù)。這種情況轉(zhuǎn)變成了4000%的消耗,這樣的情況對于重負(fù)載的網(wǎng)絡(luò)來是無法接受的。

為了盡可能的利用網(wǎng)絡(luò)帶寬,TCP總是希望盡可能的發(fā)送足夠大的數(shù)據(jù)。(一個(gè)連接會(huì)設(shè)置MSS參數(shù),因此,TCP/IP希望每次都能夠以MSS尺寸的數(shù)據(jù)塊來發(fā)送數(shù)據(jù))。

3.1 Nagle算法——盡可能發(fā)送大塊數(shù)據(jù),避免網(wǎng)絡(luò)中充斥著許多小數(shù)據(jù)塊

3.2 Nagle算法的規(guī)則:

  • 如果SO_SNDBUF(發(fā)送緩沖區(qū))中的數(shù)據(jù)長度達(dá)到MSS,則允許發(fā)送;
  • 如果該SO_SNDBUF中含有FIN,表示請求關(guān)閉連接,則先將SO_SNDBUF中的剩余數(shù)據(jù)發(fā)送,再關(guān)閉;
  • 設(shè)置了TCP_NODELAY=true選項(xiàng),則允許發(fā)送。TCP_NODELAY是取消TCP的確認(rèn)延遲機(jī)制,相當(dāng)于禁用了Nagle 算法。
  • 未設(shè)置TCP_CORK選項(xiàng)時(shí),若所有發(fā)出去的小數(shù)據(jù)包(包長度小于MSS)均被確認(rèn),則允許發(fā)送;
    上述條件都未滿足,但發(fā)生了超時(shí)(一般為200ms),則立即發(fā)送。

4. 粘包問題的解決策略

  • 消息定長,每個(gè)報(bào)文固定長度,不夠的空格補(bǔ)齊
  • 在包尾部增加換車換行進(jìn)行分割
  • 將消息提分為消息頭和消息體,消息頭中包含表示總長度
  • 更復(fù)雜的應(yīng)用層協(xié)議

5. netty 解決TCP粘包問題

拆包原理:

  • 如果當(dāng)前讀取的數(shù)據(jù)不足以拼接成一個(gè)完整的業(yè)務(wù)數(shù)據(jù)包,那就保留該數(shù)據(jù),繼續(xù)從tcp緩沖區(qū)中讀取,直到得到一個(gè)完整的數(shù)據(jù)包
  • 如果當(dāng)前讀到的數(shù)據(jù)加上已經(jīng)讀取的數(shù)據(jù)足夠拼接成一個(gè)數(shù)據(jù)包,那就將已經(jīng)讀取的數(shù)據(jù)拼接上本次讀取的數(shù)據(jù),夠成一個(gè)完整的業(yè)務(wù)數(shù)據(jù)包傳遞到業(yè)務(wù)邏輯,多余的數(shù)據(jù)仍然保留,以便和下次讀到的數(shù)據(jù)嘗試拼接

5.1 ByteToMessageDecoder

ByteToMessageDecoder:netty 中的拆包也是如上這個(gè)原理,內(nèi)部會(huì)有一個(gè)累加器,每次讀取到數(shù)據(jù)都會(huì)不斷累加,然后嘗試對累加到的數(shù)據(jù)進(jìn)行拆包,拆成一個(gè)完整的業(yè)務(wù)數(shù)據(jù)包,這個(gè)基類叫做 ByteToMessageDecoder

累加器

ByteToMessageDecoder中定義了兩個(gè)累加器

public static final Cumulator MERGE_CUMULATOR = ...;
public static final Cumulator COMPOSITE_CUMULATOR = ...;

MERGE_CUMULATOR的原理是每次都將讀取到的數(shù)據(jù)通過內(nèi)存拷貝的方式,拼接到一個(gè)大的字節(jié)容器中,這個(gè)字節(jié)容器在 ByteToMessageDecoder中叫做 cumulation。

public static final Cumulator MERGE_CUMULATOR = new Cumulator() {
        @Override
        public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) {
            if (!cumulation.isReadable() && in.isContiguous()) {
                // If cumulation is empty and input buffer is contiguous, use it directly
                cumulation.release();
                return in;
            }
            try {
                final int required = in.readableBytes();
                //  判斷是否需要擴(kuò)容
                if (required > cumulation.maxWritableBytes() ||
                        (required > cumulation.maxFastWritableBytes() && cumulation.refCnt() > 1) ||
                        cumulation.isReadOnly()) {
                    // Expand cumulation (by replacing it) under the following conditions:
                    // - cumulation cannot be resized to accommodate the additional data
                    // - cumulation can be expanded with a reallocation operation to accommodate but the buffer is
                    //   assumed to be shared (e.g. refCnt() > 1) and the reallocation may not be safe.
                    return expandCumulation(alloc, cumulation, in);
                }
                //累加
                cumulation.writeBytes(in, in.readerIndex(), required);
                in.readerIndex(in.writerIndex());
                return cumulation;
            } finally {
                // We must release in in all cases as otherwise it may produce a leak if writeBytes(...) throw
                // for whatever release (for example because of OutOfMemoryError)
                in.release();
            }
        }
    };

擴(kuò)容:

static ByteBuf expandCumulation(ByteBufAllocator alloc, ByteBuf oldCumulation, ByteBuf in) {
        int oldBytes = oldCumulation.readableBytes();
        int newBytes = in.readableBytes();
        int totalBytes = oldBytes + newBytes;
        ByteBuf newCumulation = alloc.buffer(alloc.calculateNewCapacity(totalBytes, MAX_VALUE));
        ByteBuf toRelease = newCumulation;
        try {
            // This avoids redundant checks and stack depth compared to calling writeBytes(...)
            newCumulation.setBytes(0, oldCumulation, oldCumulation.readerIndex(), oldBytes)
                .setBytes(oldBytes, in, in.readerIndex(), newBytes)
                .writerIndex(totalBytes);
            in.readerIndex(in.writerIndex());
            toRelease = oldCumulation;
            return newCumulation;
        } finally {
            toRelease.release();
        }
    }

擴(kuò)容也是一個(gè)內(nèi)存拷貝操作,新增的大小即是新讀取數(shù)據(jù)的大小

拆包

累加器原理清楚之后。回到主流程,channelRead方法,channelRead方法是每次從TCP緩沖區(qū)讀到數(shù)據(jù)都會(huì)調(diào)用的方法,觸發(fā)點(diǎn)在AbstractNioByteChannelread方法中,里面有個(gè)while循環(huán)不斷讀取,讀取到一次就觸發(fā)一次channelRead。

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof ByteBuf) {
            CodecOutputList out = CodecOutputList.newInstance();
            try {
                //1.累加數(shù)據(jù)
                first = cumulation == null;
                cumulation = cumulator.cumulate(ctx.alloc(), first ? Unpooled.EMPTY_BUFFER : cumulation, (ByteBuf) msg);
                //2. 將累加到的數(shù)據(jù)傳遞給業(yè)務(wù)進(jìn)行拆包
                callDecode(ctx, cumulation, out);
            } catch (DecoderException e) {
                throw e;
            } catch (Exception e) {
                throw new DecoderException(e);
            } finally {
                try {
                    //3.netty會(huì)在每次讀取到一次數(shù)據(jù),業(yè)務(wù)拆包之后對字節(jié)字節(jié)容器做清理,清理部分的代碼如下
                    if (cumulation != null && !cumulation.isReadable()) {
                         //如果字節(jié)容器當(dāng)前已無數(shù)據(jù)可讀取,直接銷毀字節(jié)容器,并且標(biāo)注一下當(dāng)前字節(jié)容器一次數(shù)據(jù)也沒讀取
                        numReads = 0;
                        cumulation.release();
                        cumulation = null;
                    } else if (++numReads >= discardAfterReads) {
                        // We did enough reads already try to discard some bytes so we not risk to see a OOME.
                        // See https://github.com/netty/netty/issues/4275
                       //字節(jié)容器中仍然有未被業(yè)務(wù)拆包器讀取的數(shù)據(jù),那就做一次壓縮,有效數(shù)據(jù)段整體移到容器首部
                        numReads = 0;
                        discardSomeReadBytes();
                    }
                    //4. 傳遞業(yè)務(wù)數(shù)據(jù)包給業(yè)務(wù)解碼器處理
                    int size = out.size();
                    firedChannelRead |= out.insertSinceRecycled();
                    fireChannelRead(ctx, out, size);
                } finally {
                    out.recycle();
                }
            }
        } else {
            ctx.fireChannelRead(msg);
        }
    }

方法體可以分為以下幾個(gè)邏輯步驟

1.累加數(shù)據(jù)
2.將累加到的數(shù)據(jù)傳遞給業(yè)務(wù)進(jìn)行業(yè)務(wù)拆包
3.清理字節(jié)容器
4.傳遞業(yè)務(wù)數(shù)據(jù)包給業(yè)務(wù)解碼器處理

壓縮,有效數(shù)據(jù)段整體移到容器首部

discardSomeReadBytes之前,字節(jié)累加器中的數(shù)據(jù)分布
+--------------+----------+----------+
|   readed     | unreaded | writable | 
+--------------+----------+----------+

discardSomeReadBytes之后,字節(jié)容器中的數(shù)據(jù)分布
+----------+-------------------------+
| unreaded |      writable           | 
+----------+-------------------------+
這樣字節(jié)容器又可以承載更多的數(shù)據(jù)了

callDecode 字節(jié)容器的數(shù)據(jù)拆分成業(yè)務(wù)數(shù)據(jù)包塞到業(yè)務(wù)數(shù)據(jù)容器out中

    protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
        try {
            while (in.isReadable()) {
                int outSize = out.size();

                if (outSize > 0) {
                    fireChannelRead(ctx, out, outSize);
                    out.clear();

                    // Check if this handler was removed before continuing with decoding.
                    // If it was removed, it is not safe to continue to operate on the buffer.
                    //
                    // See:
                    // - https://github.com/netty/netty/issues/4635
                    if (ctx.isRemoved()) {
                        break;
                    }
                    outSize = 0;
                }

                int oldInputLength = in.readableBytes();
                //進(jìn)行拆包 傳進(jìn)去的是當(dāng)前讀取到的未被消費(fèi)的所有的數(shù)據(jù),以及業(yè)務(wù)協(xié)議包容器
                decodeRemovalReentryProtection(ctx, in, out);

                // Check if this handler was removed before continuing the loop.
                // If it was removed, it is not safe to continue to operate on the buffer.
                //
                // See https://github.com/netty/netty/issues/1664
                if (ctx.isRemoved()) {
                    break;
                }

                if (outSize == out.size()) {
                    //一個(gè)是拆包器什么數(shù)據(jù)也沒讀取,可能數(shù)據(jù)還不夠業(yè)務(wù)拆包器處理,直接break等待新的數(shù)據(jù)
                    if (oldInputLength == in.readableBytes()) {
                        break;
                    } else {
                        //拆包器已讀取部分?jǐn)?shù)據(jù),說明解碼器仍然在工作,繼續(xù)解碼
                        continue;
                    }
                }

                if (oldInputLength == in.readableBytes()) {
                    throw new DecoderException(
                            StringUtil.simpleClassName(getClass()) +
                                    ".decode() did not read anything but decoded a message.");
                }

                if (isSingleDecode()) {
                    break;
                }
            }
        } catch (DecoderException e) {
            throw e;
        } catch (Exception cause) {
            throw new DecoderException(cause);
        }
    }

5.LineBasedFramDecoder源碼

ByteOrder byteOrder ByteOrder.BIG_ENDIAN
int maxFrameLength Integer.MAX_VALUE 包的最大長度,超出包的最大長度netty將會(huì)做一些特殊處理
int lengthFieldOffset 0 長度域的偏移量
int lengthFieldLength 4 長度域長度
int lengthAdjustment 0 長度域的偏移量矯正。 如果長度域的值,除了包含有效數(shù)據(jù)域的長度外,還包含了其他域(如長度域自身)長度,那么,就需要進(jìn)行矯正。矯正的值為:包長 - 長度域的值 – 長度域偏移 – 長度域長。
int initialBytesToStrip 12 表示netty拿到一個(gè)完整的數(shù)據(jù)包之后向業(yè)務(wù)解碼器傳遞之前,應(yīng)該跳過多少字節(jié)
boolean failFast true 超過最大maxFrameLength是是否報(bào)錯(cuò)
https://blog.csdn.net/john1337/article/details/102806307

1.基于長度的拆包


上面這類數(shù)據(jù)包協(xié)議比較常見的,前面幾個(gè)字節(jié)表示數(shù)據(jù)包的長度(不包括長度域),后面是具體的數(shù)據(jù)。拆完之后數(shù)據(jù)包是一個(gè)完整的帶有長度域的數(shù)據(jù)包(之后即可傳遞到應(yīng)用層解碼器進(jìn)行解碼),創(chuàng)建一個(gè)如下方式的LengthFieldBasedFrameDecoder即可實(shí)現(xiàn)這類協(xié)議

  new LengthFieldBasedFrameDecoder(Integer.MAX, 0, 4);
    /**
   * @param maxFrameLength  包的最大長度
   *        the maximum length of the frame.  If the length of the frame is
   *        greater than this value, {@link TooLongFrameException} will be
   *        thrown.
   * @param lengthFieldOffset  長度域的偏移量 在這里是0,表示無偏移
   *        the offset of the length field
   * @param lengthFieldLength  長度域長度 這里是4,表示長度域的長度為4
   *        the length of the length field
   */
  public LengthFieldBasedFrameDecoder(
          int maxFrameLength,
          int lengthFieldOffset, int lengthFieldLength) {
      this(maxFrameLength, lengthFieldOffset, lengthFieldLength, 0, 0);
  }

2. 基于長度的截?cái)嗖鸢?/h2>


度域被截掉,我們只需要指定另外一個(gè)參數(shù)就可以實(shí)現(xiàn),這個(gè)參數(shù)叫做initialBytesToStrip,表示netty拿到一個(gè)完整的數(shù)據(jù)包之后向業(yè)務(wù)解碼器傳遞之前,應(yīng)該跳過多少字節(jié)

new LengthFieldBasedFrameDecoder(Integer.MAX, 0, 4, 0, 4);
int maxFrameLength
int lengthFieldOffset
int lengthFieldLength
int lengthAdjustment
int initialBytesToStrip

3.基于偏移長度的拆包

下面這種方式二進(jìn)制協(xié)議是更為普遍的,前面幾個(gè)固定字節(jié)表示協(xié)議頭,通常包含一些magicNumber,protocol version 之類的meta信息,緊跟著后面的是一個(gè)長度域,表示包體有多少字節(jié)的數(shù)據(jù)



只需要基于第一種情況,調(diào)整第二個(gè)參數(shù)既可以實(shí)現(xiàn)

new LengthFieldBasedFrameDecoder(Integer.MAX, 4, 4);

lengthFieldOffset 是4,表示跳過4個(gè)字節(jié)之后的才是長度域

4.基于可調(diào)整長度的拆包


即長度域在前,header在后,這種情況又是如何來調(diào)整參數(shù)達(dá)到我們想要的拆包效果呢?

1.長度域在數(shù)據(jù)包最前面表示無偏移,lengthFieldOffset 為 0
2.長度域的長度為3,即lengthFieldLength為3
2.長度域表示的包體的長度略過了header,這里有另外一個(gè)參數(shù),叫做 lengthAdjustment,包體長度調(diào)整的大小,長度域的數(shù)值表示的長度加上這個(gè)修正值表示的就是帶header的包,這里是 12+2,header和包體一共占14個(gè)字節(jié)

new LengthFieldBasedFrameDecoder(Integer.MAX, 0, 3, 2, 0);

5.基于偏移可調(diào)整長度的截?cái)嗖鸢?/h2>

更變態(tài)一點(diǎn)的二進(jìn)制協(xié)議帶有兩個(gè)header,比如下面這種



拆完之后,HDR1 丟棄,長度域丟棄,只剩下第二個(gè)header和有效包體,這種協(xié)議中,一般HDR1可以表示magicNumber,表示應(yīng)用只接受以該magicNumber開頭的二進(jìn)制數(shù)據(jù),rpc里面用的比較多

我們?nèi)匀豢梢酝ㄟ^設(shè)置netty的參數(shù)實(shí)現(xiàn)

1.長度域偏移為1,那么 lengthFieldOffset為1
2.長度域長度為2,那么lengthFieldLength為2
3.長度域表示的包體的長度略過了HDR2,但是拆包的時(shí)候HDR2也被netty當(dāng)作是包體的的一部分來拆,HDR2的長度為1,那么lengthAdjustment 為1
4.拆完之后,截掉了前面三個(gè)字節(jié),那么initialBytesToStrip 為 3 (長度為2 + HDR2為1 = 3)

最后,代碼實(shí)現(xiàn)為

   new LengthFieldBasedFrameDecoder(Integer.MAX, 1, 2, 1, 3);

6.LengthFieldBasedFrameDecoder 源碼

    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        //discardingTooLongFrame 默認(rèn)初始化為false
        if (discardingTooLongFrame) {
            discardingTooLongFrame(in);
        }

        //in.readableBytes() 可以字節(jié)的長度
        //lengthFieldEndOffset 長度字段結(jié)束偏移量
        //如果當(dāng)前可讀的字節(jié)數(shù)< 長度域開始的字節(jié)數(shù) 返回等待, null表示返回等待
        if (in.readableBytes() < lengthFieldEndOffset) {
            return null;
        }

        // 拿到長度域的實(shí)際字節(jié)偏移
        int actualLengthFieldOffset = in.readerIndex() + lengthFieldOffset;
        //得到 未調(diào)整的數(shù)據(jù)幀長度 其實(shí)就是讀物數(shù)據(jù)域標(biāo)識(shí)的值,代表本次內(nèi)容的數(shù)據(jù)長度
        // 拿到實(shí)際的未調(diào)整過的包長度
        long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder);

        if (frameLength < 0) {
            failOnNegativeLengthField(in, frameLength, lengthFieldEndOffset);
        }

        //機(jī)上偏移量
        frameLength += lengthAdjustment + lengthFieldEndOffset;

        if (frameLength < lengthFieldEndOffset) {
            failOnFrameLengthLessThanLengthFieldEndOffset(in, frameLength, lengthFieldEndOffset);
        }

        if (frameLength > maxFrameLength) {
            exceededFrameLength(in, frameLength);
            return null;
        }

        // never overflows because it's less than maxFrameLength
        int frameLengthInt = (int) frameLength;
        //如果當(dāng)前可讀字節(jié) < 本次數(shù)據(jù)包的字節(jié)數(shù),則說明發(fā)生了粘包問題,返回 等待下一個(gè)包 然后讀取完成數(shù)組
        if (in.readableBytes() < frameLengthInt) {
            return null;
        }

        //需要丟棄的字節(jié)數(shù) > 當(dāng)前數(shù)據(jù)包的長度 拋出異常
        if (initialBytesToStrip > frameLengthInt) {
            failOnFrameLengthLessThanInitialBytesToStrip(in, frameLength, initialBytesToStrip);
        }
        //跳過需要丟棄的字節(jié)數(shù)
        in.skipBytes(initialBytesToStrip);

        // extract frame
        int readerIndex = in.readerIndex();
        //actualFrameLength 數(shù)據(jù)包有用數(shù)據(jù)的長度
        int actualFrameLength = frameLengthInt - initialBytesToStrip;
        //讀取數(shù)據(jù)
        ByteBuf frame = extractFrame(ctx, in, readerIndex, actualFrameLength);
        in.readerIndex(readerIndex + actualFrameLength);
        return frame;
    }
// 數(shù)據(jù)包長度超出最大包長度,進(jìn)入丟棄模式
    private void exceededFrameLength(ByteBuf in, long frameLength) {
        long discard = frameLength - in.readableBytes();
        tooLongFrameLength = frameLength;

        if (discard < 0) {
            // 當(dāng)前可讀字節(jié)已達(dá)到frameLength,直接跳過frameLength個(gè)字節(jié),丟棄之后,后面有可能就是一個(gè)合法的數(shù)據(jù)包
            // buffer contains more bytes then the frameLength so we can discard all now
            in.skipBytes((int) frameLength);
        } else {
            // 當(dāng)前可讀字節(jié)未達(dá)到frameLength,說明后面未讀到的字節(jié)也需要丟棄,進(jìn)入丟棄模式,先把當(dāng)前累積的字節(jié)全部丟棄
            // Enter the discard mode and discard everything received so far.
            discardingTooLongFrame = true;
            // bytesToDiscard表示下次還需要丟棄多少字節(jié)
            bytesToDiscard = discard;
            in.skipBytes(in.readableBytes());
        }
        failIfNecessary(true);
    }

http://www.lxweimin.com/p/a0a51fd79f62
https://blog.csdn.net/e_wsq/article/details/77854547
https://www.cnblogs.com/651434092qq/p/11067528.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容