2021SC@SDUSC
DelimiterBasedFrameDecoder
DelimiterBasedFrameDecoder是基于分隔符的解码器,根据用户自己指定的分割符来进行解码,并且可以同时指定多个分割符,只要在读取数据时,碰到了其中任意一个分隔符,就可以进行一次解码
需要注意的是:如果只指定两个分隔符,并且是\r\n 和\n 时,基于分隔符的解码器就变成了一个基于行的解码器,那么在解码时,就直接使用行分割器 LineBaseFrameDecoder 解码
DelimiterBasedFrameDecoder定义了如下几个成员变量:
// 分隔符数组,因为可以同时制定过个分隔符,所以使用数组来存放
private final ByteBuf[] delimiters;
// 最大长度限制
private final int maxFrameLength;
// 是否跳过分隔符,true表示跳过
private final boolean stripDelimiter;
// 是否立即丢弃,true:立即
private final boolean failFast;
// 是否处于丢弃模式
private boolean discardingTooLongFrame;
// 累计丢弃的字节数
private int tooLongFrameLength;
// 如果分隔符是\r\n和\n时,就直接使用基于行的解码器
private final LineBasedFrameDecoder lineBasedDecoder;
注意到,该解码器比 LineBaseFrameDecoder 解码器多了两个成员变量,一个是 delimiters 属性,这是一个数组,用来存放用户自定义的分隔符,因为用户可以自定义多个分隔符,所以使用数组来存放。另一个是 lineBasedDecoder 属性,这个属性表示的是基于行的解码器,delimiters 数组中的分隔符当且仅当为 \r\n 和 \n 时,分隔符解码器就变成了行解码器,该属性的值在LineBaseFrameDecoder构造方法中被初始化:
public DelimiterBasedFrameDecoder(
int maxFrameLength, boolean stripDelimiter, boolean failFast, ByteBuf... delimiters) {
...
if (isLineBased(delimiters) && !isSubclass()) {
lineBasedDecoder = new LineBasedFrameDecoder(maxFrameLength, stripDelimiter, failFast);
this.delimiters = null;
} else {
...
lineBasedDecoder = null;
}
...
}
在构造方法中,会通过 isLineBased(delimiters) 方法来判断分隔符是否是 \r\n 和 \n,如果是,就创建一个行解码器,然后赋值给 lineBasedDecoder 属性;否则就令 lineBasedDecoder 属性为空:
private static boolean isLineBased(final ByteBuf[] delimiters) {
// 当分隔符是 \r\n 和 \n 时,才返回 true,也就是将分隔符解码器变成基于行的解码器
if (delimiters.length != 2) {
return false;
}
ByteBuf a = delimiters[0];
ByteBuf b = delimiters[1];
// 保证令a = \r\n,令b= \n
if (a.capacity() < b.capacity()) {
a = delimiters[1];
b = delimiters[0];
}
return a.capacity() == 2 && b.capacity() == 1
&& a.getByte(0) == '\r' && a.getByte(1) == '\n'
&& b.getByte(0) == '\n';
}
与前面介绍的解码器一样,分隔符解码器也重写了父类中的抽象方法 decode():
protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
// 调用decode()的重载方法解码
Object decoded = decode(ctx, in);
// 如果能成功解码,就添加到out中
if (decoded != null) {
out.add(decoded);
}
}
同样,核心是decode(ctx, in); 重点看一下这个函数(整体框架如下):
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
// 首先判断基于行的解码器是否被初始化了,如果被初始化了,就表明分隔符就是\r\n和\n,直接只用行解码器进行解码
if (lineBasedDecoder != null) {
return lineBasedDecoder.decode(ctx, buffer);
}
int minFrameLength = Integer.MAX_VALUE;
ByteBuf minDelim = null;
// 遍历所有的分隔符,然后找到最小位置的分割符
for (ByteBuf delim: delimiters) {
// 找到最小分隔符的位置
}
// 如果找到了分割符
if (minDelim != null) {
//如果处于丢弃模式
if (discardingTooLongFrame) {
return null;
}else{
return frame;
}
} else {
//如果没有找到分割符
//判断是否处于非丢弃模式
if (!discardingTooLongFrame) {
} else {
}
return null;
}
}
首先判断 lineBasedDecoder 是否为空,如果不为空,就表示分隔符是 \r\n 和 \n,那就直接使用行解码器解码;否则进入后面的逻辑
由于可以指定多个分隔符,所以需要找到最先出现的分隔符是哪一个,以及它的位置,查找方法是遍历每一个分隔符,然后分别找到它们在可读数据中的索引,分隔符的索引最小的就是我们要找的分隔符
后面的逻辑与行解码器基本是一样的,也是先分两种情况:找到分隔符以及没有找到分隔符。然后对于前面每一种情况,再分为是否处于丢弃模式。注意与行解码器不同的是,行解码器先是判断是否处于丢弃模式,再判断是否找到了分隔符。同样是只有找到了分隔符,且解码器不处于丢弃模式,才能解码出数据,否则将返回 null。