<2021SC@SDUSC>netty常见编解码器(四)

2021SC@SDUSC

前言

在这一篇博客中,将会介绍netty的LengthFieldBasedFrameDecoder类,通过自定义长度解决TCP粘包拆包问题。

一、LengthFieldBasedFrameDecoder类

package io.netty.handler.codec;

import static io.netty.util.internal.ObjectUtil.checkNotNull;
import static io.netty.util.internal.ObjectUtil.checkPositive;
import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;

import java.nio.ByteOrder;
import java.util.List;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;

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 LengthFieldBasedFrameDecoder(
            int maxFrameLength,
            int lengthFieldOffset, int lengthFieldLength) {
        this(maxFrameLength, lengthFieldOffset, lengthFieldLength, 0, 0);
    }

    public LengthFieldBasedFrameDecoder(
            int maxFrameLength,
            int lengthFieldOffset, int lengthFieldLength,
            int lengthAdjustment, int initialBytesToStrip) {
        this(
                maxFrameLength,
                lengthFieldOffset, lengthFieldLength, lengthAdjustment,
                initialBytesToStrip, true);
    }

    public LengthFieldBasedFrameDecoder(
            int maxFrameLength, int lengthFieldOffset, int lengthFieldLength,
            int lengthAdjustment, int initialBytesToStrip, boolean failFast) {
        this(
                ByteOrder.BIG_ENDIAN, maxFrameLength, lengthFieldOffset, lengthFieldLength,
                lengthAdjustment, initialBytesToStrip, failFast);
    }

    public LengthFieldBasedFrameDecoder(
            ByteOrder byteOrder, int maxFrameLength, int lengthFieldOffset, int lengthFieldLength,
            int lengthAdjustment, int initialBytesToStrip, boolean failFast) {

        this.byteOrder = checkNotNull(byteOrder, "byteOrder");

        checkPositive(maxFrameLength, "maxFrameLength");

        checkPositiveOrZero(lengthFieldOffset, "lengthFieldOffset");

        checkPositiveOrZero(initialBytesToStrip, "initialBytesToStrip");

        if (lengthFieldOffset > maxFrameLength - lengthFieldLength) {
            throw new IllegalArgumentException(
                    "maxFrameLength (" + maxFrameLength + ") " +
                    "must be equal to or greater than " +
                    "lengthFieldOffset (" + lengthFieldOffset + ") + " +
                    "lengthFieldLength (" + lengthFieldLength + ").");
        }

        this.maxFrameLength = maxFrameLength;
        this.lengthFieldOffset = lengthFieldOffset;
        this.lengthFieldLength = lengthFieldLength;
        this.lengthAdjustment = lengthAdjustment;
        this.lengthFieldEndOffset = lengthFieldOffset + lengthFieldLength;
        this.initialBytesToStrip = initialBytesToStrip;
        this.failFast = failFast;
    }

    @Override
    protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        Object decoded = decode(ctx, in);
        if (decoded != null) {
            out.add(decoded);
        }
    }

    private void discardingTooLongFrame(ByteBuf in) {
        long bytesToDiscard = this.bytesToDiscard;
        int localBytesToDiscard = (int) Math.min(bytesToDiscard, in.readableBytes());
        in.skipBytes(localBytesToDiscard);
        bytesToDiscard -= localBytesToDiscard;
        this.bytesToDiscard = bytesToDiscard;

        failIfNecessary(false);
    }

    private static void failOnNegativeLengthField(ByteBuf in, long frameLength, int lengthFieldEndOffset) {
        in.skipBytes(lengthFieldEndOffset);
        throw new CorruptedFrameException(
           "negative pre-adjustment length field: " + frameLength);
    }

    private static void failOnFrameLengthLessThanLengthFieldEndOffset(ByteBuf in,
                                                                      long frameLength,
                                                                      int lengthFieldEndOffset) {
        in.skipBytes(lengthFieldEndOffset);
        throw new CorruptedFrameException(
           "Adjusted frame length (" + frameLength + ") is less " +
              "than lengthFieldEndOffset: " + lengthFieldEndOffset);
    }

    private void exceededFrameLength(ByteBuf in, long frameLength) {
        long discard = frameLength - in.readableBytes();
        tooLongFrameLength = frameLength;

        if (discard < 0) {
            // buffer contains more bytes then the frameLength so we can discard all now
            in.skipBytes((int) frameLength);
        } else {
            // Enter the discard mode and discard everything received so far.
            discardingTooLongFrame = true;
            bytesToDiscard = discard;
            in.skipBytes(in.readableBytes());
        }
        failIfNecessary(true);
    }

    private static void failOnFrameLengthLessThanInitialBytesToStrip(ByteBuf in,
                                                                     long frameLength,
                                                                     int initialBytesToStrip) {
        in.skipBytes((int) frameLength);
        throw new CorruptedFrameException(
           "Adjusted frame length (" + frameLength + ") is less " +
              "than initialBytesToStrip: " + initialBytesToStrip);
    }

    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        if (discardingTooLongFrame) {
            discardingTooLongFrame(in);
        }

        if (in.readableBytes() < lengthFieldEndOffset) {
            return null;
        }

        int actualLengthFieldOffset = in.readerIndex() + lengthFieldOffset;
        long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder);

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

        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;
        if (in.readableBytes() < frameLengthInt) {
            return null;
        }

        if (initialBytesToStrip > frameLengthInt) {
            failOnFrameLengthLessThanInitialBytesToStrip(in, frameLength, initialBytesToStrip);
        }
        in.skipBytes(initialBytesToStrip);

        // extract frame
        int readerIndex = in.readerIndex();
        int actualFrameLength = frameLengthInt - initialBytesToStrip;
        ByteBuf frame = extractFrame(ctx, in, readerIndex, actualFrameLength);
        in.readerIndex(readerIndex + actualFrameLength);
        return frame;
    }

    protected long getUnadjustedFrameLength(ByteBuf buf, int offset, int length, ByteOrder order) {
        buf = buf.order(order);
        long frameLength;
        switch (length) {
        case 1:
            frameLength = buf.getUnsignedByte(offset);
            break;
        case 2:
            frameLength = buf.getUnsignedShort(offset);
            break;
        case 3:
            frameLength = buf.getUnsignedMedium(offset);
            break;
        case 4:
            frameLength = buf.getUnsignedInt(offset);
            break;
        case 8:
            frameLength = buf.getLong(offset);
            break;
        default:
            throw new DecoderException(
                    "unsupported lengthFieldLength: " + lengthFieldLength + " (expected: 1, 2, 3, 4, or 8)");
        }
        return frameLength;
    }

    private void failIfNecessary(boolean firstDetectionOfTooLongFrame) {
        if (bytesToDiscard == 0) {
            // Reset to the initial state and tell the handlers that
            // the frame was too large.
            long tooLongFrameLength = this.tooLongFrameLength;
            this.tooLongFrameLength = 0;
            discardingTooLongFrame = false;
            if (!failFast || firstDetectionOfTooLongFrame) {
                fail(tooLongFrameLength);
            }
        } else {
            // Keep discarding and notify handlers if necessary.
            if (failFast && firstDetectionOfTooLongFrame) {
                fail(tooLongFrameLength);
            }
        }
    }

    protected ByteBuf extractFrame(ChannelHandlerContext ctx, ByteBuf buffer, int index, int length) {
        return buffer.retainedSlice(index, length);
    }

    private void fail(long frameLength) {
        if (frameLength > 0) {
            throw new TooLongFrameException(
                            "Adjusted frame length exceeds " + maxFrameLength +
                            ": " + frameLength + " - discarded");
        } else {
            throw new TooLongFrameException(
                            "Adjusted frame length exceeds " + maxFrameLength +
                            " - discarding");
        }
    }
}

二、属性及构造函数

首先介绍一下LengthFieldBasedFrameDecoder类中的属性。

  • byteOrder:数据采用大端法还是小端法。
  • maxFrameLength:这是数据的最大长度。
  • lengthFieldOffset:长度域位于数据中的偏移量。
  • lengthFieldLength:长度域的长度。
  • lengthFieldEndOffset:在构造函数中可以看到this.lengthFieldEndOffset = lengthFieldOffset + lengthFieldLength;,实际指向数据中位于长度之后的下一个字节的偏移。
  • lengthAdjustment:文档中给出的解释是,加到长度域的惩罚值。之后会解释。
  • initialBytesToStrip:在解析数据时,第一次从数据中去掉的字节数,一般地,由于长度域在解码后就没有什么作用了,会丢弃长度域。
  • failFast:一个布尔变量,当它是true时,当解码器发现数据超出maxFrameLength时,会立刻抛出TooLongFrameExecption异常,反之,会在解析完整个数据后,才抛出异常。
  • discardingTooLongFrame:在decode中使用,一个布尔值。
  • tooLongFrameLength:在decode中使用。
  • bytesToDiscard:在decode中使用,表示抛弃掉的字节数。
    在LengthFieldBasedFrameDecoder中,重载了许多构造函数。与之前的许多解码器一样,实际上为这些值设置了默认值,方便用户使用。

三、例子

在这一节中,会就长度域的使用来解释一些参数的具体使用方式。其实就是翻译一下文档。
(1)例一:
lengthFieldOffset = 0
lengthFieldLength = 2
lengthAdjustment = 0
initialBytesToStrip = 0 (= do not strip header)

BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes)
±----------±------------------------+ ±----------±------------------------+
| Length | Actual Content |----->| Length | Actual Content |
| 0x000C | “HELLO, WORLD” | | 0x000C | “HELLO, WORLD” |
±----------±------------------------+ ±----------±-------------------------+
最简单的情况,如之前所说,lengthFieldOffset=0是长度域开始的下标,lengthFieldLength=2表示长度域的大小是2个字节。

(2)例二:
lengthFieldOffset = 0
lengthFieldLength = 2
lengthAdjustment = 0
initialBytesToStrip = 2 (= the length of the Length field)

BEFORE DECODE (14 bytes) AFTER DECODE (12 bytes)
±-------±---------------+ ±---------------+
| Length | Actual Content |----->| Actual Content |
| 0x000C | “HELLO, WORLD” | | “HELLO, WORLD” |
±-------±---------------+ ±---------------+
在这里,区别于上一个例子的是initialBytesToStrip=2,在解析时抛弃掉长度域,实际上2就是长度域的字节。

(3)例三:
lengthFieldOffset = 0
lengthFieldLength = 2
lengthAdjustment = -2 (= the length of the Length field)
initialBytesToStrip = 0

BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes)
±-------±---------------+ ±-------±---------------+
| Length | Actual Content |----->| Length | Actual Content |
| 0x000E | “HELLO, WORLD” | | 0x000E | “HELLO, WORLD” |
±-------±---------------+ ±-------±---------------+
在这里,区别于例一的是lengthAdjustment=-2,首先这里长度域中的值是0x000E,等于十进制的14,实际场景中,一些协议会将长度域的字节数计入数据长度,所以,添加惩罚值,14+(-2)=12。

(四)例四:
lengthFieldOffset = 2 (= the length of Header 1)
lengthFieldLength = 3
lengthAdjustment = 0
initialBytesToStrip = 0

BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes)
±---------±---------±---------------+ ±---------±---------±---------------+
| Header 1 | Length | Actual Content |----->| Header 1 | Length | Actual Content |
| 0xCAFE | 0x00000C | “HELLO, WORLD” | | 0xCAFE | 0x00000C | “HELLO, WORLD” |
±---------±---------±---------------+ ±---------±---------±---------------+
在长度域之前添加头信息。实际上,在计算机网络中,大多数协议都需要头部信息。lengthFieldLength=3是长度域长度。lengthFieldOffset=2表示长度域的开始下标是2,也就是第三个字节,注意到,头部的长度也是2。

(五)例五:
lengthFieldOffset = 0
lengthFieldLength = 3
lengthAdjustment = 2 (= the length of Header 1)
initialBytesToStrip = 0

BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes)
±---------±---------±---------------+ ±---------±---------±---------------+
| Length | Header 1 | Actual Content |----->| Length | Header 1 | Actual Content |
| 0x00000C | 0xCAFE | “HELLO, WORLD” | | 0x00000C | 0xCAFE | “HELLO, WORLD” |
±---------±---------±---------------+ ±---------±---------±---------------+
在本例中,头部域不再是出现在长度域之前,而是出现在长度域之后,数据域之前。首先,长度域字节为3,长度为0x000000C,等于12。通过增加惩罚项12+2=14,这样解码器将额外的头部字节计入了要解析的字节数。

(六)例六:
lengthFieldOffset = 1 (= the length of HDR1)
lengthFieldLength = 2
lengthAdjustment = 1 (= the length of HDR2)
initialBytesToStrip = 3 (= the length of HDR1 + LEN)

BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes)
±-----±-------±-----±---------------+ ±-----±---------------+
| HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
| 0xCA | 0x000C | 0xFE | “HELLO, WORLD” | | 0xFE | “HELLO, WORLD” |
±-----±-------±-----±---------------+ ±-----±---------------+
在这一例中,有两个头部分别出现在长度域之前和之后,实际上就是将之前的两个例子结合起来使用。最后会抛弃前三个字节,即HDR1和Length。

(七)例七:
lengthFieldOffset = 1
lengthFieldLength = 2
lengthAdjustment = -3 (= the length of HDR1 + LEN, negative)
initialBytesToStrip = 3

BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes)
±-----±-------±-----±---------------+ ±-----±---------------+
| HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
| 0xCA | 0x0010 | 0xFE | “HELLO, WORLD” | | 0xFE | “HELLO, WORLD” |
±-----±-------±-----±---------------+ ±-----±---------------+
这里区别于前一个例子的地方在于,lengthAdjustment=-3,实际上就是将HDR1和Length域的长度计入,这里将其减去,方便之后解码器解析数据。

四、decode方法

与之前的几个解码器一样,有两个decode方法,这里会简单分析protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception,关于这里的一些判断就不分析了。
这里首先回去判断discardingTooLongFrame这个标识是不是true,如果是的话,执行discardingTooLongFrame(in);;否则,跳过。而在这个方法中,首先找到缓冲区的可读字节和bytesToDiscard的最小值,之后跳过这些字节,然后去更新bytesToDiscard
之后是从缓冲区找到长度域的开始字节。然后,通过getUnadjustedFrameLength方法从缓冲区中读出数据的长度。默认长度域的字节为1/2/3/4/8,如果不是的话,抛出异常,可以通过继承来扩充功能。
之后,就是根据协议计算实际的数据长度,原因就在于获取的frameLength可能包含了头部字节、长度域字节等,如之前例子所说的,这里会去获得真正要读取的字节数。
在跳过需要抛弃的字节数initialBytesToStrip后,从缓冲区获取数据,返回。

五、总结

在本篇博客中,分析了LengthFieldBasedFrameDecoder类,包括它的属性、构造函数、使用场景分析,最后,分析了它解析数据的逻辑。LengthFieldBasedFrameDecoder能够根据自定义字节数来解决TCP的粘包半包问题,而且处理灵活,相对其它解码器更加常用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东羚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值