★
说明:《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());
}
}