关于netty:netty系列之自定义编码和解码器要注意的问题

11次阅读

共计 3882 个字符,预计需要花费 10 分钟才能阅读完成。

简介

在之前的系列文章中,咱们提到了 netty 中的 channel 只承受 ByteBuf 类型的对象,如果不是 ByteBuf 对象的话,须要用编码和解码器对其进行转换,明天来聊一下 netty 自定义的编码和解码器实现中须要留神的问题。

自定义编码器和解码器的实现

在介绍 netty 自带的编码器和解码器之前,通知大家怎么实现自定义的编码器和解码器。

netty 中所有的编码器和解码器都是从 ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter 衍生而来的。

对于 ChannelOutboundHandlerAdapter 来说,最重要的两个类是 MessageToByteEncoder 和 MessageToMessageEncoder。

MessageToByteEncoder 是将音讯编码成为 ByteBuf,这个类也是咱们自定义编码最罕用的类,间接继承这个类并实现 encode 办法即可。留神到这个类有一个泛型,这个泛型指定的就是音讯的对象类型。

例如咱们想将 Integer 转换成为 ByteBuf,能够这样写:

       public class IntegerEncoder extends MessageToByteEncoder<Integer> {
            @Override
           public void encode(ChannelHandlerContext ctx, Integer msg, ByteBuf out)
                   throws Exception {out.writeInt(msg);
           }
       }

MessageToMessageEncoder 是在音讯和音讯之间进行转换,因为音讯并不能间接写入到 channel 中,所以须要和 MessageToByteEncoder 配合应用。

上面是一个 Integer 到 String 的例子:

       public class IntegerToStringEncoder extends
               MessageToMessageEncoder<Integer> {
  
            @Override
           public void encode(ChannelHandlerContext ctx, Integer message, List<Object> out)
                   throws Exception {out.add(message.toString());
           }
       }

对于 ChannelInboundHandlerAdapter 来说,最重要的两个类是 ByteToMessageDecoder 和 MessageToMessageDecoder。

ByteToMessageDecoder 是将 ByteBuf 转换成对应的音讯类型,咱们须要继承这个类,并实现 decode 办法,上面是一个从 ByteBuf 中读取所有可读的字节,并将后果放到一个新的 ByteBuf 中,

       public class SquareDecoder extends ByteToMessageDecoder {
            @Override
           public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
                   throws Exception {out.add(in.readBytes(in.readableBytes()));
           }
       }
   

MessageToMessageDecoder 是音讯和音讯之间的转换,同样的只须要实现 decode 办法即可,如下从 String 转换到 Integer:

       public class StringToIntegerDecoder extends
               MessageToMessageDecoder<String> {
  
            @Override
           public void decode(ChannelHandlerContext ctx, String message,
                              List<Object> out) throws Exception {out.add(message.length());
           }
       }

ReplayingDecoder

下面的代码看起来很简略,然而在实现的过程中还有一些问题要留神。

对于 Decoder 来说,咱们从 ByteBuf 中读取数据,而后进行转换。然而在读取的过程中,并不知道 ByteBuf 中数据的变动状况,有可能在读取的过程中 ByteBuf 还没有筹备好,那么就须要在读取的时候对 ByteBuf 中可读字节的大小进行判断。

比方咱们须要解析一个数据结构,这个数据结构的前 4 个字节是一个 int,示意前面 byte 数组的长度,咱们须要先判断 ByteBuf 中是否有 4 个字节,而后读取这 4 个字节作为 Byte 数组的长度,而后再读取这个长度的 Byte 数组,最终失去要读取的后果,如果其中的某一步呈现问题,或者说可读的字节长度不够,那么就须要间接返回,期待下一次的读取。如下所示:

   public class IntegerHeaderFrameDecoder extends ByteToMessageDecoder {
  
      @Override
     protected void decode(ChannelHandlerContext ctx,
                             ByteBuf buf, List<Object> out) throws Exception {if (buf.readableBytes() < 4) {return;}
  
       buf.markReaderIndex();
       int length = buf.readInt();
  
       if (buf.readableBytes() < length) {buf.resetReaderIndex();
          return;
       }
  
       out.add(buf.readBytes(length));
     }
   }

这种判断是比较复杂同时也是可能出错的,为了解决这个问题,netty 提供了 ReplayingDecoder 用来简化下面的操作,在 ReplayingDecoder 中,假如所有的 ByteBuf 曾经处于筹备好的状态,间接从两头读取即可。

下面的例子用 ReplayingDecoder 重写如下:

   public class IntegerHeaderFrameDecoder
        extends ReplayingDecoder<Void> {
  
     protected void decode(ChannelHandlerContext ctx,
                             ByteBuf buf, List<Object> out) throws Exception {out.add(buf.readBytes(buf.readInt()));
     }
   }

它的实现原理是去尝试读取对应的字节信息,如果没有读到,则抛出异样,ReplayingDecoder 接管到异样之后,会从新调用 decode 办法。

尽管 ReplayingDecoder 应用起来非常简单,然而它有两个问题。

第一个问题是性能问题,因为会去反复调用 decode 办法,如果 ByteBuf 自身并没有变动,就会导致反复 decode 同一个 ByteBuf,照成性能的节约。解决这个问题就是在在 decode 的过程中分阶段进行,比方下面的例子中,咱们须要先读取 Byte 数组的长度,而后再读取真正的 byte 数组。所以在读完 byte 数组长度之和,能够调用 checkpoint() 办法做一个保留点,下次再执行 decode 办法的时候就能够跳过这个保留点,持续后续的执行过程,如下所示:

   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.");
       }
     }
   }

第二个问题是同一个实例的 decode 办法可能会被调用屡次,如果咱们在 ReplayingDecoder 中有公有变量的话,则须要思考对这个公有变量的荡涤工作,防止屡次调用造成的数据净化。

总结

通过继承下面的几个类,咱们就能够本人实现编码和解码的逻辑了。然而如同还有点问题,自定义编码和解码器是不是太简单了?还须要判断要读取的 byte 数组的大小。有没有更加简略的办法呢?

有的,敬请期待 netty 系列的下一篇文章:netty 自带的编码器和解码器.

本文的例子能够参考:learn-netty4

本文已收录于 http://www.flydean.com/14-netty-cust-codec/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!

正文完
 0