Netty诡异报错did not read anything but decoded a message

###前言
用netty做数据校验的时候,很自然的想法是写一个decoder,比如XXXXChecksumDecoder,如果校验出错,就丢弃这个数据包,一般来说,这种单纯的做数据校验的decoder,不会改变数据大小,就是说,传入的bytebuf大小如果是10byte,传出的bytebuf大小也应该是10byte,decoder只是做了一次数据校验,这个时候,经常遇到的问题是netty报错:did not read anything but decoded a message,就是提示使用者,必须读取一些字节,传出的bytebuf必须比传入的bytebuf小。

###问题解决方法
一般来说,很自然的想法是把decoder写成这样:

public class XXXXChecksumDecoder extends ByteToMessageDecoder {

@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
		int checkSumRemote = in.readUnsignedShortLE(Header.CHECKSUM_POS);
		in.setShortLE(Header.CHECKSUM_POS, 0);
		int checkSumLocal = ChecksumUtil.calculateChecksumValue(in);
		in.setShortLE(Header.CHECKSUM_POS, checkSumRemote);

		if(checkSumLocal != checkSumRemote)
		{
			in.skipBytes(in.readableBytes());
			System.out.println("CheckSum Error");
		}else {
			out.add(in);
		}
	}
}

但这样会抛出异常XXXXChecksumDecoder.decode()did not read anything but decoded a message
既然netty提示说必须要读走一些byte,那么这样行不行呢?

public class XXXXChecksumDecoder extends ByteToMessageDecoder {

@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
		int checkSumRemote = in.readUnsignedShortLE(Header.CHECKSUM_POS);
		in.setShortLE(Header.CHECKSUM_POS, 0);
		int checkSumLocal = ChecksumUtil.calculateChecksumValue(in);
		in.setShortLE(Header.CHECKSUM_POS, checkSumRemote);

		if(checkSumLocal != checkSumRemote)
		{
			in.skipBytes(in.readableBytes());
			System.out.println("CheckSum Error");
		}else {
			in.skipBytes(in.readableBytes());//这里跳过所有可读字节
			out.add(in);
		}
	}
}

但是如果这样,上层decoder收到的bytebuf,就已经是被全部读过的了,就是bytebuf的可读区域已经归0了,那要怎么解决呢?正确的做法是这样

public class XXXXChecksumDecoder extends ByteToMessageDecoder {

@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
		int checkSumRemote = in.readUnsignedShortLE(Header.CHECKSUM_POS);
		in.setShortLE(Header.CHECKSUM_POS, 0);
		int checkSumLocal = ChecksumUtil.calculateChecksumValue(in);
		in.setShortLE(Header.CHECKSUM_POS, checkSumRemote);

		if(checkSumLocal != checkSumRemote)
		{
			System.out.println("CheckSum Error");
		}else {
			Bytebuf frame = in.retainedDuplicate();
			out.add(frame);
		}
		in.skipBytes(in.readableBytes());
	}
}

in的副本返回给上层decoder,并且跳过所有in的可读字节。因为retainedDuplicate()只是将in的引用数加1并且复制其readerIndex、writerIndex等,并没有真的复制缓冲区,所以这样几乎不消耗额外性能。之后就安全地in.skipBytes(in.readableBytes())读走所有字节。

###问题的原因
为什么会出现这样的问题呢?源码中是这样的:

protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
    try {
        while (in.isReadable()) {
            int outSize = out.size();
            int oldInputLength = in.readableBytes();
            decode(ctx, in, out);
            if (outSize == out.size()) {
                if (oldInputLength == in.readableBytes()) {
                    break;
                } else {
                    continue;
                }
            }

            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);
    }
}

源码中,如果List<Object> out不增长的话,是不会抛出这个异常的,比如定长数据包解析中decoder一开始检查到可读数据没有达到数据包的大小,直接return,这时候是不会报异常的,只有decoder在out中增加了对象,就是说decoder产生了数据,但是传入的数据并没有减少,才会有这个错误,为什么要这样呢?Netty的作者给出了答案:

if you produce a message you need to also read something from the ByteBuf. This check was added to catch endless loops generated by user decoder bugs.

这是用来防止由decoder引起的无限循环的机制,这么想,如果每次decoder都生成一个新对象,但是in的readerIndex却不增长,这样再次调用decoder时,传入的in的readerIndex还是一样的,这时候decoder又会生成一个新对象,虽然不是一定的,但是这样容易引起无限循环,所以netty用异常来警告使用者,每次都必须从in里读出一些字节,减少in的大小,如果不想读,像上面的checksum例子,那就必须复制一个in,然后把原来的in的数据读掉。

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值