内置解码器LengthFieldBasedFrameDecoder源码解析

904 阅读4分钟

这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战

一、源码入口寻找

上一节课我们学习了LineBasedFrameDecoder的实现方式,本节课我们学习LengthFieldBasedFrameDecoder的实现原理,它的源码比上一节课的行级解码器的难度要大一些,需要大家仔细理解!

同样,LengthFieldBasedFrameDecoder也是继承自ByteToMessageDecoder抽象解码器!

image-20210509150648708

我们前几节课学习过LengthFieldBasedFrameDecoder的使用方式,我们再来回顾一下它的使用方式!

ch.pipeline().addLast("lengthFieldBasedFrameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));

当时也对它的参数有过一些介绍:

maxFrameLength:本次能接收的最大的数据长度

lengthFieldOffset:设置的长度域的偏移量,长度域在数据包的起始位置

lengthFieldLength:长度域的长度

lengthAdjustment:数据包的偏移量,计算方式=数据长度 +lengthAdjustment=数据总长度

initialBytesToStrip:需要跳过的字节数

所以我们就以这个例子作为切入点,分析它的源码,进入到 decode方法:

二、源码分析

protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
    if (discardingTooLongFrame) {
        discardingTooLongFrame(in);
    }
    //长度域偏移量 + 长度域的长度  等于数据长度域尾部的位置
    //当可读字节话没有到长度域的尾部位置的时候 就直接返回 证明数据包不完整
    if (in.readableBytes() < lengthFieldEndOffset) {
        return null;
    }
    //计算长度绝对偏移量
    int actualLengthFieldOffset = in.readerIndex() + lengthFieldOffset;
    //读取长度大小
    long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder);
    //长度小于0的话  就是不合法 直接抛异常
    if (frameLength < 0) {
        failOnNegativeLengthField(in, frameLength, lengthFieldEndOffset);
    }
    //算出本次应该读取到的末尾位置
    frameLength += lengthAdjustment + lengthFieldEndOffset;

    //抽取出来的长度是小于长度域尾部的位置的话    就抛异常 同时跳过 该字节数
    if (frameLength < lengthFieldEndOffset) {
        failOnFrameLengthLessThanLengthFieldEndOffset(in, frameLength, lengthFieldEndOffset);
    }
    //如果本次读取的字节数大于最大长度  最大长度 弟弟
    if (frameLength > maxFrameLength) {
        exceededFrameLength(in, frameLength);
        return null;
    }

    // 永远不会溢出,因为它小于maxFrameLength
    int frameLengthInt = (int) frameLength;
    //如果当前可读字节数小于当前的数据包长度  就返回 等待下一次读取
    if (in.readableBytes() < frameLengthInt) {
        return null;
    }

    //跳过的字节数大于 数据包长度 直接报错
    if (initialBytesToStrip > frameLengthInt) {
        failOnFrameLengthLessThanInitialBytesToStrip(in, frameLength, initialBytesToStrip);
    }
    //跳过需要跳过的数据
    in.skipBytes(initialBytesToStrip);

    // 提取框
    int readerIndex = in.readerIndex();
    int actualFrameLength = frameLengthInt - initialBytesToStrip;
    //从当前的都指针读取actualFrameLength个字节
    ByteBuf frame = extractFrame(ctx, in, readerIndex, actualFrameLength);
    //将都指针调整到数据包末尾
    in.readerIndex(readerIndex + actualFrameLength);
    //返回这个完整的数据包
    return frame;
}

代码比较长,我们逐行分析,主要分为以下几个步骤:

1. 计算长度的大小

//计算绝对偏移量   数据的读指针+长度域的偏移量 = 该长度域的偏移量在整个数据内的偏移量
int actualLengthFieldOffset = in.readerIndex() + lengthFieldOffset;

假设当前的数据包结构如下:

image-20210509162057370

先计算长度域在数据包内的绝对偏移量位置:

long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder);

image-20210509162219181

开始读取长度的大小:

long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder);
protected long getUnadjustedFrameLength(ByteBuf buf, int offset, int length, ByteOrder order) {
    buf = buf.order(order);
    long frameLength;
    switch (length) {
        case 1:
            frameLength = buf.getUnsignedByte(offset);
            break;
        case 2:
            frameLength = buf.getUnsignedShort(offset);
            break;
        case 3:
            frameLength = buf.getUnsignedMedium(offset);
            break;
        case 4:
            frameLength = buf.getUnsignedInt(offset);
            break;
        case 8:
            frameLength = buf.getLong(offset);
            break;
        default:
            throw new DecoderException(
                "unsupported lengthFieldLength: " + lengthFieldLength + " (expected: 1, 2, 3, 4, or 8)");
    }
    return frameLength;
}

基于先前计算的索引位置,读取数据的长度,由此可知,数据的长度位只能设置为 1、2、3、4、8 其余的都会报错!注意这里自己跟一下,调用的get方法获取的长度,并不会影响读写指针的位置!

此时的数据结构如下:

image-20210509163706491

以上图为例返回的为14!那么我们至此也就获取到数据包的数据体的长度!下一步就是需要根据这个长度读取数据!

2. 根据计算的长度读取数据

//算出本次应该读取到的末尾位置  计算本次数据包的再总数包的末尾位置
frameLength += lengthAdjustment + lengthFieldEndOffset;

计算出本次需要读取的数据包的总长度 = 头长度+长度域长度+数据包长度

image-20210509163724752

判断当前数据包的可读数据是否足够本次要读取的数据长度:

// 永远不会溢出,因为它小于maxFrameLength
int frameLengthInt = (int) frameLength;
//如果当前可读字节数小于当前的数据包长度  就返回 等待下一次读取
if (in.readableBytes() < frameLengthInt) {
    //不读了
    return null;
}

当可读数据不足以拼装成一个完整的数据包,就直接返回null不读了,下次再来等够一个数据包了在读!

当发现跳过的字节数居然大于要读取的数据的总长度,也报错:

//跳过的字节数大于 数据包长度 直接报错
if (initialBytesToStrip > frameLengthInt) {
    failOnFrameLengthLessThanInitialBytesToStrip(in, frameLength, initialBytesToStrip);
}

以上校验都通过后,根据设置的跳过字节的长度,跳过字节数

//跳过需要跳过的数据
in.skipBytes(initialBytesToStrip);

image-20210509164133495

 // 获取当前读指针的位置
int readerIndex = in.readerIndex();
//数据长度 - 需要跳过的字节数
int actualFrameLength = frameLengthInt - initialBytesToStrip;

按照上图的指示,我们在现在属性如下:

  • readerIndex = 17

  • actualFrameLength = 20 - 6 = 14

开始正式的读取数据:

ByteBuf frame = extractFrame(ctx, in, readerIndex, actualFrameLength);
/**
 * 从指定的位置切取length个长度的数据
 */
protected ByteBuf extractFrame(ChannelHandlerContext ctx, ByteBuf buffer, int index, int length) {
    return buffer.retainedSlice(index, length);
}

切取出来一个完整的数据包:

image-20210509164422175

设置数据包的读指针的位置到数据包的末尾:

in.readerIndex(readerIndex + actualFrameLength);

image-20210509164623550

至此一个完整数据包就被解码出来了!

三、总结

  1. 先根据提供的长度偏移和长度域长度读取数据包的长度
  2. 根据数据包的长度+偏移量长度计算出本次需要读取的总数据包的长度
  3. 根据设置的跳过数据的长度计算有效的护数据长度
  4. 将有效的数据长度读取出来,然后改变读指针的位置到数据包的末尾
  5. 将解码出来的数据交给ByteToMessageDecoder进行后续操作!