【第23篇】Netty的ReplayingDecoder源码分析与特性解读

1、 ReplayingDecoder

  • ReplayingDecoder S 是指一个枚举,如果不需要指定Void即可
  • ReplayingDecoder 不需要判断(ByteBuf)中的数量是否足够
public abstract class ReplayingDecoder<S> extends ByteToMessageDecoder {
ReplayingDecoder
  • 可见ByteToMessageDecoder的子类。类定义中的泛型 S 是一个用于记录解码状态的状态机枚举类,在state(S s)、checkpoint(S s)等方法中会用到。在简单解码时也可以用java.lang.Void来占位。
  • 与ByteToMessageDecoder不同,该类可以在接收到所需要长度的字节之后再调用decode方法,而不用一遍又一遍的手动检查流中的字节长度
  • 从源码上看ReplayingDecoder重写了ByteToMessageDecoder的callDecode()方法
protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
       replayable.setCumulation(in);
       try {
           while (in.isReadable()) {
               int oldReaderIndex = checkpoint = in.readerIndex();
               int outSize = out.size();

               if (outSize > 0) {
                   fireChannelRead(ctx, out, outSize);
                   out.clear();//清除
                   //在继续解码之前,检查这个处理程序是否已被删除。
                   //如果它被移除,继续在缓冲区上操作是不安全的。
                   // See:
                   // - https://github.com/netty/netty/issues/4635
                   if (ctx.isRemoved()) {
                       break;
                   }
                   outSize = 0;
               }

               S oldState = state;
               int oldInputLength = in.readableBytes();
               try {
                   //解码移除再入保护
                   decodeRemovalReentryProtection(ctx, replayable, out);
                   //在继续循环之前,检查是否删除了这个处理程序。
                   //如果它被移除,继续在缓冲区上操作是不安全的。
                   // See https://github.com/netty/netty/issues/1664
                   if (ctx.isRemoved()) {
                       break;
                   }

                   if (outSize == out.size()) {
                       if (oldInputLength == in.readableBytes() && oldState == state) {
                           throw new DecoderException(
                                   StringUtil.simpleClassName(getClass()) + ".decode() must consume the inbound " +
                                   "data or change its state if it did not decode anything.");
                       } else {
                           //以前的数据已被丢弃或导致状态转换。
                           //也许它还在继续读。
                           continue;
                       }
                   }
               } catch (Signal replay) {
                   replay.expect(REPLAY);
                   //在继续循环之前,检查是否删除了此处理程序。
                   //如果它被移除,继续在缓冲区上操作是不安全的。
                   // See https://github.com/netty/netty/issues/1664
                   if (ctx.isRemoved()) {
                       break;
                   }

                   //返回到这个检查点(或是旧的位置)和重试
                   int checkpoint = this.checkpoint;
                   if (checkpoint >= 0) {
                       in.readerIndex(checkpoint);
                   } else {
                       
                   }
                   break;
               }

               if (oldReaderIndex == in.readerIndex() && oldState == state) {
                   throw new DecoderException(
                          StringUtil.simpleClassName(getClass()) + ".decode() method must consume the inbound data " +
                          "or change its state if it decoded something.");
               }
               if (isSingleDecode()) {
                   break;
               }
           }
       } catch (DecoderException e) {
           throw e;
       } catch (Throwable cause) {
           throw new DecoderException(cause);
       }
   }

2、ReplayingDecoder如何工作?

  • ReplayingDecoder传递一个专门的ByteBuf实现,当缓冲区中没有足够的数据时,这个实现会抛出某种类型的错误。在上面的IntegerHeaderFrameDecoder中,您只是假设在调用buf.readInt()时,缓冲区中有4个或更多字节。如果缓冲区中确实有4个字节,它将像您期望的那样返回整数报头。否则,将引发错误并将控制返回到ReplayingDecoder。如果ReplayingDecoder捕捉到错误,那么它会将缓冲区的readerIndex倒回“初始”位置(即缓冲区的开始),并在缓冲区接收到更多数据时再次调用decode(..)方法。
  • 请注意,ReplayingDecoder总是抛出相同的缓存错误实例,以避免每次抛出时创建新错误并填充其堆栈跟踪的开销。

3、ReplayingDecoder如何提高性能

  • 幸运的是,使用checkpoint()方法可以显著提高复杂解码器实现的性能。checkpoint()方法更新缓冲区的“初始”位置,以便重新播放解码器将缓冲区的readerIndex回滚到调用checkpoint点()方法的最后位置。

4、ReplayingDecoder使用枚举Enum调用 checkpoint(T)

  • 即使您可以只使用checkpoint()方法并自己管理解码器的状态,但是管理解码器状态的最简单方法是创建一个表示解码器当前状态的Enum类型,并在状态发生变化时调用checkpoint(T)方法。根据要解码的消息的复杂性,可以有任意多个状态:
 public enum MyDecoderState {
     READ_LENGTH,
     READ_CONTENT;
   }
   
  public class IntegerHeaderFrameDecoder extends ReplayingDecoder<MyDecoderState> {
  
     private int length;
  
     public IntegerHeaderFrameDecoder() {
       // Set the initial state.
       super(MyDecoderState.READ_LENGTH);
     }
  
     @Override
     protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) throws Exception {
       switch (state()) {
        case READ_LENGTH:
            length = buf.readInt();
            checkpoint(MyDecoderState.READ_CONTENT);
        case READ_CONTENT:
            ByteBuf frame = buf.readBytes(length);
            checkpoint(MyDecoderState.READ_LENGTH);
            out.add(frame);
         break;
       default:
         throw new Error("Shouldn't reach here.");
       }
     }
   }

5、ReplayingDecoder调用没有参数的checkpoint()方法

  • 管理解码器状态的另一种方法是自己管理它。
  public class IntegerHeaderFrameDecoder extends ReplayingDecoder<Void> {}

6、 ReplayingDecoder用管道中的另一个解码器替换一个解码器

  • 如果您要编写一个协议多路复用器,您可能需要用另一个重放解码器(ByteToMessageDecoder或MessageToMessageDecoder,实际的协议解码器)替换ReplayingDecoder(协议检测器)。仅仅通过调用ChannelPipeline是不可能实现这一点的。替换(ChannelHandler, String, ChannelHandler),但需要一些额外的步骤:
 public class FirstDecoder extends ReplayingDecoder<Void> {
  
        @Override
       protected void decode(ChannelHandlerContext ctx,  ByteBuf buf, List<Object> out) {
           ...
           // Decode the first message
           Object firstMessage = ...;
  
           // Add the second decoder
           ctx.pipeline().addLast("second", new SecondDecoder());
  
           if (buf.isReadable()) {
               // Hand off the remaining data to the second decoder
               out.add(firstMessage);
               out.add(buf.readBytes(super.actualReadableBytes()));
           } else {
               // Nothing to hand off
               out.add(firstMessage);
           }
           // Remove the first decoder (me)
           ctx.pipeline().remove(this);
       }
}       

7、开发过程中编写解码器与编码器的建议(注意点)

  • 建议要么解码器,要么编码器,遵循单一原则,不会那么困扰,方便开发与维护
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容