我使用EmbeddedChannel
来测试我的handlers
和codecs
以下列格式处理邮件:
+------------+------------------+----------------+
| Header | Payload Length | Payload |
| 16 bytes | 2 bytes | "Some data" |
+------------+------------------+----------------+
首先,我想要实现的目标:
AttributeMap
的{{1}}中以供日后使用; ChannelHandlerContext
用于路由消息。我使用以下处理程序:
ByteBuf
提取标题信息并将其添加到属性列表中。ByteToMessageCodec<ByteBuf>
读取有效负载长度并等待/检索整个帧。LengthFieldBasedFrameDecoder
将使用从属性列表中检索的标头对象来相应地路由有效负载。当邮件传递给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
答案 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)。