Netty-解码器架构与常用解码器 (2)

我们直接跟进源码: 可以看到,在把ByteBuf真正通过下面的decodeRemovalReentryProtection(ctx, in, out);的子类进行解码时, 它记录下来了当时ByteBuf中可读的字节数, 它用这个标记和经过子类处理之后的ByteBuf的可读的字节数进行比对,从而判断出子类是否真的读取成功

protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) { try { while (in.isReadable()) { int outSize = out.size(); if (outSize > 0) {// todo 如果盛放解析完成后的数据的 out集合中有数据 fireChannelRead(ctx, out, outSize); /// todo 传播channelRead事件,数据也传递进去 out.clear(); // todo 清空out 集合 if (ctx.isRemoved()) { break; } outSize = 0; } // todo 记录 子类使用in之前, in中的可读的字节 int oldInputLength = in.readableBytes(); //todo 调用子类重写的 decode() decodeRemovalReentryProtection(ctx, in, out); if (ctx.isRemoved()) { break; } if (outSize == out.size()) { // todo 0 = 经过上面的decode解析后的 out.size()==0 , 说明没解析出任何东西 if (oldInputLength == in.readableBytes()) { // todo 第一种情况就是 可能字节数据不够, 根本没从in中读 break; } else { continue; // todo 情况2: 从in中读了, 但是没来得及继续出 内容 } } // todo 来到这里就说明,已经解析出数据了 , // todo 解析出数据了 就意味着in中的readIndex被子类改动了, 即 oldInputLength != in.readableBytes() // todo 如下现在还相等, 肯定是出问题了 if (oldInputLength == in.readableBytes()) { throw new DecoderException( StringUtil.simpleClassName(getClass()) + ".decode() did not read anything but decoded a message."); } if (isSingleDecode()) { break; } } } catch (DecoderException e) { throw e; } catch (Throwable cause) { throw new DecoderException(cause); } } 如何实现自己的解码器?

实现自己的解码器, 记得了解这三个参数分别是什么

ctx: 当前的hander所在的 Context

cumulation: 累加器,其实就是ByteBuf

out: 她其实是个容器, 用来盛放 经过编码之后的数据,也就是可以被后续的处理器使用 类型

实现的思路就是继承ByteToMessageDecoder然后重写它唯一的抽象方法,decode(), 实现的逻辑如下:

protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { System.out.println("MyDeCoderHandler invoke..."); System.out.println(in.readableBytes()); if (in.readableBytes()>=8){ out.add(in.readLong()); } } 创建的编解码器 固定长度的解码器FixedLengthFrameDecoder

他里面只维护着一个private final int frameLength;
使用时,我们通过构造函数传递给他,他就会按照下面的方式解码

我们看一下它的javaDoc

原始数据 * +---+----+------+----+ * | A | BC | DEFG | HI | * +---+----+------+----+ 如果frameLength==3 * +-----+-----+-----+ * | ABC | DEF | GHI | * +-----+-----+-----+

它的decode() 实现如下

protected Object decode( @SuppressWarnings("UnusedParameters") ChannelHandlerContext ctx, ByteBuf in) throws Exception { if (in.readableBytes() < frameLength) { return null; } else { // 从in中截取 frameLength 长度的 字节流 return in.readRetainedSlice(frameLength); } } 行解码器LineBasedFrameDecoder

她会根据换行符进行解码, 无论用户发送过来的数据是以 \r\n 还是 \n 类型的换行符LineBasedFrameDecoder

使用:

public LineBasedFrameDecoder(final int maxLength) { this(maxLength, true, false); } public LineBasedFrameDecoder(final int maxLength, final boolean stripDelimiter, final boolean failFast) { this.maxLength = maxLength; this.failFast = failFast; this.stripDelimiter = stripDelimiter; }

第一个构造函数

入参位置是我们指定的每一行最大的字节数, 超过了这个大小的所有行,将全部被丢弃

默认跳过分隔符

出现了超过最大值的行,不报异常

第二个构造函数

入参1 是我们指定的每一行最大的字节数, 超过了这个大小的所有行,将全部被丢弃

入参2 指定每次解析是否跳过换行符

入参3 指定出现大于规定的最大字节数时是否报异常

看它重写的decode()的实现逻辑如下:

它总起来分成四种情况

非丢弃模式

找到了换行符

如 readIndex + 换行符的位置 < maxLength 的关系 --> 解码

如 readIndex + 换行符的位置 > maxLength的关系 --> 丢弃

未找到换行符

如果可解析的长度 > maxLength --> 丢弃

丢弃模式

找到了换行符

丢弃

未找到换行符

丢弃

基于分隔符的解码器DelimiterBasedFrameDecoder

它主要有这几个成员变量, 根据这几个成员变量,可以选出使用它哪个构造函数

private final ByteBuf[] delimiters; 分隔符,数组 private final int maxFrameLength; 每次能允许的最大解码长度 private final boolean stripDelimiter; 是否跳过分隔符 private final boolean failFast; 超过最大解码长度时,是否抛出异常 private boolean discardingTooLongFrame; 是否丢弃超过最大限度的帧 private int tooLongFrameLength; 记录超过最大范围的字节数值

分三步

第一, 判断我们传递进入的分隔符是否是\n \r\n 如果是的话,就是用上面的, 行解码器

第二步, 按照最细的力度进行解码, 比如, 我们有两个解码器, AB, 当前的readIndex 到A, 有2个字节, 到B有3个字节, 就会按照A进行解码

解码

基于长度域的解码器LengthFieldBasedFrameDecoder

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsxfzd.html