这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战
一、源码入口寻找
上一节课我们学习了LineBasedFrameDecoder的实现方式,本节课我们学习LengthFieldBasedFrameDecoder的实现原理,它的源码比上一节课的行级解码器的难度要大一些,需要大家仔细理解!
同样,LengthFieldBasedFrameDecoder也是继承自ByteToMessageDecoder抽象解码器!
我们前几节课学习过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;
假设当前的数据包结构如下:
先计算长度域在数据包内的绝对偏移量位置:
long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder);
开始读取长度的大小:
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方法获取的长度,并不会影响读写指针的位置!
此时的数据结构如下:
以上图为例返回的为14!那么我们至此也就获取到数据包的数据体的长度!下一步就是需要根据这个长度读取数据!
2. 根据计算的长度读取数据
//算出本次应该读取到的末尾位置 计算本次数据包的再总数包的末尾位置
frameLength += lengthAdjustment + lengthFieldEndOffset;
计算出本次需要读取的数据包的总长度 = 头长度+长度域长度+数据包长度
判断当前数据包的可读数据是否足够本次要读取的数据长度:
// 永远不会溢出,因为它小于maxFrameLength
int frameLengthInt = (int) frameLength;
//如果当前可读字节数小于当前的数据包长度 就返回 等待下一次读取
if (in.readableBytes() < frameLengthInt) {
//不读了
return null;
}
当可读数据不足以拼装成一个完整的数据包,就直接返回null不读了,下次再来等够一个数据包了在读!
当发现跳过的字节数居然大于要读取的数据的总长度,也报错:
//跳过的字节数大于 数据包长度 直接报错
if (initialBytesToStrip > frameLengthInt) {
failOnFrameLengthLessThanInitialBytesToStrip(in, frameLength, initialBytesToStrip);
}
以上校验都通过后,根据设置的跳过字节的长度,跳过字节数
//跳过需要跳过的数据
in.skipBytes(initialBytesToStrip);
// 获取当前读指针的位置
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);
}
切取出来一个完整的数据包:
设置数据包的读指针的位置到数据包的末尾:
in.readerIndex(readerIndex + actualFrameLength);
至此一个完整数据包就被解码出来了!
三、总结
- 先根据提供的长度偏移和长度域长度读取数据包的长度
- 根据数据包的长度+偏移量长度计算出本次需要读取的总数据包的长度
- 根据设置的跳过数据的长度计算有效的护数据长度
- 将有效的数据长度读取出来,然后改变读指针的位置到数据包的末尾
- 将解码出来的数据交给ByteToMessageDecoder进行后续操作!