Netty提供的粘包拆包解决方案(三)

2021SC@SDUSC


DelimiterBasedFrameDecoder

DelimiterBasedFrameDecoder是基于分隔符的解码器,根据用户自己指定的分割符来进行解码,并且可以同时指定多个分割符,只要在读取数据时,碰到了其中任意一个分隔符,就可以进行一次解码

需要注意的是:如果只指定两个分隔符,并且是\r\n 和\n 时,基于分隔符的解码器就变成了一个基于行的解码器,那么在解码时,就直接使用行分割器 LineBaseFrameDecoder 解码

DelimiterBasedFrameDecoder定义了如下几个成员变量:

// 分隔符数组,因为可以同时制定过个分隔符,所以使用数组来存放
private final ByteBuf[] delimiters;

// 最大长度限制
private final int maxFrameLength;

// 是否跳过分隔符,true表示跳过
private final boolean stripDelimiter;

// 是否立即丢弃,true:立即
private final boolean failFast;

// 是否处于丢弃模式
private boolean discardingTooLongFrame;

// 累计丢弃的字节数
private int tooLongFrameLength;

// 如果分隔符是\r\n和\n时,就直接使用基于行的解码器
private final LineBasedFrameDecoder lineBasedDecoder;

注意到,该解码器比 LineBaseFrameDecoder 解码器多了两个成员变量,一个是 delimiters 属性,这是一个数组,用来存放用户自定义的分隔符,因为用户可以自定义多个分隔符,所以使用数组来存放。另一个是 lineBasedDecoder 属性,这个属性表示的是基于行的解码器,delimiters 数组中的分隔符当且仅当为 \r\n 和 \n 时,分隔符解码器就变成了行解码器,该属性的值在LineBaseFrameDecoder构造方法中被初始化:

public DelimiterBasedFrameDecoder(
        int maxFrameLength, boolean stripDelimiter, boolean failFast, ByteBuf... delimiters) {

    ...
    if (isLineBased(delimiters) && !isSubclass()) {
        lineBasedDecoder = new LineBasedFrameDecoder(maxFrameLength, stripDelimiter, failFast);
        this.delimiters = null;
    } else {
        ...
        lineBasedDecoder = null;
    }
    ...
}

在构造方法中,会通过 isLineBased(delimiters) 方法来判断分隔符是否是 \r\n 和 \n,如果是,就创建一个行解码器,然后赋值给 lineBasedDecoder 属性;否则就令 lineBasedDecoder 属性为空:

private static boolean isLineBased(final ByteBuf[] delimiters) {
    // 当分隔符是 \r\n 和 \n 时,才返回 true,也就是将分隔符解码器变成基于行的解码器
    if (delimiters.length != 2) {
        return false;
    }

    ByteBuf a = delimiters[0];
    ByteBuf b = delimiters[1];
    // 保证令a = \r\n,令b= \n
    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';
}

与前面介绍的解码器一样,分隔符解码器也重写了父类中的抽象方法 decode():

protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
    // 调用decode()的重载方法解码
    Object decoded = decode(ctx, in);
    // 如果能成功解码,就添加到out中
    if (decoded != null) {
        out.add(decoded);
    }
}

同样,核心是decode(ctx, in); 重点看一下这个函数(整体框架如下):

protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
    // 首先判断基于行的解码器是否被初始化了,如果被初始化了,就表明分隔符就是\r\n和\n,直接只用行解码器进行解码
    if (lineBasedDecoder != null) {
        return lineBasedDecoder.decode(ctx, buffer);
    }
    int minFrameLength = Integer.MAX_VALUE;
    ByteBuf minDelim = null;
    // 遍历所有的分隔符,然后找到最小位置的分割符
    for (ByteBuf delim: delimiters) {
        // 找到最小分隔符的位置
    }
    // 如果找到了分割符
    if (minDelim != null) {

        //如果处于丢弃模式
        if (discardingTooLongFrame) {

            return null;
        }else{
            return frame;
        }
    } else {
        //如果没有找到分割符
        //判断是否处于非丢弃模式
        if (!discardingTooLongFrame) {

        } else {

        }
        return null;
    }
}

首先判断 lineBasedDecoder 是否为空,如果不为空,就表示分隔符是 \r\n 和 \n,那就直接使用行解码器解码;否则进入后面的逻辑

由于可以指定多个分隔符,所以需要找到最先出现的分隔符是哪一个,以及它的位置,查找方法是遍历每一个分隔符,然后分别找到它们在可读数据中的索引,分隔符的索引最小的就是我们要找的分隔符

后面的逻辑与行解码器基本是一样的,也是先分两种情况:找到分隔符以及没有找到分隔符。然后对于前面每一种情况,再分为是否处于丢弃模式。注意与行解码器不同的是,行解码器先是判断是否处于丢弃模式,再判断是否找到了分隔符。同样是只有找到了分隔符,且解码器不处于丢弃模式,才能解码出数据,否则将返回 null。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值