将字节流转换成消息的解码器
Netty解码器有很多种,比如基于长度的、基于分割符的、私有协议的,但是总体的思路都是一致的。
- 拆包思路:当数据满足了解码条件时,将其拆开。放到数组,然后发送到业务 handler 处理。
- 半包思路:当读取的数据不够时,先存起来,直到满足解码条件后,放进数组,送到业务 handler 处理。
概念
frame 帧
解析器
一般情况下,通过添加***FrameDecoder帧解码器,在管道pipeline中尽早地处理数据。
- DelimiterBasedFrameDecoder:以某个分割符为一帧
- FixedLengthFrameDecoder:以固定长度字节为一帧
- LineBasedFrameDecoder:以换行符为一帧
- LengthFieldBasedFrameDecoder:这种针对,协议有不同的部分组成,如头部,数据部分,头部中包含数据长度信息的情况。
ByteToMessageDecoder
- 所有ByteToMessageDecoder解码器的子类不能被Sharable注解
- 如果ByteBuf.readBytes(int)方法的返回buffer没有被释放,可能造成内存泄漏
channelRead 方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 从对象池中取出一个空的List
CodecOutputList out = CodecOutputList.newInstance();
ByteBuf data = (ByteBuf) msg;
first = cumulation == null;
if (first) {
// 第一次解码
cumulation = data;// 累计
} else {
// 第二次解码,就将 data 向 cumulation 追加,并释放 data
cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data);
}
// 得到追加后的 cumulation 后,调用 decode 方法进行解码
// 解码过程中,调用 fireChannelRead 方法,主要目的是将累积区的内容 decode 到 数组中
callDecode(ctx, cumulation, out);
// 如果累计区没有可读字节了
if (cumulation != null && !cumulation.isReadable()) {
// 将次数归零
numReads = 0;
// 释放累计区
cumulation.release();
// 等待 gc
cumulation = null;
} // 如果超过了 16 次,就压缩累计区,主要是将已经读过的数据丢弃,将 readIndex 归零。
else if (++ numReads >= discardAfterReads) {
numReads = 0;
discardSomeReadBytes();
}
int size = out.size();
// 如果没有向数组插入过任何数据
decodeWasNull = !out.insertSinceRecycled();
// 循环数组,向后面的 handler 发送数据,如果数组是空,那不会调用
fireChannelRead(ctx, out, size);
// 将数组中的内容清空,将数组的数组的下标恢复至原来
out.recycle();
}