Decoder和Encoder
在Netty⾥⾯,有四个核⼼概念,它们分别是:
Channel:⼀个客户端与服务器通信的通道。
ChannelHandler:业务逻辑处理器,通常情况下,业务逻辑都是存在于ChannelHandler之中。
ChannelInboundHandler:输⼊处理器
ChannelOutboundHandler:输出处理器
ChannelPipeline:⽤于存放ChannelHandler的双向链表。
ChannelContext:通信管道的上下⽂
它们之间的交互流程是:
事件传递给 ChannelPipeline 的第⼀个 ChannelHandler
ChannelHandler 通过关联的 ChannelHandlerContext 传递事件给 ChannelPipeline 中的下⼀个
⽽我们要讲的Decoder和Encoder,就是ChannelInboundHandler和ChannelOutboundHandler,分别⽤于在数据流进来的时候将字节码转换为消息对象和数据流出去的时候将消息对象转换为字节码。
对于解码器,Netty中主要提供了抽象基类ByteToMessageDecoder和MessageToMessageDecoder
1. 抽象类 ByteToMessageDecoder
⽤于将接收到的⼆进制数据(Byte)解码,得到完整的请求报⽂(Message)。
通常,ByteToMessageDecoder解码后内容会得到⼀个ByteBuf实例列表,每个ByteBuf实例都包含了⼀个完整的报⽂信息。你可以直接把这些ByteBuf实例直接交给之后的ChannelInboundHandler处理,或者将这些包含了完整报⽂信息的ByteBuf实例解析封装到不同的Java对象实例后,再交其处理。不管哪⼀种情况,之后的ChannelInboundHandler在处理时不需要再考虑粘包、拆包问题。
ByteToMessageDecoder提供的⼀些常见的实现类:
FixedLengthFrameDecoder:定长协议解码器,我们可以指定固定的字节数算⼀个完整的报⽂
LineBasedFrameDecoder:⾏分隔符解码器,遇到\n或者\r\n,则认为是⼀个完整的报⽂
DelimiterBasedFrameDecoder:分隔符解码器,与LineBasedFrameDecoder类似,只不过分隔符可以⾃⼰指定
LengthFieldBasedFrameDecoder:长度编码解码器,将报⽂划分为报⽂头/报⽂体,根据报⽂头中的Length字段确定报⽂体的长度,因此报⽂提的长度是可变的
JsonObjectDecoder:json格式解码器,当检测到匹配数量的"{" 、”}”或”[””]”时,则认为是⼀个完整的json对象或者json数组。
这些实现类,都只是将接收到的⼆进制数据,解码成包含完整报⽂信息的ByteBuf实例后,就直接交给了之后的ChannelInboundHandler处理。之所以不将ByteBuf中的信息封装到Java对象中,道理很简单,Netty根本不知道开发者想封装到什么对象中,甚⾄不知道报⽂中的具体内容是什么,因此不如直接把包含了完整报⽂信息的ByteBuf实例,交给开发⼈员来⾃⼰解析封装。
当然也有例外,例如Netty提供的XmlDecoder,直接将⼆进制数据流解析成Aalto XML parser类库中定义的xml对象。
我们也可以⾃定义ByteToMessageDecoder,此时需要覆盖ByteToMessageDecoder的decode⽅法:
protected abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception;
参数说明:
in:需要解码的⼆进制数据。
List<Object> out:解码后的有效报⽂列表,我们需要将解码后的报⽂添加到这个List中。之所以使⽤⼀个List表⽰,是因为考虑到粘包问题,因此⼊参的in中可能包含多个有效报⽂。当然,也有可能发⽣了拆包,in中包含的数据还不⾜以构成⼀个有效报⽂,此时不往List中添加元素即可。
另外特别要注意的是,在解码时,不需要直接调⽤ByteBuf的readXXX⽅法来读取数据,⽽是应该⾸先要判断能否构成⼀个有效的报⽂。例如对于以下的案例,假设协议规定传输的数据都是int类型的整数:
上图中显式输⼊的ByteBuf中包含4个字节,每个字节的值分别为:1,2,3,4。我们⾃定义⼀个ToIntegerDecoder进⾏解码,尽管这⾥我看到了4个字节刚好可以构成⼀个int类型整数,但是在真正解码之前,我们并不知道ByteBuf包含的字节数能否构成⼀个或者多个完成的有效报⽂,因此需要⾸先判断ByteBuf中剩余可读的字节,是否⼤于等于4,如下:
public class ToIntegerDecoder extends ByteToMessageDecoder {
@Override
public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if (in.readableBytes() >= 4) {
out.adInt());
} }
}
只有在可读字节数>=4的情况下,我们才进⾏解码,即读取⼀个int,并添加到List中。
在可读字节数⼩于4的情况下,我们并没有做任何处理,假设剩余可读字节数为3,不⾜以构成1个int。那么⽗类ByteToMessageDecoder发现这次解码List中的元素没有变化,则会对in中的剩余3个字节进⾏缓存,等待下1个字节的到来,之后再回到调⽤ToIntegerDecoder的decode⽅法。
另外,细⼼的读者可能注意到了,在ToIntegerDecoder的decode⽅法中,每次最多只读取⼀个1个int。
如果ByteBuf中的字节数很多,例如为16,那么可以构成4个int,⽽这⾥只读取了1个int,那么剩余12字节怎么办?这个其实不⽤担⼼,ByteToMessageDecoder再每次回调⼦类的decode⽅法之后,都会判断输⼊的ByteBuf中是否还有剩余字节可读,如果还有,会再次回调⼦类的decode⽅法,直到某个回调decode⽅法List中的元素个数没有变化时才停⽌,元素个数没有变化,实际上意味着⼦类已经没有办法从剩余的字节中读取⼀个有效报⽂。
由于存在剩余可读字节时,ByteToMessageDecoder会⾃动再次回调⼦类decode⽅法,因此笔者建议在实现ByteToMessageDecoder
时,decode⽅法每次只解析⼀个有效报⽂即可,没有必要⼀次全部解析出来。
2. 抽象类 MessageToMessageDecoder
ByteToMessageDecoder是将⼆进制流进⾏解码后,得到有效报⽂。⽽MessageToMessageDecoder则是将⼀个本⾝就包含完整报⽂信息的对象转换成另⼀个Java对象。
举例来说,前⾯介绍了ByteToMessageDecoder的部分⼦类解码后,会直接将包含了报⽂完整信息的ByteBuf实例交由之后的ChannelInboundHandler处理,此时,你可以在ChannelPipeline中,再添加⼀个MessageToMessageDecoder,将ByteBuf中的信息解析后封装到Java对象中,简化之后的ChannelInboundHandler的操作。
另外,⼀些场景下,有可能你的报⽂信息已经封装到了Java对象中,但是还要继续转成另外的Java对象,因此⼀个MessageToMessageDecoder后⾯可能还跟着另⼀个MessageToMessageDecoder。⼀个⽐较容易的理解的类⽐案例是Java Web编程,通常客户端浏览器发送过来的⼆进制数据,已经被web容器(如tomcat)解析成了⼀个HttpServletRequest对象,但是我们还是需要将HttpServletRequest中的数据提取出来,封装成我们⾃⼰的POJO类,也就是从⼀个Java对象(HttpServletRequest)转换成另⼀个Java对象(我们的POJO类)。
除了⼀些公有协议的解码器外,Netty提供的MessageToMessageDecoder实现类较少,主要是:
(1) StringDecoder
⽤于将包含完整的报⽂信息的ByteBuf转换成字符串。我们可以将其与ByteToMessageDecoder的⼀些实现类联合使⽤,以LineBasedFrameDecoder为例,其将⼆进制数据流按⾏分割后封装到ByteBuf中。我们可以在其之后再添加⼀个StringDecoder,将ByteBuf 中的数据转换成字符串。
(2) Base64Decoder
⽤于Base64编码。例如,前⾯我们提到LineBasedFrameDecoder、DelimiterBasedFrameDecoder等ByteToMessageDecoder实现类,是使⽤特殊字符作为分隔符作为解码的条件。但是如果报⽂内容中
如果本⾝就包含了分隔符,那么解码就会出错。此时,对于发送⽅,可以先使⽤Base64Encoder对报⽂内容进⾏Base64编码,然后我们选择Base64编码包含的64种字符之外的其他特殊字符作为分隔符。在解码时,⾸先特殊字符进⾏分割,然后通过Base64Decoder解码得到原始的⼆进制字节流。
MessageToMessageDecoder的类声明如下:
public abstract class MessageToMessageDecoder<I> extends ChannelInboundHandlerAdapter
其中泛型参数I表⽰我们要解码的消息类型。例前⾯,我们在ToIntegerDecoder中,把⼆进制字节流转换成了⼀个int类型的整数。
类似的,MessageToMessageDecoder也有⼀个decode⽅法需要覆盖,如下:
/**
* 参数msg,需要进⾏解码的参数。例如ByteToMessageDecoder解码后的得到的包含完整报⽂信息ByteBuf
* List<Object> out参数:将msg经过解析后得到的java对象,添加到放到List<Object> out中
*/
protected abstract void decode(ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception;
例如,现在我们想编写⼀个IntegerToStringDecoder,把前⾯编写的ToIntegerDecoder输出的int参数转换成字符串,此时泛型I就应该是Integer类型。
IntegerToStringDecoder源码如下所⽰:
public class IntegerToStringDecoder extends MessageToMessageDecoder<Integer> {
@Override
public void decode(ChannelHandlerContext ctx, Integer msg List<Object> out) throws Exception {
out.add(String.valueOf(msg));
}
}
此时我们应该按照如下顺序组织ChannelPipieline中ToIntegerDecoder和IntegerToStringDecoder 的关
系:
ChannelPipieline ch=....
ch.addLast(new ToIntegerDecoder());
ch.addLast(new IntegerToStringDecoder());
也就是说,前⼀个ChannelInboudHandler输出的参数类型,就是后⼀个ChannelInboudHandler的输⼊类型。
特别需要注意的⼀点是,如果我们指定MessageToMessageDecoder的泛型参数为ByteBuf,表⽰其可以直接针对ByteBuf进⾏解码,那么其是否能替代ByteToMessageDecoder呢?
答案是不可以的。因为ByteToMessageDecoder除了进⾏解码,还要会对不⾜以构成⼀个完整数据的报⽂拆包数据(拆包)进⾏缓存。⽽MessageToMessageDecoder则没有这样的逻辑。
因此通常的使⽤建议是,使⽤⼀个ByteToMessageDecoder进⾏粘包、拆包处理,得到完整的有效报⽂的ByteBuf实例,然后交由之后的⼀个或者多个MessageToMessageDecoder对ByteBuf实例中的数据进⾏解析,转换成POJO类。
与ByteToMessageDecoder和MessageToMessageDecoder相对应,Netty提供了对应的编码器实
现MessageToByteEncoder和MessageToMessageEncoder,⼆者都实现ChannelOutboundHandler接⼝。
相对来说,编码器⽐解码器的实现要更加简单,原因在于解码器除了要按照协议解析数据,还要要处理粘包、拆包问题;⽽编码器只要将数据转换成协议规定的⼆进制格式发送即可。
1. 抽象类 MessageToByteEncoder
MessageToByteEncoder也是⼀个泛型类,泛型参数I表⽰将需要编码的对象的类型,编码的结果是将信息转换成⼆进制流放⼊ByteBuf中。⼦类通过覆写其抽象⽅法encode,来实现编码,如下所⽰:
public abstract class MessageToByteEncoder<I> extends ChannelOutboundHandlerAdapter {
....
protected abstract void encode(ChannelHandlerContext ctx, I msg, ByteBuf out) throws Exception;
}
可以看到,MessageToByteEncoder的输出对象out是⼀个ByteBuf实例,我们应该将泛型参数msg包含的信息写⼊到这个out对象中。MessageToByteEncoder使⽤案例:
public class IntegerToByteEncoder extends MessageToByteEncoder<Integer> {
@Override
protected void encode(ChannelHandlerContext ctx, Integer msg, ByteBuf out) throws Exception {
out.writeInt(msg);//将Integer转成⼆进制字节流写⼊ByteBuf中
}
}
2. 抽象类 MessageToMessageEncoder
MessageToMessageEncoder同样是⼀个泛型类,泛型参数I表⽰将需要编码的对象的类型,编码的结果是将信息放到⼀个List中。⼦类通过覆写其抽象⽅法encode,来实现编码,如下所⽰:
public abstract class MessageToMessageEncoder<I> extends ChannelOutboundHandlerAdapter {
...
protected abstract void encode(ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception;
...
}
与MessageToByteEncoder不同的,MessageToMessageEncoder编码后的结果放到的out参数类型是⼀个List中。例如,你⼀次发送2个报⽂,因此msg参数中实际上包含了2个报⽂,因此应该解码出两个报⽂对象放到List中。
MessageToMessageEncoder提供的常见⼦类包括:
LineEncoder:按⾏编码,给定⼀个CharSequence(如String),在其之后添加换⾏符\n或者\r\n,并封装到ByteBuf进⾏输出,与LineBasedFrameDecoder相对应。
Base64Encoder:给定⼀个ByteBuf,得到对其包含的⼆进制数据进⾏Base64编码后的新的ByteBuf进⾏输出,与Base64Decoder相对应。
LengthFieldPrepender:给定⼀个ByteBuf,为其添加报⽂头Length字段,得到⼀个新的ByteBuf进⾏输出。Length字段表⽰报⽂长度,与LengthFieldBasedFrameDecoder相对应。
StringEncoder:给定⼀个CharSequence(如:StringBuilder、StringBuffer、String等),将其转换成ByteBuf进⾏输出,与
StringDecoder对应。
细⼼的读者注意到了,这些MessageToMessageEncoder实现类最终输出的都是ByteBuf,因为最终在⽹络上传输的都要是⼆进制数据。
编码解码器同时具有编码与解码功能,特点同时实现了ChannelInboundHandler和ChannelOutboundHandler接⼝,因此在数据输⼊和输出时都能进⾏处理。Netty提供提供了⼀个ChannelDuplexHandler适配器类,编码解码器的抽象基
类ByteToMessageCodec、MessageToMessageCodec都继承与此类,如下:
ByteToMessageCodec内部维护了⼀个ByteToMessageDecoder和⼀个MessageToByteEncoder实例,可以认为是⼆者的功集合,泛型参数I是接受的编码类型:
public abstract class ByteToMessageCodec<I> extends ChannelDuplexHandler {
servlet和tomcat的关系private final TypeParameterMatcher outboundMsgMatcher;
private final MessageToByteEncoder<I> encoder;
private final ByteToMessageDecoder decoder = new ByteToMessageDecoder(){…}
...
protected abstract void encode(ChannelHandlerContext ctx, I msg, ByteBuf out) throws Exception;
protected abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception;
...
}
MessageToMessageCodec内部维护了⼀个MessageToMessageDecoder和⼀个MessageToMessageEncoder实例,可以认为是⼆者的功集合,泛型参数INBOUND_IN和OUTBOUND_IN分别表⽰需要解码和编码的数据类型。
public abstract class MessageToMessageCodec<INBOUND_IN, OUTBOUND_IN> extends ChannelDuplexHandler { private final MessageToMessageEncoder<Object> encoder= ...
private final MessageToMessageDecoder<Object> decoder =…
...
protected abstract void encode(ChannelHandlerContext ctx, OUTBOUND_IN msg, List<Object> out) throws Exception; protected abstract void decode(ChannelHandlerContext ctx, INBOUND_IN msg, List<Object> out) throws Exception; }
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论