Netty 提供了丰富的解码器抽象基类,主要分为两类:
- 解码字节到消息(ByteToMessageDecoder 和 ReplayingDecoder)
- 解码消息到消息(MessageToMessageDecoder)
一、ByteToMessageDecoder
ByteToMessageDecoder 用于将字节转为信息(或其他字节序列)。方法如下:
在下面的例子中,我们将实现从入站 ByteBuf 读取每个整数并将其传递给 pipeline 中的下一个 ChannalInboundHandler。流程如下:
代码如下:
1 /** 2 * 读取四字节,解码成整形 3 * 继承于 ByteToMessageDecoder 4 * 5 */ 6 public class ToIntegerDecoder extends ByteToMessageDecoder { 7 8 @Override 9 protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { 10 if(in.readableBytes() >= 4) { // int是4字节 11 out.add(in.readInt()); // 添加到解码信息的List中 12 } 13 } 14 15 }
注意,一旦一个消息被编码或解码会自动调用 ReferenceCountUtil.release(message)。如果你稍后还需要用到这个引用,你可以调用 ReferenceCountUtil.retain(message)。
二、ReplayingDecoder
上面的例子读取缓冲区的数据之前需要检查缓冲区是否有足够的字节,使用 ReplayingDecoder 就无需自己检查。若 ByteBuf 中有足够的字节,则会正常读取;若没有足够的字节则会停止解码。如下:
1 /** 2 * 读取四字节,解码成整形 3 * 继承于ReplayingDecoder,不需要检查缓存区是否有足够的字节 4 * 5 */ 6 public class ToIntegerDecoder2 extends ReplayingDecoder<Void> { 7 8 @Override 9 protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { 10 out.add(in.readInt()); // 读取整形并添加到解码信息的List中 11 } 12 13 }
三、MessageToMessageDecoder
用于从一种消息解码为另一种消息(例如,POJO 到 POJO)。与上面类似,代码如下:
1 /** 2 * 将整形解码为字符串 3 * 继承于MessageToMessageDecoder 4 * 5 */ 6 public class IntegerToStringDecoder extends MessageToMessageDecoder<Integer> { 7 8 @Override 9 protected void decode(ChannelHandlerContext ctx, Integer msg, List<Object> out) throws Exception { 10 out.add(String.valueOf(msg)); // 将整形转换为字符串 11 } 12 13 }
四、在解码中处理太大的帧
Netty 是异步架构需要将缓冲区字节存在内存中,知道你能够解码它们。因此,你不能让你的解码器缓存太多的数据以免耗尽可用内存。下面为解决方案:
1 /** 2 * 在解码时处理太大的帧 3 * 4 */ 5 public class SafeByteToMessageDecoder extends ByteToMessageDecoder { 6 private static final int MAX_FRAME_SIZE = 1024; 7 8 @Override 9 protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { 10 int readable = in.readableBytes(); 11 if(readable > MAX_FRAME_SIZE) { // 缓冲区数据过大 12 in.skipBytes(readable); // 忽略所有可读的字节 13 // 抛出异常通知这个帧数据超长 14 throw new TooLongFrameException("帧数据超长"); 15 } 16 // TODO 数据编码 17 } 18 19 }
这种保护很重要,尤其是当你编码一个有可变帧大小的协议的时候。