Java Netty:从管道、协议封装中动态添加和删除帧解码器

Java Netty:从管道、协议封装中动态添加和删除帧解码器,java,netty,Java,Netty,我已经为Netty 3.3.1-Final工作了三周了。 我的协议有3个步骤,每个步骤需要不同的帧解码器: 读参数 传输一些数据 数据管道的相互关闭 我经历了很多我无法理解的“阻塞”问题。在阅读org.jboss.netty.example.portunification示例时,我终于发现在尝试动态更改帧解码器时出现了一些缓冲区问题:在更改下一个帧解码器时,一个帧解码器的缓冲区(可能)不是空的 在Netty中有没有办法轻松做到这一点?我必须改变我的协议吗?我是否需要编写一个大型帧解码器并管理

我已经为Netty 3.3.1-Final工作了三周了。 我的协议有3个步骤,每个步骤需要不同的帧解码器:

  • 读参数
  • 传输一些数据
  • 数据管道的相互关闭
我经历了很多我无法理解的“阻塞”问题。在阅读org.jboss.netty.example.portunification示例时,我终于发现在尝试动态更改帧解码器时出现了一些缓冲区问题:在更改下一个帧解码器时,一个帧解码器的缓冲区(可能)不是空的

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

今天我想到了一个帧解码器(下面的代码),目的是热添加和删除一些帧解码器,你怎么看

谢谢你的帮助

雷诺

-----------帧解码器运行器类--------------

/**
*此帧解码器能够将未使用的字节从一个解码器转发到下一个解码器。它提供
*替换管道内帧解码器的安全方法。
*仅仅从管道中动态添加和删除FrameDecoder是不安全的,因为存在风险
*帧解码器缓冲区内的未读字节数,您不想删除。
*/
公共类FrameDecoderRunnizer扩展了FrameDecoder{
私有最终方法FrameDecodeCodeMethod;
易变布尔跳跃=假;
LastFrameEventHandler事件处理程序;
链接列表条目;
条目=null;
公共FrameDecodeRunnizer(LastFrameEventHandler事件处理程序){
this.eventHandler=eventHandler;
this.entries=新建LinkedList();
试一试{
this.frameDecoderCodeMethod=FrameDecoder.class.getMethod(“decode”,ChannelHandlerContext.class,Channel.class,ChannelBuffer.class);
}catch(NoSuchMethodException-ex){
抛出新的运行时异常(ex);
}catch(SecurityException-ex){
抛出新的运行时异常(ex);
}
}
public void addLast(帧解码器、最后帧标识符){
addLast(新条目(解码器、标识符));
}
私有对象callDecode(帧解码器解码器、ChannelHandlerContext ctx、通道通道、通道缓冲区)引发异常{
返回FrameDecoderCodeMethod.invoke(解码器、ctx、通道、缓冲区);
}
@凌驾
受保护对象解码(ChannelHandlerContext ctx、通道通道、通道缓冲区)引发异常{
if(entry==null&&!entries.isEmpty()){
entry=entries.getFirst();
}
if(条目==null){
返回缓冲区;//无帧,无解码
}
//执行解码操作
Object obj=callDecode(entry.getDecoder(),ctx,channel,buffer);
if(obj!=null&&entry.getIdentifier().isLastFrame(obj)){
//火灾事件
eventHandler.lastObjectDecoded(entry.getDecoder(),obj);
entry=null;
}
返回obj;
}
/**
*当当前解码器更改为下一个解码器时,可以使用此接口执行某些操作。
*这对于更改管道中的某些上层处理程序非常有用。
*/
公共接口LastFrameEventHandler{
公共无效lastObjectDecoded(帧解码器,对象obj);
}
公共接口LastFrameIdentifier{
/**
*如果在此帧之后,我们应该禁用此解码器,则为True。
*@param obj
*@返回
*/
公共抽象布尔isLastFrame(对象解码);
}
私人班级入学{
帧解码器;
LastFrameIdentifier标识符;
公共条目(帧解码器、LastFrameIdentifier标识符){
this.decoder=解码器;
this.identifier=标识符;
}
公共帧解码器getDecoder(){
返回解码器;
}
公共LastFrameIdentifier getIdentifier(){
返回标识符;
}
}
}

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

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

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

我也遇到过类似的问题,从管道中移除帧解码器似乎不会阻止调用它,而且,没有一种明显的方法可以让解码器表现得好像它不在链中一样:Netty坚持认为decode()至少读取一个字节,因此您不能简单地返回传入的ChannelBuffer,而返回null会停止进程
    /**
     * 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;
            }
        }
}
return new Object[] { firstMessage, buf.readBytes(buf.readableBytes()) };
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));
}