Netty Codec 编解码器
Netty 对于解决数据从一种特定协议格式到另一种格式的转换(如二进制字节和POJO之间地转换),提供了 codecs (编解码器) 组件来处理的。使用 Netty 的 codecs ,可以很方便地为各种不同地协议编写编解码器;
- 解码器:负责处理入站数据,将消息从字节或其他序列形式转成指定的消息对象;
- 编码器:负责处理出站数据,将消息对象转化为字节或其他序列形式;
消息被编码后解码后会自动通过
ReferenceCountUtil.release(message) 释放,如果不想释放消息可以使用
ReferenceCountUtil.retain(message),这将会使引用数量增加而没有消息发布,大多数时候不需要这么做。
Decoder 解码器
Decoder 主要用于入站处理,Netty 中的提供的 Decoder 分为以下 2 类:
- 字节到消息解码(byte-to-message):ByteToMessageDecoder 、ReplayingDecoder 等;
- 消息到消息解码(message-to-message):MessageToMessageDecoder 等;
Decoder 是 ChannelInboundHandler 的抽象实现,所以 Decoder 的使用方法类似于 ChannelInboundHandler ,可以在 ChannelPipeline 中直接加入 Decoder ,就是可实现入站信息的编码;
ByteToMessageDecoder
用于将字节转为消息(或其他字节序列),其中有 2 个需要覆盖的比较重要的方法如下:
decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) | 解码方法,将一个 BytedBuf 解码为一个 List<Object> 对象,并传递给下一个 ChannelHandler; |
decodeLast(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) | 当 Channel 处于 inactive 状态时,会触发该方法一次; |
以下示例中,每次从入站的 ByteBuf 中读取 4 个字节,编码为 int,并添加到一个 List,当decode方法执行结束后,out 包含的数据就会转发到下一个 ChannelInboundHandler 中;
public class ToIntegerDecoder extends ByteToMessageDecoder { //1
public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
throws Exception {
if (in.readableBytes() >= 4) { //当可读字节达到 4 个时,读取这 4 个字节,并编码为一个 Integer,并添加到 out 传递到下一个 ChannelInboundHandler
out.add(in.readInt());
}
}
}
ReplayingDecoder
ReplayingDecoder 是 byte-to-message 解码的一种特殊的抽象基类,读取缓冲区的数据之前需要检查缓冲区是否有足够的字节,使用 ReplayingDecoder 就无需自己检查;若 ByteBuf 中有足够的字节,则会正常读取;若没有足够的字节则会停止解码;
以下示例中使用 ReplayingDecoder 改写以上的示例:
public class ToIntegerDecoder2 extends ReplayingDecoder<Void> {
public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
throws Exception {
out.add(in.readInt());
}
}
※此外 Decoder 还有其他丰富的 Decoder,如:io.netty.handler.codec.LineBasedFrameDecoder 通过结束控制符("\n" 或 "\r\n").解析入站数据io.netty.handler.codec.http.HttpObjectDecoder 用于 HTTP 数据解码;
MessageToMessageDecoder
用于从一种消息解码为另外一种消息(例如,POJO 到 POJO),同样最简单实现 MessageToMessageDecoder 只需覆盖 decode 方法即可;
以下示例中实现 IntegerToStringDecoder,用于将 Integer 转化为 String 格式;
public class IntegerToStringDecoder extends MessageToMessageDecoder<Integer> {
public void decode(ChannelHandlerContext ctx, Integer msg, List<Object> out) throws Exception {
out.add(String.valueOf(msg));
}
}
解码时处理长帧
为了避免过长帧导致内存溢出,可以在解码器里设置一个最大字节数阈值,如果超出,将抛出 TooLongFrameException (并由 ChannelHandler.exceptionCaught() 捕获)。然后由译码器的用户决定如何处理它。虽然一些协议,比如 HTTP、允许这种情况下有一个特殊的响应,有些可能没有,此时唯一的选择可能就是关闭连接;
如以下 SafeByteToMessageDecoder,当帧长度大于 1024 字节时,会抛出 TooLongFrameException 通知其他 ChannelHandler 对其进行处理:
public class SafeByteToMessageDecoder extends ByteToMessageDecoder { //1
private static final int MAX_FRAME_SIZE = 1024;
public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
int readable = in.readableBytes();
if (readable > MAX_FRAME_SIZE) {
in.skipBytes(readable); //当帧过长时,跳过该帧,并通知其他 ChannelHandler
throw new TooLongFrameException("Frame too big!");
}
}
}
Encoder 编码器
Encoder 主要用于出站处理,Netty 中的提供的 Encoder 分为以下 2 类:
- 消息到字节编码(message-to-byte):MessageToByteDecoder 等;
- 消息到消息编码(message-to-message):MessageToMessageDecoder 等;
Encoder 是 ChannelOutboundHandler 的抽象实现,所以 Decoder 的使用方法类似于 ChannelOutboundHandler ,可以在 ChannelPipeline 中直接加入 Encoder ,就是可实现出站信息的解码;
MessageToByteEncoder
以下示例实现一个 IntegerToByteEncoder,用于将 Integer 类型消息转化为 bytes,并传递给下一个 ChannelOutboundHandler;
public class IntegerToByteEncoder extends MessageToByteEncoder<Integer> {
public void encode(ChannelHandlerContext ctx, Integer msg, ByteBuf out) throws Exception {
out.writeInteger(msg);
}
}
MessageToMessageEncoder
以下实现一个 StringToIntegerEncoder ,用于将 String 编码,并传递给下一个 ChannelOutboundHandler;
public class StringToIntegerEncoder extends MessageToMessageEncoder<Integer> {
public void encode(ChannelHandlerContext ctx, String msg, List<Object> out) throws Exception {
out.add(Integer.parseInt(msg));
}
}
Codec 编解码器
以上的 Decoder 和 Encoder 是分开的,Netty 也提供了 Codec 编解码类,同时可以支持编码、解码过程;
Netty 的编解码器也可以分为 byte-to-message,message-to-message 两类,典型的就是 ByteToMessageCodec、MessageToMessageCodec,实现一个 Codec 最简单的方式只需要覆盖其 decode,encode 方法即可;
但是结合编码器解码器在一起可能会增加耦合度,降低代码的重用性,Netty 提供了 CombinedChannelDuplexHandler 用于组装一个 Decoder 和 Encoder 为一个编解码器;
如下示例:
//入站解码器
public class ByteToCharDecoder extends ByteToMessageDecoder { //1
public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
throws Exception {
if (in.readableBytes() >= 2) { //2
out.add(in.readChar());
}
}
}
//出站编码器
public class CharToByteEncoder extends MessageToByteEncoder<Character> { //1
public void encode(ChannelHandlerContext ctx, Character msg, ByteBuf out)
throws Exception {
out.writeChar(msg); //2
}
}
//组合以上编码器、解码器
public class CombinedByteCharCodec extends CombinedChannelDuplexHandler<ByteToCharDecoder, CharToByteEncoder> {
public CombinedByteCharCodec() {
super(new ByteToCharDecoder(), new CharToByteEncoder());
}
}