Java Netty ByteToMessageCodec<;ByteBuf>;解码消息两次(部分)

Java Netty ByteToMessageCodec<;ByteBuf>;解码消息两次(部分),java,netty,Java,Netty,我正在使用EmbeddedChannel测试我的处理程序和编解码器以以下格式处理消息: +------------+------------------+----------------+ | Header | Payload Length | Payload | | 16 bytes | 2 bytes | "Some data" | +------------+------------------+-----

我正在使用
EmbeddedChannel
测试我的
处理程序
编解码器
以以下格式处理消息:

 +------------+------------------+----------------+      
 |   Header   |  Payload Length  |    Payload     |
 |  16 bytes  |     2 bytes      |  "Some data"   |     
 +------------+------------------+----------------+
首先,我想达到的目标是:

  • 通过创建一个对象来处理前16个字节,以在其中存储标题详细信息,并将解码后的标题对象添加到
    ChannelHandlerContext
    AttributeMap
    ,以供以后使用
  • 等待/检索整个有效负载数据
  • 使头对象和整个有效负载作为
    ByteBuf
    在最终处理程序上可用,以路由消息
  • 我将使用以下处理程序:

  • ByteToMessageCodec
    提取标题信息并将其添加到属性列表
  • LengthFieldBasedFrameDecoder
    读取有效负载长度并等待/检索整个帧
  • SimpleChannelInboundHandler
    ,它将使用从属性列表检索到的头对象来相应地路由有效负载
  • 将消息传递到
    ByteToMessageCodec
    解码
    方法时,将正确处理和提取报头。然后,我继续将Header对象添加到
    AttributeMap
    ,并添加
    ByteBuf
    (其readableBytes=2字节(有效负载长度指示器)+有效负载长度)

    假设有效负载长度为1020字节。消息最初由
    编解码器接收
    将具有
    readableBytes=16字节+2字节+1020字节
    。头由
    解码
    方法读取,剩余的可用字节(1022)随后添加到
    列表输出

    如果我的理解是正确的,剩余的字节现在将传递给下一个处理程序
    LengthFieldBasedFrameDecoder
    ,该处理程序将读取长度指示器并将有效负载(1020字节)传递给
    SimpleChannelHander
    ,但我一定搞错了

    decode
    方法再次被调用,与添加到
    列表中的1022字节相同

    在解码方法的JavaDoc中,有以下内容:

    Decode the from one ByteBuf to an other. This method will be called till either the input ByteBuf
    has nothing to read when return from this method or till nothing was read from the input ByteBuf.
    
    这是否意味着将调用
    decode
    ,直到
    readableBytes==0

    将消息的其余部分传递给
    LengthFieldBasedFrameDecoder
    的最有效方式是什么

    我假设
    LengthFieldBasedFrameDecoder
    需要一个
    ByteBuf
    作为输入,那么这是否意味着我需要设置
    readerIndex=0
    并将ByteBuf的副本添加到
    列表中

    任何帮助/建议/批评都将不胜感激,我希望以尽可能干净的方式做到这一点

    以下是我的
    解码方法:

    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        byte [] headerBytes = new byte[HEADER_LENGTH];
        in.readBytes(headerBytes, 0, HEADER_LENGTH);
    
        Header header = new Header(headerBytes);
        System.out.println("Decoded Header: \n" + header);
    
        //Set the header attribute so it can be used by routing handlers
        ctx.attr(ChannelAttributes.HEADER).getAndSet(header);
        //pass to next handler
        out.add(in);
    }
    
    受保护的无效解码(ChannelHandlerContext ctx、ByteBuf输入、列表输出)引发异常{
    字节[]头字节=新字节[头字节长度];
    in.readBytes(头字节,0,头长度);
    表头=新表头(表头字节);
    System.out.println(“解码头:\n”+头);
    //设置header属性,以便路由处理程序可以使用它
    ctx.attr(ChannelAttributes.HEADER).getAndSet(HEADER);
    //传递给下一个处理程序
    out.add(in);
    }
    
    注:我正在阅读

    这是否意味着在readableBytes==0之前将调用decode

    基本上,是的。
    ByteToMessageDecoder的简化视图如下所示

    while (in.isReadable()) {
        int outputSizeBefore = out.size();
        int readableBytesBefore = in.readableBytes();
    
        callYourDecodeImpl(ctx, in, out);
    
        int outputSizeAfter = out.size();
        int readableBytesAfter = in.readableBytes();
    
        boolean didNotDecodeAnything = outputSizeBefore == outputSizeAfter;
        boolean didNotReadAnything = readableBytesBefore == readableBytesAfter;
    
        if(didNotDecodeAnything && didNotReadAnything) {
            break;
        }
    
        // next iteration, continue with decoding
    }
    
    因此,解码器将持续读取标题,直到输入缓冲区耗尽

    要获得所需的行为,必须将
    isSingleDecode
    标志设置为true:

    class MyDecoder extends ByteToMessageDecoder {
    
        MyDecoder() {
            setSingleDecode(true);
        }
    
        // your decode impl as before
    }
    

    这将在解码实现解码某些内容后停止循环。 现在,您的
    LengthFieldBasedFrameDecoder
    将被添加到
    out
    列表中的
    ByteBuf
    调用。 帧解码工作如您所述,无需向列表中添加副本。 您的
    SimpleChannelInboundHandler
    将使用有效负载帧作为
    msg
    调用

    但是,您将无法从
    SimpleChannelInboundHandler
    由于每个通道处理程序的
    ChannelHandlerContext
    都是不同的,因此不共享atributes

    解决此问题的一种方法是为此使用事件。 在
    解码器
    中,不要将
    标题
    添加到
    属性映射
    ,而是将其作为事件发送:

    // instead of
    // ctx.attr(Header.ATTRIBUTE_KEY).getAndSet(header);
    // do this
    ctx.fireUserEventTriggered(ChannelAttributes.HEADER);
    
    然后,像这样编写
    SimpleChannelInboundHandler

    class MyMessageHandler extends SimpleChannelInboundHandler<ByteBuf> {
    
        private Header header = null;
    
        MyMessageHandler() {
              super(true);
        }
    
        @Override
        public void userEventTriggered(final ChannelHandlerContext ctx, final Object evt) throws Exception {
            if (evt instanceof Header) {
                header = (Header) evt;
            } else {
                super.userEventTriggered(ctx, evt);
            }
        }
    
        @Override
        protected void channelRead0(final ChannelHandlerContext ctx, final ByteBuf msg) throws Exception {
            if (header != null) {
                System.out.println("header = " + header);
                // continue with header, such as routing...
            }
            header = null;
        }
    }
    
    class MyMessageHandler extends ChannelInboundHandlerAdapter {
        private Header header = null;
    
        @Override
        public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
            if (msg instanceof Header) {
                header = (Header) msg;
                System.out.println("got the header " + header);
            } else if (msg instanceof ByteBuf) {
                ByteBuf byteBuf = (ByteBuf) msg;
                System.out.println("got the message " + msg);
                try {
                    // continue with header, such as routing...
                } finally {
                    ReferenceCountUtil.release(msg);
                }
            } else {
                super.channelRead(ctx, msg);
            }
        }
    }
    
    然后,像这样编写
    ChannelInboundHandler

    class MyMessageHandler extends SimpleChannelInboundHandler<ByteBuf> {
    
        private Header header = null;
    
        MyMessageHandler() {
              super(true);
        }
    
        @Override
        public void userEventTriggered(final ChannelHandlerContext ctx, final Object evt) throws Exception {
            if (evt instanceof Header) {
                header = (Header) evt;
            } else {
                super.userEventTriggered(ctx, evt);
            }
        }
    
        @Override
        protected void channelRead0(final ChannelHandlerContext ctx, final ByteBuf msg) throws Exception {
            if (header != null) {
                System.out.println("header = " + header);
                // continue with header, such as routing...
            }
            header = null;
        }
    }
    
    class MyMessageHandler extends ChannelInboundHandlerAdapter {
        private Header header = null;
    
        @Override
        public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
            if (msg instanceof Header) {
                header = (Header) msg;
                System.out.println("got the header " + header);
            } else if (msg instanceof ByteBuf) {
                ByteBuf byteBuf = (ByteBuf) msg;
                System.out.println("got the message " + msg);
                try {
                    // continue with header, such as routing...
                } finally {
                    ReferenceCountUtil.release(msg);
                }
            } else {
                super.channelRead(ctx, msg);
            }
        }
    }
    
    LengthFieldBasedFrameDecoder
    只是忽略不是
    ByteBuf
    s的消息, 因此,您的头将只传递它(假定它没有实现
    ByteBuf
    ),并且 到达您的
    ChannelInboundHandler
    。然后,该消息将被解码到 有效负载帧并传递给您的
    ChannelInboundHandler

    作为后续回答:我为那些使用
    ByteToMessageCodec
    的人找到了一种替代方法,该方法无法实现
    setSingleDecode
    方法

    通过in.readRetainedSlice()读取字节,如下所示

    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        byte [] headerBytes = new byte[HEADER_LENGTH];
        in.readBytes(headerBytes, 0, HEADER_LENGTH);
    
        Header header = new Header(headerBytes);
        System.out.println("Decoded Header: \n" + header);
    
        //Set the header attribute so it can be used by routing handlers
        ctx.attr(ChannelAttributes.HEADER).getAndSet(header);
        //pass to next handler
        int length = in.readShort();
        out.add(in.readRetainedSlice(length));
    }
    
    受保护的无效解码(ChannelHandlerContext ctx、ByteBuf输入、列表输出)引发异常{
    字节[]头字节=新字节[头字节长度];
    in.readBytes(头字节,0,头长度);
    表头=新表头(表头字节);
    System.out.println(“解码头:\n”+头);
    //设置header属性,以便路由处理程序可以使用它
    ctx.attr(ChannelAttributes.HEADER).getAndSet(HEADER);
    //传递给下一个处理程序
    int length=in.readShort();
    out.add(in.readRetainedSlice(长度));
    }
    
    iAN2TEDV关注字节复制的效率,但当
    readableBytes
    超过您的消息长度时,这是不可避免的,您不能只