内置解码器LineBasedFrameDecoder源码解析| 8月更文挑战

761 阅读5分钟

一、分析源码入口

LineBasedFrameDecoder 是我们前面介绍到的行级解码器,它以\n或者\r\n作为分隔符的解码原则!我们首先看它的类结构图:

image-20210509133201643

我们上节课说到channelRead方法,最终会调用子类的decode方法进行解码,我们直接进入到decode方法进行源码解析!

二、源码分析

protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
    //查找行尾  返回\n或者\r的位置
    final int eol = findEndOfLine(buffer);
    if (!discarding) {
        //存在结尾
        if (eol >= 0) {
            final ByteBuf frame;
            //结尾位置 - 都指针位置  = 有效数据长度
            final int length = eol - buffer.readerIndex();
            //如果当前索引下指向的是\r证明是以\r\n开头的   结尾数据占两个字节否则占用一个
            final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
            //如果有效数据长度大于预设长度进行报错和丢弃
            if (length > maxLength) {
                //重新指定读指针的位置
                buffer.readerIndex(eol + delimLength);
                //进行报错
                fail(ctx, length);
                return null;
            }
            //是否丢弃换行符
            if (stripDelimiter) {
                //如果设置为丢弃 则从当前都指针位置切片 并增加引用计数
                //相当于调用了readSlice().retain()
                frame = buffer.readRetainedSlice(length);
                //跳过换行符  \n跳过一个  \r\n跳过两个
                buffer.skipBytes(delimLength);
            } else {
                //从当前的读指针位置读取  有效数据长度+换行数据长度的位置
                frame = buffer.readRetainedSlice(length + delimLength);
            }
            //返回这个切片
            return frame;
        } else {
            //当前有效数据
            final int length = buffer.readableBytes();
            if (length > maxLength) {
                discardedBytes = length;
                buffer.readerIndex(buffer.writerIndex());
                discarding = true;
                offset = 0;
                if (failFast) {
                    fail(ctx, "over " + discardedBytes);
                }
            }
            //不做处理
            return null;
        }
    } else {
        ........................忽略........................
        return null;
    }
}

1. 查找行尾

final int eol = findEndOfLine(buffer);
/**
 * 返回找到的行尾缓冲区中的索引。
 * 如果在缓冲区中未找到行尾,则返回-1。
 */
private int findEndOfLine(final ByteBuf buffer) {
    //获取可读字节的长度
    int totalLength = buffer.readableBytes();
    //从当前读指针+偏移量的位置查询到最大可读数-偏移量的位置  查询\n
    int i = buffer.forEachByte(buffer.readerIndex() + offset, totalLength - offset, ByteProcessor.FIND_LF);
    //如果存在\n
    if (i >= 0) {
        //初始化偏移量
        offset = 0;
        //如果换行符的前一位是\r 就返回\r的位置
        if (i > 0 && buffer.getByte(i - 1) == '\r') {
            i--;
        }
    } else {
        //没查询到就记录一个当前的偏移量将偏移量移动到数据末尾等待下一次数据过来在读
        offset = totalLength;
    }
    return i;
}
int i = buffer.forEachByte(buffer.readerIndex() + offset, totalLength - offset, ByteProcessor.FIND_LF);

遍历整个ByteBuf对象,逐个字节对比,是否存在 \n 如果存在,就直接返回所在的索引,注意从头开始寻找的,如果能找到,这里必定返回的是第一个\n!如果没找到就返回-1, 有兴趣的小伙伴可以跟一下,这里不做太多的讲解!

//初始化偏移量
offset = 0;
//如果换行符的前一位是\r 就返回\r的位置
if (i > 0 && buffer.getByte(i - 1) == '\r') {
    i--;
}

如果存在\n的话,向前找一位,看是否是**\r**,如果是的话证明数据包是以**\r\n**结尾的,我们将索引为定到\r的位置!

如果不是以\n结尾就进入到 else的逻辑:

offset = totalLength;

直接记录一个本次读取的偏移量,以便于下次接着向后寻找\n!

2. 截取数据

如果返回的数据存在\n 证明是存在结尾的,进入到if逻辑中!

//\n或者\r的位置 - 读指针位置  = 完整数据包的有效数据长度
final int length = eol - buffer.readerIndex();

首先计算出数据的有效长度!

//如果当前索引下指向的是\r证明是以\r\n开头的   结尾数据占两个字节否则占用一个
final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;

判断是以什么结尾的,如果是以 \n 结尾,证明分割符的长度是1 ;如果是以\r\n结尾的,证明长度是2!

if (length > maxLength) {
    //重新指定读指针的位置
    buffer.readerIndex(eol + delimLength);
    //进行报错
    fail(ctx, length);
    return null;
}

这里会判断当前的有效数据是否超过了设置的最大长度(构造函数时设置的),如果超过了最大长度,直接丢弃这部分数据(将读指针后移至数据末尾),然后报错!

//是否丢弃换行符
if (stripDelimiter) {
    //如果设置为丢弃 则从当前都指针位置切片 并增加引用计数
    //相当于调用了readSlice().retain()
    frame = buffer.readRetainedSlice(length);
    //跳过换行符  \n跳过一个  \r\n跳过两个
    buffer.skipBytes(delimLength);
} else {
    //从当前的读指针位置读取  有效数据长度+换行数据长度的位置
    frame = buffer.readRetainedSlice(length + delimLength);
}

判断是否丢弃换行符,也是我们再构造LineBasedFrameDecoder 传入的参数,默认是true;

如果设置了丢弃换行符的话:

  • 调用readRetainedSlice切片方法,从当前的完整的数据包中,从当前读指针的位置,向后切一个完整数据包的位置(不包含换行符)!
  • 调用跳过方法,跳过换行符,使读指针一定到数据包后面!

如果丢弃换行符为false的话:

  • 直接从当前的完整的数据包中切出来一个 有效数据长度+换行符的长度的数据! 类似:数据\n的存在,这种是不需要跳过的!

至此,我们已经根据换行符得到了一个完整的数据包,我们只需要将他返回,再抽象类ByteToMessageDecoder中,它返回的数据就会被添加到out集合中,等待处理!

三、总结

  1. 先遍历数据包的每一个字节!查找第一个换行符的索引位置!
  2. 从完整的数据包中,从都指针开始截取到结尾位置!