7 Netty解码
将二进制数据流解析成自定义协议数据包ByteBuf
两个部分:
1.解码器基类
2.常见的解码器分析
7.1 解码器基类ByteToMessageDecoder解码步骤
- 流程
1.使用一个累加器cumulation(类型为ByteBuf)来累加字节流
2.调用子类的decode方法进行解析(callDecode(),去累加器读取数据)
3.如果解析到了ByteBuf,则将解析到的ByteBuf向下传播(fireChannelRead())
- 解析
ByteToMessageDecoder#channelRead方法里,如果msg不是ByteBuf,则向下传播,说明解码是基于ByteBuf的
如果累加器为null,则直接将msg赋值给累加器,进行第二步;如果不为null,则追加,可能需要扩容
子类将解析到的ByteBuf对象放到CodecOutputList这个ArrayList里面,然后向下传播给后面的handler,这样就有可能传播到业务的解码器里面进行业务的解码处理
子类decode方法框架:
@Override
protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
Object decoded = decode(ctx, in);//decode由子类自己实现
if (decoded != null) {
out.add(decoded);
}
}
注意:自定义解码器如果没有读取到数据,则要返回null,ByteToMessageDecoder才会break结束循环,等待下一次数据的到来
7.2 基于固定长度解码器FixedLengthFrameDecoder的分析
以固定长度frameLength 来划分数据包
7.3 基于行解码器LineBasedFrameDecoder的分析
以换行符来划分数据包(字节流以\r\n,\n结尾时可以使用)
使用findEndOfLine来找到eol(endOfLine)
非丢弃模式处理:支持\n和\r\n两种分隔符(具体算法:找到\n,然后加个if判断前一个字符是否为\r,是的话i–,就得到\r的位置了)
1.正常情况下:readIndex到第一个eol的数据封装成数据包后,将readIndex指针移到第一个eol下一个位置,重复此过程直到结束即可
当然,如果readIndex到第一个eol的数据的长度大于max,则丢弃,不会封装成数据包
2.而如果readIndex到writeIndex中间没有eol,则会判断其长度是否大于maxLength:
如果小于则直接返回null,代表什么都没有解析到;
如果大于,则需要丢弃(即将readIndex移到writeIndex的位置上),然后进入丢弃模式(discarding=true):
丢弃模式:
3.正常情况下,将readIndex移到第一个eol的下一个位置,即丢弃第一个eol前的数据,然后进入非丢弃模式;
4.而如果readIndex到writeIndex中间没有eol,则将writeIndex-readIndex这段丢弃(将readIndex移到writeIndex的位置上)
7.4 基于分隔符解码器DelimiterBasedFrameDecoder的分析
可以传进去多个分隔符来划分数据包;还要传一个maxFrameLength
- 流程
1.如果分隔符是基于行的分隔符,则使用行解码器LineBasedFrameDecoder来处理
2.找到最小分隔符,首先以最小分隔符来拆分数据包
3.解码
- 解析
1 LineBasedFrameDecoder的初始化:构造DelimiterBasedFrameDecoder的过程中,发现使用“\ n”和“\r\n”作为分隔符进行解码时进行
2 最小分隔符:不是指分隔符本身大小,而是指分隔出来的数据包大小;
每个分隔符分隔出来的数据包中,取最小的数据,这个分隔符才是最小分隔符
3
找到最小分隔符:
- 非丢弃模式:
如果发现分隔出来的数据包大小大于maxFrameLength,则将这段数据包+分隔符全部丢弃,然后fail方法抛出异常,返回null;
如果小于,即正常情况,封装成ByteBuf(同样有“是否包含分隔符一起封装”的逻辑),返回此ByteBuf
- 丢弃模式:
丢弃找到的这段数据包+分隔符,然后进入非丢弃模式,返回null
没有找到最小分隔符:
- 丢弃模式:
丢弃可读字节,返回null
- 非丢弃模式:
如果可读字节大于max,则丢弃这段可读字节,进入丢弃模式,返回null;
如果可读字节小于max,则什么都不做,留着这段字节留待下次使用,返回null
行分隔解码器就是按照这个逻辑编写的,代码跟DelimiterBasedFrameDecoder不一样而已:DelimiterBasedFrameDecoder先判断分隔符,而行分隔解码器则知道了分隔符,所以直接按照丢弃非丢弃来编写
7.5 基于长度域解码器LengthFieldBasedFrameDecoder的分析
7.5.1 重要参数分析
https://blog.csdn.net/fst438060684/article/details/82912122
lengthFieldOffset:长度域的偏移量,也就是长度域要从什么地方开始,跳过lengthFieldOffset,就开始Length的运算了
lengthFieldLength:长度域的长度,也就是长度域(Length域)占多少个字节
lengthAdjustment:长度域的值的调整,也就是长度域里面的还要做多少调整才是符合要求的,这个值可以是正值,可以是负值,正值表示还要加多少,负值表示还要减去多少,lengthFieldLength里的值和lengthAdjustment运算过后得到的值就是真正的内容
initialBytesToStrip:原始需要跳过多少才返回给用户
源码例子1:没有偏移,长度域的长度为2字节,也就是为0x000C(12)
* <b>lengthFieldOffset</b> = <b>0</b>
* <b>lengthFieldLength</b> = <b>2</b>
* lengthAdjustment = 0
* initialBytesToStrip = 0 (= do not strip header)
*
* BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes)
* +--------+----------------+ +--------+----------------+
* | Length | Actual Content |----->| Length | Actual Content