Netty ByteToMessageCodec <bytebuf>解码消息两次(部分)</bytebuf>

时间:2014-10-28 09:17:23

标签: java netty

我使用EmbeddedChannel来测试我的handlerscodecs以下列格式处理邮件:

 +------------+------------------+----------------+      
 |   Header   |  Payload Length  |    Payload     |
 |  16 bytes  |     2 bytes      |  "Some data"   |     
 +------------+------------------+----------------+

首先,我想要实现的目标:

  1. 首先处理16个字节,方法是创建一个对象以存储标题详细信息,然后将解码后的标题对象添加到AttributeMap的{​​{1}}中以供日后使用;
  2. 等待/检索整个有效载荷数据;
  3. 将最终处理程序上的Header对象和整个有效负载ChannelHandlerContext用于路由消息。
  4. 我使用以下处理程序:

    1. ByteBuf提取标题信息并将其添加到属性列表中。
    2. ByteToMessageCodec<ByteBuf>读取有效负载长度并等待/检索整个帧。
    3. LengthFieldBasedFrameDecoder将使用从属性列表中检索的标头对象来相应地路由有效负载。
    4. 当邮件传递给SimpleChannelInboundHandler的{​​{1}}方法时,会正确处理和提取标题。然后我继续将Header对象添加到decode并添加ByteToMessageCodec(其readBytes = 2字节(有效负载长度指示符)+有效负载长度)。

      假设有效载荷长度为1020个字节。该邮件最初由AttributeMap收到ByteBuf。标头由codec方法读取,剩余的可用字节(1022)随后被添加到readableBytes = 16 bytes + 2 bytes + 1020 bytes

      如果我的理解是正确的,那么剩下的字节现在将传递给下一个处理程序decode,它将读取长度指示符并将有效负载(1020字节)传递给List<Object> out ,但我必须弄错。

      LengthFieldBasedFrameDecoder方法再次称为 ,并且添加到SimpleChannelHanlder的1022个字节相同。

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

      decode

      这是否意味着在List<Object> out之前调用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的副本添加到ByteBuf

      任何帮助/建议/批评都将受到赞赏,我希望以最干净的方式做到这一点。

      这是我的readerIndex = 0方法:

      List<Object> out

      注意:我正在阅读Netty in Action MEAP v8

2 个答案:

答案 0 :(得分:3)

  

这是否意味着在readBytes == 0之前调用解码?

基本上,是的。 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
}

MyDecoder decoder = new MyDecoder();
decoder.setSingleDecode(true);

这将在解码实现解码之后停止循环。 现在,您将使用LengthFieldBasedFrameDecoder添加到ByteBuf列表的out来调用SimpleChannelInboundHandler。 帧解码按照您的描述工作,无需将副本添加到列表中。 将使用有效负载帧调用msg作为AttributeMap

但是,您无法通过SimpleChannelInboundHandler中的ChannelHandlerContext阅读标题 因为每个频道处理程序的decoder是不同的,所以不分享atrributes。

解决此问题的一种方法是为此使用事件。 在Header中,不要将AttributeMap添加到// 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; } }

ChannelInboundHandlerAdapter

另一种方法是将两个对象发送到管道并使用 SimpleChannelInboundHandler代替decoder。 在Header中,不是将AttributeMap添加到out,而是将其添加到// ... out.add(header); out.add(in);

ChannelInboundHandler

然后,像这样写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只是忽略了不是ByteBuf的消息, 所以你的Header只会传递它(假设它没有实现ChannelInboundHandler)和 到达你的ChannelInboundHandler。然后,消息将被解码为 有效负载框架并传递给您的{{1}}。

答案 1 :(得分:0)

作为knutwalker's回答的后续跟进:我找到了一种替代方法,可以让那些使用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));
}

Ian2thedv关注字节复制的效率,但是当readableBytes超过你的消息长度时,它是不可避免的,你不能只是out.add(in)。

相关问题