netty基础06_编码器和解码器
对于网络编程需要实现某种 codec (编解码器);
网络数据通常以二进制进行传输;
codec的作用就是将原始字节数据与目标程序数据格式进行互转;
解码器Decoder用来处理入站数据;例如将二进数据转java对象;
编码器 Encoder负责处理出站数据,比如将出栈数据转换成适合传输的字节流;
1. Decoder(解码器)
decoder 负责将“入站”数据从一种格式转换到另一种格式;
Netty的decoder实现了入站处理器接口ChannelInboundHandler;
decoder分为两类:
解码字节到消息 -> ByteToMessageDecoder 和 ReplayingDecoder
解码消息到消息 -> MessageToMessageDecoder
0)关于解码器的引用计数器
引用计数需要特别的注意。
对于编码器和解码器来说:
一旦消息被编码或者解码,它就会被 ReferenceCountUtil.release(message)调用自动释放。
如果需要保留引用以便稍后使用,可以调用 ReferenceCountUtil.retain(message)方法。
这将会增加该引用计数,从而防止该消息被释放。
1) ByteToMessageDecoder
用于将字节转换成消息;
该抽象类有两个方法:
因为数据传到服务端有可能不是一次请求就能完成的,中间可能需要经过几次数据传输,并且每一次传输传多少数据也是不确定;
Decode方法时抽象方法,用来做解码的实现;
decodeLast方法默认实现为调用Decode方法;
可以重写decodeLast方法来标识一条完整的消息结束,比如用来产生一个 LastHttpContent 消息,表示一条http消息已经被解码完成。
需要注意的是,原子类型的 int 在被添加到 List 中时,会被自动装箱为 Integer;
例如:将4个字节的数据解码成Integer并在控制台输出
每次从入站的 ByteBuf 读取四个字节,解码成Integer,并添加到一个 List
当不能再添加数据到 list 时,它所包含的内容就会被发送到下个 ChannelInboundHandler
解码器:
public class NettyDecoder extends ByteToMessageDecoder{ @Override protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception { if(byteBuf.readableBytes() >= 4){ list.add(byteBuf.readInt()); //将4个字节转为整型 } } }
服务类:
public class NettyDecoderServer { public void server(int port) throws Exception{ ServerBootstrap bootstrap = new ServerBootstrap(); //服务端引导类 EventLoopGroup boss = new NioEventLoopGroup(); //需要两个EventLoopGroup,一个监听消息,一个处理消息 EventLoopGroup worker = new NioEventLoopGroup(); try { bootstrap.group(boss, worker) //引导 .channel(NioServerSocketChannel.class) .localAddress(port) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel sc) throws Exception { sc.pipeline().addLast(new NettyDecoder()); //管道中添加解码器 sc.pipeline().addLast(new SimpleChannelInboundHandler<Integer>() { //消息处理器,用来输出解码后的消息 @Override protected void channelRead0(ChannelHandlerContext channelHandlerContext, Integer integer) throws Exception { System.out.println("解码后=========>"+integer); } }); } }); ChannelFuture future = bootstrap.bind().sync(); future.channel().closeFuture().sync(); }finally { boss.shutdownGracefully().sync(); worker.shutdownGracefully().sync(); } } public static void main(String[] args) throws Exception{ NettyDecoderServer server = new NettyDecoderServer(); server.server(8000); } }
向服务端发送数据:
以16进制发送,两个4字节;
结果:
2) ReplayingDecoder
ReplayingDecoder 继承自 ByteToMessageDecoder;
ByteToMessageDecoder在解码前需要读取数据之前需要检查缓冲区 ByteBuf是否有足够的字节,很麻烦;
ReplayingDecoder无需自己检查;若ByteBuf中有足够的字节,则会正常读取;若没有足够的字节则会停止解码。
类声明:
public abstract class ReplayingDecoder<S> extends ByteToMessageDecoder
类型参数 S 指定了用于状态管理的类型,其中 Void 代表不需要状态管理;
使用ReplayingDecoder实现将数据转Integer:
和ByteToMessageDecoder类似,只不过不用检查字节是否足够了;
public class ToIntegerDecoder2 extends ReplayingDecoder<Void> { @Override public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { out.add(in.readInt()); } }
从ByteBuf中提取的int将会被添加到List中。
如果没有足够的字节可用,这个readInt()方法的实现将会抛出一个 io.netty.Signal其将在基类中被捕获并处理;
当有更多的数据可供读取时,该decode()方法将会被再次调用;
3) MessageToMessageDecoder
用于从一种消息解码为另外一种消息(例如,POJO 到 POJO)
public abstract class MessageToMessageDecoder<I>extends ChannelInboundHandlerAdapter
类型参数 I 指定了 decode()方法的输入参数 msg 的类型;
方法:
例如:将Integer转String的解码器
public class IntegerToStringDecoder extends MessageToMessageDecoder<Integer> { //由泛型决定待转换的消息类型 @Override public void decode(ChannelHandlerContext ctx, Integer msg, List<Object> out) throws Exception { out.add(String.valueOf(msg)); } }
netty提供的消息转换器HttpObjectAggregator就继承自MessageToMessageDecoder;
HttpObjectAggregator是http消息聚合器,用来将解码后产生的http消息整合成一条完整的http请求;
4) 解码时处理太大的帧
由于 Netty 是一个异步框架,所以需要在字节可以解码之前在内存中缓冲它们;
如果对缓冲区不加限制可能造成内存不足的后果;
为了解决这一问题 Netty 提供了一个TooLongFrameException
可以在解码器里设置一个最大字节数阈值,如果超出,就抛出TooLongFrameException
ChannelHandler.exceptionCaught()会捕获到该异常,然后决定如何处理;
public class SafeByteToMessageDecoder extends ByteToMessageDecoder { private static final int MAX_FRAME_SIZE = 1024; //定义阈值为1k @Override public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { int readable = in.readableBytes(); if (readable > MAX_FRAME_SIZE) { //如果缓冲区的可读字节大于1k则跳过并抛出帧数过大异常 in.skipBytes(readable); throw new TooLongFrameException("Frame too big!"); } // do something } }
2. Encoder(编码器)
encoder实现了 ChannelOutboundHandler接口;
用来把出站数据从一种格式转换到另外一种格式;
编码器包括两类:
编码从消息到字节 -> MessageToByteEncoder
编码从消息到消息 -> MessageToMessageEncoder
1)MessageToByteEncoder
只有一个需要实现的抽象方法;
解码器经常需要在 Channel 关闭时产生一个“最后的消息"而提供了decodeLast();
而对于编码器没有这个需求,在连接被关闭之后仍然产生一个消息是毫无意义的。;
例如:将short类型的出站消息编码成byte类型
编码器:
public class ShortToByteEncoder extends MessageToByteEncoder<Short> { @Override public void encode(ChannelHandlerContext ctx, Short msg, ByteBuf out) throws Exception { out.writeShort(msg); } }
其接受一个 Short 类型的实例作为消息;
将它编码为 Short 的原子类型值,并将它写入 ByteBuf 中;
其将随后被转发给 ChannelPipeline 中的下一个 ChannelOutboundHandler;
每个传出的 Short 值都将会占用 ByteBuf 中的 2 字节;
2) MessageToMessageEncoder
也是只有一个抽象方法encode;
例如:将出站消息从Integer转换成String
public class IntegerToStringEncoder extends MessageToMessageEncoder<Integer> { // @Override public void encode(ChannelHandlerContext ctx, Integer msg, List<Object> out) throws Exception { out.add(String.valueOf(msg)); // } }
3. Codec(编解码器)
Codec同时实现了ChannelInboundHandler 和 ChannelOutboundHandler接口;
相当于将编码器和解码器结合在一个类中;
分类:
ByteToMessageCodec
MessageToMessageCodec
1)ByteToMessageCodec
结合了ByteToMessageDecoder 以及它的逆向——MessageToByteEncoder;
使用场景:
我们需要将字节解码为某种形式的消息,可能是 POJO,随后再次对它进行编码为字节 。
2) MessageToMessageCodec
类签名:
public abstract class MessageToMessageCodec<INBOUND_IN,OUTBOUND_IN>
api:
decode()方法是将 INBOUND_IN类型的消 息转换 为 OUTBOUND_IN类型的 消息;
而encode()方法则进行它的逆向操作;
3)CombinedChannelDuplexHandler
解码器和编码器结合在一起可能会牺牲可重用性;
使用CombinedChannelDuplexHandler既能够避免这种惩罚, 又不会牺牲将一个解码器和一个编码器作为一个单独的单元部署所带来的便利性。
public class CombinedChannelDuplexHandler<I extends ChannelInboundHandler,
O extends ChannelOutboundHandler>
相当于是一个编码器和解码器的容器;
编码和解码的操作交给其中装配的编码器和解码器实例来执行;
使用:在构造方法中传入需要装入的编码器和解码器
public class CombinedByteCharCodec extends CombinedChannelDuplexHandler<ByteToCharDecoder, CharToByteEncoder> { public CombinedByteCharCodec() { super(new ByteToCharDecoder(), new CharToByteEncoder()); } }