Netty---编解码(使用)

★  
   说明:《Netty,zookeeper,redis》学习笔记
    Netty底层需要从ByteBuf读取二进制数据,传入流水线处理器中,处理器将二进制信息解码成为pojo对象。这个解码的操作需要Netty的Decoder解码去完成。出战的时候,又需要把Pojo对象,转成ByteBuf中的二进制数据,然后通过通道发送给对方。

一,Decoder 使用

    Netty的内置解码器:ByteToMessageDecoder,将二进制数据转成pojo对象。所有的Netty中的解码器都是INbound入站式的。
1.ByteToMessageDecoder
     它是一个抽象类,实现了解码的基本逻辑和流程。具体的实习需要我们自己来处理,它存在一个容器List<Object>用来存放解码出来的Pojo对象,交给后面的处理器进行运用。
自定义整数解码器实现:
public class Byte2IntegerDecoder extends ByteToMessageDecoder {
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
        while (byteBuf.readableBytes()>=4){
            int i=byteBuf.readInt();
            System.out.println("解码出一个整数:"+i);
            list.add(i);
        }
    }
}
   它的使用:放到流水线中,将解码出来的结果,交给后面的handler进行处理,下方代码添加了两个handler,可见第一个就是解码器,解析出结果pojo,放到list<Object>中,如何把结果一个一个的传入到第二个handler,它的方法参数msg,就是我们解码出来的对象,使用强制类型转换,切换到我们需要的类型上面,进行解析
public class NettyDiscardHandler extends ChannelInboundHandlerAdapter {
    public void channelRead(ChannelHandlerContext ctx,Object msg)throws Exception{
       Integer integer=(Integer)msg;
        System.out.println(integer);
    }
    @Test
    public void text(){
        ChannelInitializer i=new ChannelInitializer<EmbeddedChannel>() {
            @Override
            protected void initChannel(EmbeddedChannel channel) throws Exception {
                channel.pipeline().addLast(new Byte2IntegerDecoder());
                channel.pipeline().addLast(new NettyDiscardHandler());
            }
        };
        EmbeddedChannel channel=new EmbeddedChannel(i);
        for(int j=0;j<10;j++){
            ByteBuf buf= Unpooled.buffer();
            buf.writeInt(j*100);
            channel.writeInbound(buf);
        }
    }
}
2.ReplayingDecoder解码器
    它是ByteToMessageDecoder是子类
   上面的解码器存在问题,我们需要对ByteBuf里面存放的 长度进行检查,如果有足够的字节,才能进行解码,那么我们是否能够把这个任务交给netty来实现呢?这就推出了此款解码器,它的实现原理是 内部封装了原本的ByteBuf,提供了一个DecoderBuffer(对原本的ByteBuf进行了装饰,装饰器模式),它的特点就是在读取数据之前首先进行长度的判断,长度合格才允许读取。
public class Byte2IntegerDecoder extends ReplayingDecoder {
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
            int i=byteBuf.readInt();
            System.out.println("解码出一个整数:"+i);
            list.add(i);
    }
}
      ReplayingDecoder解码器不仅仅是用来判断长度合法性的,面对 nio的拆包/粘包问题,它都可以解析,它更重要的运用场景就是分包传输的应用。将ByteBuf中的乱序的数据,还原到发送端发送时的数据顺序。
 
案例:实现连续两个整数的读取,并且返回两个数的和。
public class Byte2IntegerDecoder extends ReplayingDecoder<Byte2IntegerDecoder.Status> {
    enum Status{
        PARSE_1,PARSE_2
    }
    private int first;
    private int second;
    public Byte2IntegerDecoder(){
        super(Status.PARSE_1);
    }
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
     if(state()==Status.PARSE_1) {
          first= byteBuf.readInt();
         System.out.println("解码第一个整数:" + first);
        checkpoint(Status.PARSE_2);
     }else{
         second=byteBuf.readInt();
         System.out.println("第二个数:"+second+"\n和:"+(first+second));
         checkpoint(Status.PARSE_1);
     }
    }
}
      这里使用ReplayingDecoder的一个属性state成员,它保存当前解码器在解码过程中的进度,(我们上面读取两个整数,其实完成可以连续读出来),但是这里可能是模拟,两个值,分开传输,不会一起到达的情况,所以进行了分开获取,然后设置了两个阶段。重点就是 state的状态初始化,还有断点的设置,checkpoint(),一旦后面的读取,发现数据不够,抛出异常,读取指针就恢复到断点设置时的指位置。
 
字符串的分包解码器 案例
public class Byte2IntegerDecoder extends ReplayingDecoder<Byte2IntegerDecoder.Status> {
    enum Status{
        PARSE_1,PARSE_2
    }
    private int length;
    private byte[] inBytes;
    public Byte2IntegerDecoder(){
        super(Status.PARSE_1);
    }
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
     if(state()==Status.PARSE_1) {
          length= byteBuf.readInt();
          inBytes=new byte[length];
        checkpoint(Status.PARSE_2);
     }else{
         byteBuf.readBytes(inBytes,0,length);
         System.out.println(new String(inBytes,"UTF-8"));
         checkpoint(Status.PARSE_1);
     }
    }
}
//---------启动 稍稍修改
String content="hello";
byte[] bytes=content.getBytes(Charset.forName("utf-8"));
ByteBuf buf= Unpooled.buffer();
buf.writeInt(bytes.length);
buf.writeBytes(bytes);
channel.writeInbound(buf);
    可见,我们只是在两数和上面,进行了调整,重点就是加入了断点模式,分阶段进行读取。在传输字符串的时候,在信息上手动加入信息长度,避免信息混淆,也就是“header-content"协议。
缺点总结:不太建议使用这个类,
        1.不是所有的ByteBuf都能被ReplayingDecoderBuffer装饰了支持,有些会抛出异常。
        2.在数据解析复制的情况下,这个很消耗时间,因为数据没有到达就会恢复到断点位置,然后又重新读取,反复执行消耗cpu。
 
3.MessageToMessageDecoder
    前面都是把二进制数据解码成为Pojo对象,那么是否存在把pojo对象转换为另外一种pojo对象。于是推出了此类,它是泛型的,代表的是传入的Pojo类型。主要就是明确传入的pojo类型,返回的也是一个List<Object>其他和上面的类使用一致。
   这儿有点类似,pojo里面,视图层和dao层bean的转换,自定义转换规则。
public class Integer2StringDecoder extends MessageToMessageDecoder<Integer>{

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

4,开箱即用的Netty内置Decoder

   不同的应用场景,使用不同的解码器,比如固定长度数据包,行分割数据包,自定义分隔符数据包,自定义长度数据包等等解码器。
      1.LineBasedFrameDecoder解码器:使用换行符来代替"head-content"协议,以换行符为结束符,中间这个就作为一个完整的ByteBuf数据包,传个下一个handler。它有一个最大值,如果在最大值内没有读取到换行符,这回抛出异常(避免无限等待换行符)
    2.DelimiterBasedFrameDecoder解码器:它与第一个是差不多的,可以增加自己定义的分隔符,也就是多传入一个参数。(注意把分隔符,转换成为byte,再转换成为ByteBuf传入。
    3.LengthFieldBasedFrameDecoder解码器:这个的传入参数比较多,也比较常用,“自定义长度数据包解码器”,有五个参数,第一个是数据包最大长度,第二个是长度字段偏移量,第三个是长度字段自己占用的字节数,第四个是长度字段的偏移量矫正,第五个是丢弃的初始字节数。
    4.多字段Head-Context协议数据帧解析案例:它并不是长度+内容那么简单,还需要添加其他的参数,版本号,魔数,等等。
 
 
 
 

二,Encoder 使用

   业务处理完成后,需要将结果pojo写回二进制,传回。编码器是一个Outbound出战处理器,负责处理“出战”数据。
1.MessageToByteEncoder
    它的使用和解码器是一个道理的,它是一个抽象类,具体的功能需要我们自己去实现,使用范型去代替传入的pojo是什么类型的,最终都会编码成为byte组。
public class Integer2ByteEncoder extends MessageToByteEncoder<Integer> {
    
    @Override
    protected void encode(ChannelHandlerContext ctx, Integer msg, ByteBuf out) throws Exception {
        out.writeInt(msg);
    }
}
 
 
2.MessageToMessageEncoder
    这个和上面的Message2MessageDecoder有什么区别呢?可能是应用的位置不一样,上面那个是入站处理的,该类是出站处理。
  基本功能也是将一个pojo对象,编码成为另外一种Pojo对象。
public class String2IntegerEncoder extends MessageToMessageEncoder<String> {
    @Override
    protected void encode(ChannelHandlerContext ctx, String msg, List<Object> out) throws Exception {
        char [] array= toString().toCharArray();
        for(char a:array){
            if(a>=48 &&a<=57){
                out.add(new Integer(a));
            }
        }
    }
}

 

 
 

三,解码器和编码器的结合

    上面的两个解码和编码,是放在两个类中,我们的流水线也就意味着,需要加入两个处理器handler,那么是否能将解码和编码一起放入一个类,一次性加入流水线呢?推出了Codec类型。
1.ByteToMessageCodec编解码器
    继承它,就相当于继承了XXXDecoder,XXXEncoder。重点是如何在流水线中工作,即是入站,又是出战。所以我们在加入pipline的时候,只需要加入一次,就可以实现在入站的时候,执行解码,在出战的时候,执行编码。
 
public class Byte2IntegerCodec extends ByteToMessageCodec<Integer> {

    @Override
    protected void encode(ChannelHandlerContext ctx, Integer msg, ByteBuf out) throws Exception {
        out.writeInt(msg);
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        if(in.readableBytes()>=4){
            int i=in.readInt();
            out.add(i);
        }
    }
}

 

2.CombinedChannelDuplexHandler组合器
    上面是通过继承实现的,和组合实现具有更大的灵活性,它不需要自己再去实现编解码,而是把之前单独实现的类,复用起来。
 
public class IntegerDuplexHandler extends CombinedChannelDuplexHandler<Byte2IntegerDecoder,Integer2ByteEncoder> {
    public IntegerDuplexHandler(){
        super(new Byte2IntegerDecoder(),new Integer2ByteEncoder());
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值