★ 


   说明:《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解码器




   上面的解码器存在问题,我们需要对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);
    }
}


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);
     }
    }
}


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());
    }
}