写点什么

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

发布于: 2 小时前

简介在之前的系列文章中,我们提到了 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 IntegerHeaderFrameDecoderextends 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 IntegerHeaderFrameDecoderextends 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/


最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!


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

发布于: 2 小时前阅读数: 4
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
netty系列之:自定义编码和解码器要注意的问题