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

2021SC@SDUSC

前言

在这一篇博客中,将会介绍netty的DelimiterBasedFrameDecoder类,以特殊字符作为结束消息的结束符,用以解决粘包拆包问题。

一、DelimiterBasedFrameDecoder.java

package io.netty.handler.codec;

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

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.internal.ObjectUtil;

import java.util.List;

public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {

    private final ByteBuf[] delimiters;
    private final int maxFrameLength;
    private final boolean stripDelimiter;
    private final boolean failFast;
    private boolean discardingTooLongFrame;
    private int tooLongFrameLength;
    
    private final LineBasedFrameDecoder lineBasedDecoder;

    public DelimiterBasedFrameDecoder(int maxFrameLength, ByteBuf delimiter) {
        this(maxFrameLength, true, delimiter);
    }

    public DelimiterBasedFrameDecoder(
            int maxFrameLength, boolean stripDelimiter, ByteBuf delimiter) {
        this(maxFrameLength, stripDelimiter, true, delimiter);
    }

    public DelimiterBasedFrameDecoder(
            int maxFrameLength, boolean stripDelimiter, boolean failFast,
            ByteBuf delimiter) {
        this(maxFrameLength, stripDelimiter, failFast, new ByteBuf[] {
                delimiter.slice(delimiter.readerIndex(), delimiter.readableBytes())});
    }

    public DelimiterBasedFrameDecoder(int maxFrameLength, ByteBuf... delimiters) {
        this(maxFrameLength, true, delimiters);
    }

    public DelimiterBasedFrameDecoder(
            int maxFrameLength, boolean stripDelimiter, ByteBuf... delimiters) {
        this(maxFrameLength, stripDelimiter, true, delimiters);
    }

    public DelimiterBasedFrameDecoder(
            int maxFrameLength, boolean stripDelimiter, boolean failFast, ByteBuf... delimiters) {
        validateMaxFrameLength(maxFrameLength);
        ObjectUtil.checkNonEmpty(delimiters, "delimiters");

        if (isLineBased(delimiters) && !isSubclass()) {
            lineBasedDecoder = new LineBasedFrameDecoder(maxFrameLength, stripDelimiter, failFast);
            this.delimiters = null;
        } else {
            this.delimiters = new ByteBuf[delimiters.length];
            for (int i = 0; i < delimiters.length; i ++) {
                ByteBuf d = delimiters[i];
                validateDelimiter(d);
                this.delimiters[i] = d.slice(d.readerIndex(), d.readableBytes());
            }
            lineBasedDecoder = null;
        }
        this.maxFrameLength = maxFrameLength;
        this.stripDelimiter = stripDelimiter;
        this.failFast = failFast;
    }

    private static boolean isLineBased(final ByteBuf[] delimiters) {
        if (delimiters.length != 2) {
            return false;
        }
        ByteBuf a = delimiters[0];
        ByteBuf b = delimiters[1];
        if (a.capacity() < b.capacity()) {
            a = delimiters[1];
            b = delimiters[0];
        }
        return a.capacity() == 2 && b.capacity() == 1
                && a.getByte(0) == '\r' && a.getByte(1) == '\n'
                && b.getByte(0) == '\n';
    }

    private boolean isSubclass() {
        return getClass() != DelimiterBasedFrameDecoder.class;
    }

    @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);
        }
    }

    protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
        if (lineBasedDecoder != null) {
            return lineBasedDecoder.decode(ctx, buffer);
        }
        // Try all delimiters and choose the delimiter which yields the shortest frame.
        int minFrameLength = Integer.MAX_VALUE;
        ByteBuf minDelim = null;
        for (ByteBuf delim: delimiters) {
            int frameLength = indexOf(buffer, delim);
            if (frameLength >= 0 && frameLength < minFrameLength) {
                minFrameLength = frameLength;
                minDelim = delim;
            }
        }

        if (minDelim != null) {
            int minDelimLength = minDelim.capacity();
            ByteBuf frame;

            if (discardingTooLongFrame) {
                // We've just finished discarding a very large frame.
                // Go back to the initial state.
                discardingTooLongFrame = false;
                buffer.skipBytes(minFrameLength + minDelimLength);

                int tooLongFrameLength = this.tooLongFrameLength;
                this.tooLongFrameLength = 0;
                if (!failFast) {
                    fail(tooLongFrameLength);
                }
                return null;
            }

            if (minFrameLength > maxFrameLength) {
                // Discard read frame.
                buffer.skipBytes(minFrameLength + minDelimLength);
                fail(minFrameLength);
                return null;
            }

            if (stripDelimiter) {
                frame = buffer.readRetainedSlice(minFrameLength);
                buffer.skipBytes(minDelimLength);
            } else {
                frame = buffer.readRetainedSlice(minFrameLength + minDelimLength);
            }

            return frame;
        } else {
            if (!discardingTooLongFrame) {
                if (buffer.readableBytes() > maxFrameLength) {
                    // Discard the content of the buffer until a delimiter is found.
                    tooLongFrameLength = buffer.readableBytes();
                    buffer.skipBytes(buffer.readableBytes());
                    discardingTooLongFrame = true;
                    if (failFast) {
                        fail(tooLongFrameLength);
                    }
                }
            } else {
                // Still discarding the buffer since a delimiter is not found.
                tooLongFrameLength += buffer.readableBytes();
                buffer.skipBytes(buffer.readableBytes());
            }
            return null;
        }
    }

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

    private static int indexOf(ByteBuf haystack, ByteBuf needle) {
        for (int i = haystack.readerIndex(); i < haystack.writerIndex(); i ++) {
            int haystackIndex = i;
            int needleIndex;
            for (needleIndex = 0; needleIndex < needle.capacity(); needleIndex ++) {
                if (haystack.getByte(haystackIndex) != needle.getByte(needleIndex)) {
                    break;
                } else {
                    haystackIndex ++;
                    if (haystackIndex == haystack.writerIndex() &&
                        needleIndex != needle.capacity() - 1) {
                        return -1;
                    }
                }
            }

            if (needleIndex == needle.capacity()) {
                // Found the needle from the haystack!
                return i - haystack.readerIndex();
            }
        }
        return -1;
    }

    private static void validateDelimiter(ByteBuf delimiter) {
        ObjectUtil.checkNotNull(delimiter, "delimiter");
        if (!delimiter.isReadable()) {
            throw new IllegalArgumentException("empty delimiter");
        }
    }

    private static void validateMaxFrameLength(int maxFrameLength) {
        checkPositive(maxFrameLength, "maxFrameLength");
    }
}

二、属性

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

  • maxFrameLength:这是数据的最大长度,超出这个限制,将会抛出TooLongFrameExecption异常。
  • stripDelimiter:一个布尔变量,当变量为true时,解析数据会去除分隔符,反之不会。
  • failFast:一个布尔变量,当它是true时,当解码器发现数据超出maxFrameLength时,会立刻抛出TooLongFrameExecption异常,反之,会在解析完整个数据后,才抛出异常。
  • delimiters:类型是ByteBuf数组,指定多个分隔符,但解析数据时,选择划分方式使得数据最短。比如,数据是’ABC\nDEF\r\n’,根据换行符划分数据,那么数据会被划分成’ABC’和’DEF’,而不是 ‘ABC\nDEF’。
  • discardingTooLongFrame
  • tooLongFrameLength
  • lineBasedDecoder:LineBasedDecoder类型的变量,被final修饰,仅当分隔符为换行符时才会被设置。
    在这些变量中,除了discardingTooLongFramediscardingTooLongFrame以外,其它变量都在构造方法中完成了初始化,虽然DelimiterBasedFrameDecoder提供了六个构造方法,但最终都会走到public DelimiterBasedFrameDecoder( int maxFrameLength, boolean stripDelimiter, boolean failFast, ByteBuf... delimiters)方法中。
    在该构造方法中,首先对传入的参数进行验证,然后,判断是否应该使用LineBasedDecoder类,判断条件是isLineBased(delimiters) && !isSubclass(),与的左边是判断传入的delimiters是否是 ‘\n’和’\r\n’,与的右边是判断调用该方法的类是否不是DelimiterBasedFrameDecoder类的子类,具体原因我不是很清楚,但可能是考虑到子类在扩展时,需要实现一些自定义的操作,所以在这里,不采用LineBasedDecoder。之后,就是对其它变量赋值。

三、decode

与之前分析的其它解码器一样,有两个decode方法,其中,一个decode用于真正处理解析逻辑。
在这个decode方法中,首先判断lineBasedDecoder变量是否为null,如果不是,说明指定的分隔符是换行符,调用lineBasedDecoder来处理。这里不做分析。之后,先获取分割后最短的数据长度,以及对应的分隔符minDelim
根据minDelim是否为null判断,这里假设它是null,即没有读到换行符,或者说,数据并不完整,还有数据需要在之后的数据包中获得,首先判断discardingTooLongFrame是否为true,如果不是,意味着还在读数据,判断读取的数据是否大于maxFrameLength,如果大于,抛弃这些数据,并且将discardingTooLongFrame设置为true,否则,什么也不做。接着,如果discardingTooLongFrame为true,意味着之前的数据已经超过了长度但由于当时没有分隔符,所以在这个数据中也是需要抛弃的。总之,当minDelimnull时,返回null,或者说,不返回数据。
接着,假设minDelim不是null,也就是在这个数据中读取到了分隔符,进入另外一条分支,首先判断discardingTooLongFrame是否为true,如果是,这意味着之前的数据被抛弃了,但由于没有读到分隔符,没有完全抛弃,所以这一部分数据也应该被抛弃,跳过数据,tooLongFrameLengthdiscardingTooLongFrame置回初始值,返回null。否则,接着往下执行,如果minFrameLength大于maxFrameLength,则这个数据被丢弃,否则,读取数据并返回。

四、总结

在本篇博客中,分析了DelimiterBasedFrameDecoder类,该解析器能够指定多个分隔符并且根据分隔符分割数据,我认为,这种解析器在实际使用时,能提供更高的灵活性。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

东羚

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

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

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

打赏作者

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

抵扣说明:

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

余额充值