欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Netty源码分析 (十)----- 拆包器之LineBasedFrameDecoder

程序员文章站 2023-04-05 16:07:03
Netty 自带多个粘包拆包解码器。今天介绍 LineBasedFrameDecoder,换行符解码器。 行拆包器 下面,以一个具体的例子来看看业netty自带的拆包器是如何来拆包的 这个类叫做 LineBasedFrameDecoder,基于行分隔符的拆包器,TA可以同时处理 \n以及\r\n两种 ......

netty 自带多个粘包拆包解码器。今天介绍 linebasedframedecoder,换行符解码器。

行拆包器

下面,以一个具体的例子来看看业netty自带的拆包器是如何来拆包的

这个类叫做 linebasedframedecoder,基于行分隔符的拆包器,ta可以同时处理 \n以及\r\n两种类型的行分隔符,核心方法都在继承的 decode 方法中

protected final void decode(channelhandlercontext ctx, bytebuf in, list<object> out) throws exception {
    object decoded = decode(ctx, in);
    if (decoded != null) {
        out.add(decoded);
    }
}

netty 中自带的拆包器都是如上这种模板,我们来看看decode(ctx, in);

protected object decode(channelhandlercontext ctx, bytebuf buffer) throws exception {
    int eol = findendofline(buffer);
    int length;
    int length;
    if (!this.discarding) {
        if (eol >= 0) {
            length = eol - buffer.readerindex();
            int delimlength = buffer.getbyte(eol) == '\r' ? 2 : 1;
            if (length > this.maxlength) {
                buffer.readerindex(eol + delimlength);
                this.fail(ctx, length);
                return null;
            } else {
                bytebuf frame;
                if (this.stripdelimiter) {
                    frame = buffer.readretainedslice(length);
                    buffer.skipbytes(delimlength);
                } else {
                    frame = buffer.readretainedslice(length + delimlength);
                }

                return frame;
            }
        } else {
            length = buffer.readablebytes();
            if (length > this.maxlength) {
                this.discardedbytes = length;
                buffer.readerindex(buffer.writerindex());
                this.discarding = true;
                if (this.failfast) {
                    this.fail(ctx, "over " + this.discardedbytes);
                }
            }

            return null;
        }
    } else {
        if (eol >= 0) {
            length = this.discardedbytes + eol - buffer.readerindex();
            length = buffer.getbyte(eol) == '\r' ? 2 : 1;
            buffer.readerindex(eol + length);
            this.discardedbytes = 0;
            this.discarding = false;
            if (!this.failfast) {
                this.fail(ctx, length);
            }
        } else {
            this.discardedbytes += buffer.readablebytes();
            buffer.readerindex(buffer.writerindex());
        }

        return null;
    }
}

byteprocessor find_lf = new indexofprocessor((byte) '\n');

private static int findendofline(bytebuf buffer) {
    int i = buffer.foreachbyte(byteprocessor.find_lf);
    if (i > 0 && buffer.getbyte(i - 1) == '\r') {
        --i;
    }

    return i;
}

找到换行符位置

final int eol = findendofline(buffer);

private static int findendofline(final bytebuf buffer) {
    int i = buffer.foreachbyte(byteprocessor.find_lf);
    if (i > 0 && buffer.getbyte(i - 1) == '\r') {
        i--;
    }
    return i;
}

byteprocessor find_lf = new indexofprocessor((byte) '\n');

for循环遍历,找到第一个 \n 的位置,如果\n前面的字符为\r,那就返回\r的位置

非discarding模式的处理

接下来,netty会判断,当前拆包是否属于丢弃模式,用一个成员变量来标识

private boolean discarding;

第一次拆包不在discarding模式

非discarding模式下找到行分隔符的处理

// 1.计算分隔符和包长度
final bytebuf frame;
final int length = eol - buffer.readerindex();
final int delimlength = buffer.getbyte(eol) == '\r'? 2 : 1;

// 丢弃异常数据
if (length > maxlength) {
    buffer.readerindex(eol + delimlength);
    fail(ctx, length);
    return null;
}

// 取包的时候是否包括分隔符
if (stripdelimiter) {
    frame = buffer.readretainedslice(length);
    buffer.skipbytes(delimlength);
} else {
    frame = buffer.readretainedslice(length + delimlength);
}
return frame;

1.首先,新建一个帧,计算一下当前包的长度和分隔符的长度(因为有两种分隔符)
2.然后判断一下需要拆包的长度是否大于该拆包器允许的最大长度(maxlength),这个参数在构造函数中被传递进来,如超出允许的最大长度,就将这段数据抛弃,返回null
3.最后,将一个完整的数据包取出,如果构造本解包器的时候指定 stripdelimiter为false,即解析出来的包包含分隔符,默认为不包含分隔符

非discarding模式下未找到分隔符的处理

没有找到对应的行分隔符,说明字节容器没有足够的数据拼接成一个完整的业务数据包,进入如下流程处理

final int length = buffer.readablebytes();
if (length > maxlength) {
    discardedbytes = length;
    buffer.readerindex(buffer.writerindex());
    discarding = true;
    if (failfast) {
        fail(ctx, "over " + discardedbytes);
    }
}
return null;

首先取得当前字节容器的可读字节个数,接着,判断一下是否已经超过可允许的最大长度,如果没有超过,直接返回null,字节容器中的数据没有任何改变,否则,就需要进入丢弃模式

使用一个成员变量 discardedbytes 来表示已经丢弃了多少数据,然后将字节容器的读指针移到写指针,意味着丢弃这一部分数据,设置成员变量discarding为true表示当前处于丢弃模式。如果设置了failfast,那么直接抛出异常,默认情况下failfast为false,即安静得丢弃数据

discarding模式

如果解包的时候处在discarding模式,也会有两种情况发生

discarding模式下找到行分隔符

在discarding模式下,如果找到分隔符,那可以将分隔符之前的都丢弃掉

final int length = discardedbytes + eol - buffer.readerindex();
final int delimlength = buffer.getbyte(eol) == '\r'? 2 : 1;
buffer.readerindex(eol + delimlength);
discardedbytes = 0;
discarding = false;
if (!failfast) {
    fail(ctx, length);
}

计算出分隔符的长度之后,直接把分隔符之前的数据全部丢弃,当然丢弃的字符也包括分隔符,经过这么一次丢弃,后面就有可能是正常的数据包,下一次解包的时候就会进入正常的解包流程

discarding模式下未找到行分隔符

这种情况比较简单,因为当前还在丢弃模式,没有找到行分隔符意味着当前一个完整的数据包还没丢弃完,当前读取的数据是丢弃的一部分,所以直接丢弃

discardedbytes += buffer.readablebytes();
buffer.readerindex(buffer.writerindex());

特定分隔符拆包

这个类叫做 delimiterbasedframedecoder,可以传递给ta一个分隔符列表,数据包会按照分隔符列表进行拆分,读者可以完全根据行拆包器的思路去分析这个delimiterbasedframedecoder