Netty:从管道,协议封装动态添加和删除FrameDecoder

时间:2012-04-23 10:48:05

标签: java netty

我一直在与Netty 3.3.1-Final合作3周。 我的协议有3个步骤,每个步骤需要一个不同的 FrameDecoder

  • 阅读参数
  • 传输一些数据
  • 数据管道的相互关闭

我经历过许多我无法理解的“阻塞”问题。它最终在我看来,阅读 org.jboss.netty.example.portunification 示例,当我尝试动态更改我的FrameDecoder 时,我遇到了一些缓冲问题:一个缓冲区改变下一个时,FrameDecoder(可能)不是空的......

有没有办法在Netty中轻松完成?我必须更改我的协议吗?我是否需要编写一个大的FrameDecoder并管理状态? 如果是这样,如何避免使用公共子部分的不同协议之间的代码重复(例如“读取参数”)?

今天我想到了一个 FrameDecoderUnifier (下面的代码)的想法,目的是热添加和删除一些 FrameDecoder ,您怎么看? / p>

感谢您的帮助!

雷诺

----------- FrameDecoderUnifier类--------------

    /**
     * This FrameDecoder is able to forward the unused bytes from one decoder to the next one. It provides
     * a safe way to replace a FrameDecoder inside a Pipeline.
     * It is not safe to just add and remove FrameDecoder dynamically from a Pipeline because there is a risk
     * of unread bytes inside the buffer of the FrameDecoder you wan't to remove.
     */
    public class FrameDecoderUnifier extends FrameDecoder {

        private final Method frameDecoderDecodeMethod;
        volatile boolean skip = false;
        LastFrameEventHandler eventHandler;
        LinkedList<Entry> entries;
        Entry entry = null;

        public FrameDecoderUnifier(LastFrameEventHandler eventHandler) {
            this.eventHandler = eventHandler;
            this.entries = new LinkedList<Entry>();
            try {
                this.frameDecoderDecodeMethod = FrameDecoder.class.getMethod("decode", ChannelHandlerContext.class, Channel.class, ChannelBuffer.class);
            } catch (NoSuchMethodException ex) {
                throw new RuntimeException(ex);
            } catch (SecurityException ex) {
                throw new RuntimeException(ex);
            }
        }

        public void addLast(FrameDecoder decoder, LastFrameIdentifier identifier) {
            entries.addLast(new Entry(decoder, identifier));
        }

        private Object callDecode(FrameDecoder decoder, ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception {
            return frameDecoderDecodeMethod.invoke(decoder, ctx, channel, buffer);
        }

        @Override
        protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception {
            if (entry == null && !entries.isEmpty()) {
                entry = entries.getFirst();
            }

            if (entry == null) {
                return buffer; //No framing, no decoding
            }

            //Perform the decode operation
            Object obj = callDecode(entry.getDecoder(), ctx, channel, buffer);

            if (obj != null && entry.getIdentifier().isLastFrame(obj)) {
                //Fire event
                eventHandler.lastObjectDecoded(entry.getDecoder(), obj);
                entry = null;
            }
            return obj;
        }

        /**
         * You can use this interface to take some action when the current decoder is changed for the next one.
         * This can be useful to change some upper Handler in the pipeline.
         */
        public interface LastFrameEventHandler {

            public void lastObjectDecoded(FrameDecoder decoder, Object obj);
        }

        public interface LastFrameIdentifier {

            /**
             * True if after this frame, we should disable this decoder.
             * @param obj
             * @return 
             */
            public abstract boolean isLastFrame(Object decodedObj);
        }

        private class Entry {

            FrameDecoder decoder;
            LastFrameIdentifier identifier;

            public Entry(FrameDecoder decoder, LastFrameIdentifier identifier) {
                this.decoder = decoder;
                this.identifier = identifier;
            }

            public FrameDecoder getDecoder() {
                return decoder;
            }

            public LastFrameIdentifier getIdentifier() {
                return identifier;
            }
        }
}

2 个答案:

答案 0 :(得分:1)

我认为,应该避免使用基于某些状态切换内部解码器的帧解码器动态添加/删除上层处理程序,因为

  • 难以理解/调试代码
  • 处理程序没有明确定义的职责(这就是你正在删除/添加处理程序的原因吗?一个处理程序应该处理一个或多个(相关)类型的协议消息,而不是处理程序相同类型的消息)
  • 理想情况下,帧解码器仅提取协议帧,而不是基于状态对帧进行解码(此处帧解码器可以具有解码器的内部链以解码帧并发射具有解码消息的MessageEvent,上述处理器可以对解码的消息作出反应)

更新:在这里,我考虑了一种协议,其中每条消息都可以有唯一的标记/标识符,并且消息的末尾有明确标记(例如Tag Length Value帧格式)

答案 1 :(得分:1)

我遇到了类似的问题,因为从管道中删除帧解码器似乎并没有阻止它被调用,并且没有一种明显的方法可以让解码器表现得好像它不在chain:Netty坚持要求decode()读取至少一个字节,这样你就不能简单地返回传入的ChannelBuffer,而返回null会停止处理传入的数据,直到下一个数据包到达,从而停止协议解码过程。

首先:FrameDecoder的Netty 3.7文档实际上有一节“用管道中的另一个解码器替换解码器”。它说:

  

仅通过调用无法实现此目的   的ChannelPipeline#替换()

相反,它建议通过返回一个包装解码后的第一个数据包和接收到的其余数据的数组来传递数据。

return new Object[] { firstMessage, buf.readBytes(buf.readableBytes()) };

重要的是,必须在此之前启用“展开”,但这部分很容易被遗漏,因此不予解释。我能找到的最好的线索是Netty issue 132,它显然在FrameDecoders上产生了“展开”标志。如果为true,则解码器将以对下游处理程序透明的方式将这些数组解包到对象中。窥探源代码似乎证实这就是“展开”的含义。

其次:似乎有一种更简单的方法,因为该示例还显示了如何在管道下传递数据不变。例如,在完成其工作后,我的同步数据包FrameDecoder设置一个内部标志并将自己从管道中移除,正常返回解码的对象。设置标志后的任何后续调用都会像这样简单地传递数据:

protected Object decode(ChannelHandlerContext ctx,
                        Channel channel, ChannelBuffer cbuf) throws Exception {

    // Close the door on more than one sync packet being decoded
    if (m_received) {
        // Pass on the data to the next handler in the pipeline.
        // Note we can't just return cbuf as-is, we must drain it
        // and return a new one.  Otherwise Netty will detect that
        // no bytes were read and throw an IllegalStateException.
        return cbuf.readBytes(cbuf.readableBytes());
    }

    // Handle the framing
    ChannelBuffer decoded = (ChannelBuffer) super.decode(ctx, channel, cbuf);
    if (decoded == null) {
        return null;
    }

    // Remove ourselves from the pipeline now
    ctx.getPipeline().remove(this);
    m_received = true;

    // Can we assume an array backed ChannelBuffer?
    // I have only hints that we can't, so let's copy the bytes out.
    byte[] sequence = new byte[magicSequence.length];
    decoded.readBytes(sequence);

    // We got the magic sequence?  Return the appropriate SyncMsg
    return new SyncMsg(Arrays.equals(sequence, magicSequence));
}

从LengthFieldBasedFrameDecoder派生的解码器保持在下游并处理所有后续数据成帧。到目前为止对我有用。