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


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

    行拆包器

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

    这个类叫做 LineBasedFrameDecoder,基于行分隔符的拆包器,TA可以同时处理  以及 两种类型的行分隔符,核心方法都在继承的 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) == '
    ' ? 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) == '
    ' ? 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) '
    ');
    
    private static int findEndOfLine(ByteBuf buffer) {
        int i = buffer.forEachByte(ByteProcessor.FIND_LF);
        if (i > 0 && buffer.getByte(i - 1) == '
    ') {
            --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) == '
    ') {
            i--;
        }
        return i;
    }
    
    ByteProcessor FIND_LF = new IndexOfProcessor((byte) '
    ');

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

    非discarding模式的处理

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

    private boolean discarding;

    第一次拆包不在discarding模式

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

    // 1.计算分隔符和包长度
    final ByteBuf frame;
    final int length = eol - buffer.readerIndex();
    final int delimLength = buffer.getByte(eol) == '
    '? 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) == '
    '? 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

  • 相关阅读:
    springboot 启动报错"No bean named 'org.springframework.context.annotation.ConfigurationClassPostProcessor.importRegistry' available"
    使用SpringCloud Stream结合rabbitMQ实现消息消费失败重发机制
    Java 继承
    Java 抽象类 抽象方法 使用说明
    java 构造器(构造方法)使用详细说明
    Java enum枚举在实际项目中的常用方法
    万字长文!一次性弄懂 Nginx 处理 HTTP 请求的 11 个阶段
    Nginx 如何自定义变量?
    听说你的资源被盗用了,那你知道 Nginx 怎么防盗链吗?
    Nginx 的变量究竟是怎么一回事?
  • 原文地址:https://www.cnblogs.com/java-chen-hao/p/11445297.html
Copyright © 2020-2023  润新知