我们将编写一个 IntegerToStringDecoder 解码器来扩展 MessageToMessageDecoder,它的 decode() 方法会把 Integer 参数转换为 String 表示。和之前一样,解码的 String 将被添加到传出的 List 中,并转发给下一个 ChannelInboundHandler
public class IntegerToStringDecoder extends MessageToMessageEncoder<Integer> { @Override protected void encode(ChannelHandlerContext ctx, Integer msg, List<Object> out) throws Exception { //将 Integer 消息转换为它的 String 表示,并将其添加到输出的 List 中 out.add(String.valueOf(msg)); } } 1.4 TooLongFrameException由于 Netty 是一个异步框架,所以需要在字节可以解码之前在内存中缓冲它们。因此,不能让解码器缓冲大量的数据以至于耗尽可用的内存。为了解除这个常见的顾虑,Netty 提供了 TooLongFrameException 类,其将由解码器在帧超出指定的大小限制时抛出
为了避免这种情况,你可以设置一个最大字节数的阈值,如果超出该阈值,则会导致抛出一个 TooLongFrameException(随后会被 ChannelHandler.exceptionCaught() 方法捕获)。然后,如何处理该异常则完全取决于该解码器的用户。某些协议(如 HTTP)可能允许你返回一个特殊的响应。而在其他的情况下,唯一的选择可能就是关闭对应的连接
下面的示例使用 TooLongFrameException 来通知 ChannelPipeline 中的其他 ChannelHandler 发生了帧大小溢出的。需要注意的是,如果你正在使用一个可变帧大小的协议,那么这种保护措施将是尤为重要的
public class SafeByteToMessageDecoder extends ByteToMessageDecoder { public static final int MAX_FRAME_SIZE = 1024; @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { int readable = in.readableBytes(); // 检查缓冲区是否有超过 MAX_FRAME_SIZE 个字节 if (readable > MAX_FRAME_SIZE) { // 跳过所有的可读字节,抛出 TooLongFrameException 并通知 ChannelHandler in.skipBytes(readable); throw new TooLongFrameException("Frame too big!"); } //do something } } 2. 编码器编码器实现了 ChannelOutboundHandler,并将出站数据从一种格式转换为另一种格式,和我们方才学习的解码器的功能正好相反。Netty 提供了一组类,用于帮助你编写具有以下功能的编码器:
将消息编码为字节
将消息编码为消息
2.1 抽象类 MessageToByteEncoder前面我们看到了如何使用 ByteToMessageDecoder 来将字节转换为消息,现在我们使用 MessageToByteEncoder 来做逆向的事情
这个类只有一个方法,而解码器有两个。原因是解码器通常需要在 Channel 关闭之后产生最后一个消息(因此也就有了 decodeLast() 方法。显然这不适用于编码器的场景 —— 在连接被关闭之后仍然产生一个消息是毫无意义的
下述代码展示了 ShortToByteEncoder,其接受一个 Short 类型的实例作为消息,将它编码为Short的原子类型值,并将它写入 ByteBuf 中,其将随后被转发给 ChannelPipeline 中的 下一个 ChannelOutboundHandler。每个传出的 Short 值都将会占用 ByteBuf 中的 2 字节。
public class ShortToByteEncoder extends MessageToByteEncoder<Short> { @Override protected void encode(ChannelHandlerContext ctx, Short msg, ByteBuf out) throws Exception { // 将 Short 写入 ByteBuf out.writeShort(msg); } } 2.2 抽象类 MessageToMessageEncoderMessageToMessageEncoder 类的 encode() 方法提供了将入站数据从一个消息格式解码为另一种
下述代码使用 IntegerToStringEncoder 扩展了 MessageToMessageEncoder,编码器将每个出站 Integer 的 String 表示添加到了该 List 中
public class IntegerToStringEncoder extends MessageToMessageEncoder { @Override protected void encode(ChannelHandlerContext ctx, Object msg, List out) throws Exception { out.add(String.valueOf(msg)); } }抽象的编解码器类
虽然我们一直将解码器和编码器作为单独的实体讨论,但是你有时将会发现在同一个类中管理入站和出站数据和消息的转换是很有用的。Netty 的抽象编解码器类正好用于这个目的,因为它们每个都将捆绑一个解码器/编码器对,以处理我们一直在学习的这两种类型的操作。正如同你可能已经猜想到的,这些类同时实现了 ChannelInboundHandler 和 ChannelOutboundHandler 接口
为什么我们并没有一直优先于单独的解码器和编码器使用这些复合类呢?因为通过尽可能地将这两种功能分开,最大化了代码的可重用性和可扩展性,这是 Netty 设计的一个基本原则
1. 抽象类 ByteToMessageCodec让我们来研究这样的一个场景:我们需要将字节解码为某种形式的消息,可能是 POJO,随后再次对它进行编码。ByteToMessageCodec 将为我们处理好这一切,因为它结合了 ByteToMessageDecoder 以及它的逆向 —— MessageToByteEncoder