Codec 编码与解码


1. 什么是 Codec

从网络传输的角度来讲,数组总是以字节的格式在网络之中进行传输

  • 每当源主机发送数据到目标主机时,数据会从本地格式被转换成字节进行传输,这种转换被称为编码,编码的逻辑由 编码器 处理。
  • 每当目标主机接受来自源主机的数据时,数据会从字节转换为我们需要的格式,这种转换被称为解码,解码的逻辑由 解码器 处理。

编写一个网络应用程序需要实现某种 codec (编解码器),codec 由两部分组成:decoder (解码器) 和 encoder (编码器)

💡 其实就是序列化和反序列化

在Netty中,编码解码器实际上是 ChannelOutboundHandlerChannelInboundHandler 的实现, 因为编码和解码都属于对数据的处理,由此看来,编码解码器被设计为 ChannelHandler 也就无可厚非。

2. 解码器

在Netty中,解码器是ChannelInboundHandler的实现,即处理入站数据。 解码器主要分为两种:

  • 将字节解码为Message消息: ByteToMessageDecoderReplayingDecoder
  • 将一种消息解码为另一种消息: MessageToMessageDecoder

① ByteToMessageDecoder

ByteToMessageDecoder用于将字节解码为消息,如果我们想自定义解码器,就需要继承这个类并实现decode方法。 decode方法是自定解码器必须实现的方法,它被调用时会传入一个包含了数据的ByteBuf和一个用来添加解码消息的List。 对decode方法的调用会重复进行,直至确认没有新元素被添加到该ListByteBuf没有可读字节为止。最后,如果List不为空, 那么它的内容会被传递给ChannelPipeline中的下一个ChannelInboundHandler

下面是ByteToMessageDecoder的编程模型:

// 自定义解码器,继承 ByteToMessageDecoder
public class ToIntegerDecoder extends ByteToMessageDecoder {  
	
    // 重写 decode 方法
    @Override
    public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
            throws Exception {
        // 检查ByteBuf是否仍有4个字节可读
        if (in.readableBytes() >= 4) {  
            out.add(in.readInt());  // 从ByteBuf读取消息到List中
        }
    }
}

上面这种编程模式很简单,但是在读取ByteBuf之前验证其是否可读的步骤显得有些多余,所以可以使用ReplayingDecoder 来解决这个问题。

② ReplayingDecoder

ReplayingDecoder扩展了ByteToMessageDecoder,这使得我们不再需要检查ByteBuf,因为ReplayingDecoder 自定义了ByteBuf的实现:ReplayingDecoderByteBuf,这个包装后的ByteBuf在内部会自动检查是否可读。以下是 ReplayingDecoderByteBuf的内部实现:

image-20220327115657845

虽然ReplayingDecoderByteBuf可以自动检查可读性,但是对于某些操作并不支持,会抛出 UnsupportedOperationException异常。其编程模型如下:

// 自定义解码器,继承 ReplayingDecoder
public class ToIntegerDecoder2 extends ReplayingDecoder<Void> {
    @Override
    public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
            throws Exception {
        out.add(in.readInt());// 从ByteBuf读取消息到List中
    }
}

③ MessageToMessageDecoder

MessageToMessageDecoder用于将一种类型的消息解码另一种类型的消息,如从数据传输对象 DTO 转为 Java 对象 POJO。 这是 MessageToMessageDecoder 的原型:

public abstract class MessageToMessageDecoder<I> extends ChannelInboundHandlerAdapter

MessageToMessageDecoder 的泛型 I 定义了我们需要将消息转换成何种类型。 和ByteToMessageDecoder一样,自定义MessageToMessageDecoder的解码器也需要实现其decode方法。

以下是它的编程模型:

public class IntegerToStringDecoder extends MessageToMessageDecoder<Integer> {

    @Override
    public void decode(ChannelHandlerContext ctx, Integer msg, List<Object> out)
            throws Exception{
        out.add(String.valueOf(msg));
     }
}

3. 编码器

在Netty中,编码器是ChannelOutboundHandler的实现,即处理出站数据。 编码器同样分为两种:

  • 将消息编码为字节: MessageToByteEncoder
  • 将消息编码为消息: MessageToMessageEncoder

① MessageToByteEncoder

MessageToByteEncoder用于将消息编码为字节,如果我们需要自定编码器,就需要继承它并实现它的encode方法。 encode方法是自定义编码器必须实现的方法,它被调用时会传入相应的数据和一个存储数据的ByteBuf。 在encode被调用之后,该ByteBuf会被传递给ChannelPipeline中下一个ChannelOutboundHandler

以下是MessageToByteEncoder的编程模型:

// 自定义编码器,继承 MessageToByteEncoder
public class ShortToByteEncoder extends MessageToByteEncoder<Short>{  

    @Override
    public void encode(ChannelHandlerContext ctx , Short data, ByteBuf out)
            throws Exception {
        out.writeShort(data);// 将data写入ByteBuf   
    }
}

② MessageToMessageEncoder

MessageToMessageEncoder用于将一种类型的消息编码另一种类型的消息,其原型和 MessageToMessageDecoder相似,所以这里也不再细说。

4. 编解码器 Codec

上面的内容讲的是单独的编码器和解码器,编码器处理出站数据,是ChannelOutboundHandler的实现, 解码器负责处理入站数据,是ChannelInboundHandler的实现。

除了编码器和解码器,Netty还提供了集编码与解码 于一身的编解码器 ByteToMessageCodecMessageToMessageCodec,它们同时实现了ChannelInboundHandlerChannelOutboundHandler,其结构如下:

image-20220327115705899

虽然使用编码解码器可以同时编码和解码数据,但这样不利于代码的可重用性。 相反,单独的编码器和解码器最大化了代码的可重用性和可扩展性,所以我们应该优先考虑分开使用二者

引用

《Netty 实战(精髓)》


文章作者: Gtwff
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Gtwff !
  目录