我们都知道Java时们面向对象的语言,但是在网络编程中,像Java中那样的“对象”是无法进行传输的,因为在Socket 中,传输的只能是字节流。那么这便表示我们必须要将对象转换成可以在网络间进行传输的字节流。平常我们的编程中有使用过toString()这样的方法,然后再获取字节流。这样是可以将对象转换成字节流,但是经过网络传输后,接收方接收到字节流后,仍然需要将字节流还原成原始数据对象,那么也就需要类似于fromString()方法进行转换。由此我们便知道,数据的编解码是为了传输数据。
我们Java程序员都知道我们的平常开发中常常会使用到Java的序列化和反序列化来进行编码,当然也是用过一些其他的编码方式,那么这里便简单罗列一下主流的编解码方式。
1、Java序列化/反序列化
在Java开发中,为了方便存储和传输,我们常常使用的就是JDK知道的序列化机制。这样的机制使用过实现Serializable接口,来标记当前类是具有序列化的特性的。在Java中是使用ObjectOutputStream中的writeObject()来对对象进行序列化,使用ObjectInputStream中的readObject()方法来还原对象。但是这样的方式所编译的信息的可读性是很差的。
2、XML
XML的中文名称是可扩展标记语言,这种方式在之前的Web Service编程中是常使用过的一种方式,但是由于技术的更迭,现今已经很少看到只用这种方式了。
3、JSON
JSON(JavaScript Object Notation)是一种轻量的数据传递的方式,它完全采用独立于编程语言的文本格式来存储和展示数据,当前,这种格式使用的非常广泛,其主要原因无非是简洁、清晰的层次结构,便于阅读且易于机器的解析和生成,在网络传输中的效率也是很高的。
4、msgpack
msgpack是一种基于二进制、高效的对象序列化方式,msgpack也可以像JSON那样,在多重编程语言之间交换结构对象,相比于JSON而言,它还更轻巧、快速。但是msgpack和Java的序列化编码后一样,都会出现乱码,可读性比较差。
5、Marshalling
Marshalling是JBoss开发的Java对象序列化包,它是对JDK自带的系列化框架进行了优化,且能与Serializable接口保持兼容。和前面的XML、JSON不一样的是,Marshalling是Java专用的,但是和JDK自带的又不太一样,Marshalling具有定制性、可定义Stream头等特性。
6、Protobuf
ProtoBuf是有谷歌开发的一种灵活高效的数据序列化协议,相比XML、JSON而言,Protobuf文件的更小,且更快捷。Protobuf也支持多语言的编程,也自带了编译器,可以自动生成Java、Python、C++等不同编程语言的代码,但是所生成代码的可读性差,且内容冗余。
编码选择考量
前面我们对网络中为什么需要编解码和一些主流的编解码方式等进行了简单的说明,那么现在就将开始进行Netty中编解码的内容阐述了,在开始前,先来对编解码等相关词汇来进行简单的解释,如下:
Netty中提供了提供了强大的编解码功能,这会让我们自定义编解码器会很便易,在Netty中编解码器实现了ChannelHandlerAdapter,这是一个特殊的ChannelHandler。在netty中ChannelInboundHandler负责输入数据的处理,ChannelOutboundHandler负责输出数据的处理。那么我们来看下他们的类图,如下:
在netty中,解码器主要是由ByteToMessageDecoder和MessageToMessageDecoder两个类提供,他们都继承了ChannelInboundHandlerAdapter适配类,我们来看一下他们的类图,如下:
有上图可知,有三个抽象解码器:ByteToMessageDecoder、ReplayingDecoder、MessageToMessageDecoder,他们的作用分别是:
ByteToMessageDecoder类会通过 ChannelInboundHandlerAdapter 以类似流的方式将字节信息从一个 ByteBuf 解码到一个所需要的消息对象类型。其有几个常见的实现类,如下:
这些实现类,都只是将接收到的二进制数据,解码成包含完整报文信息的ByteBuf实例后,就直接交给了之后的ChannelInboundHandler处理。在ByteToMessageDecoder类中唯一需要实现的抽象方法是io.netty.handler.codec.ByteToMessageDecoder#decode方法,当我们自定义解码器的时候,只需要覆盖这个方法:
protected abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List
这个方法中传入了两个参数分别是ChannelHandlerContext和List,它们的作用分别是:
ByteToMessageDecoder类是通过将二进制流信息解码成相应的有效对象数据,但是MessageToMessageDecoder则是将一个本身就包含完整报文信息的对象转换成另一个Java对象。在Netty中除了一些公有协议的解码器外,Netty提供的MessageToMessageDecoder实现类较少,主要有如下几个:
和ByteToMessageDecoder类类似的,MessageToMessageDecoder也有一个decode方法需要覆盖 ,如下:
protected abstract void decode(ChannelHandlerContext ctx, I msg, List
ReplayingDecoder是继承自ByteToMessageDecoder类的一个特殊的抽象类,它支持在阻塞 I/O 范式中实现非阻塞解码器。ByteToMessageDecoder解码读取缓冲区的数据之前需要检查缓冲区是否有足够的字节,使用 ReplayingDecoder则无需自己检查,若ByteBuf中有足够的字节,则会正常读取,若没有足够的字节则会停止解码。也正是这样,使得ReplayingDecoder带有一定的局限性。
与ByteToMessageDecoder和MessageToMessageDecoder相对应,Netty提供了对应的编码器实现MessageToByteEncoder和MessageToMessageEncoder, 二者都实现ChannelOutboundHandler接口。 类结构如下:
相对来说,编码器比解码器的实现要更加简单,原因在于解码器除了要按照协议解析数据,还要要处理粘包、拆包问题,而编码器只要将数据转换成协议规定的二进制格式发送即可。
MessageToByteEncoder是一个可接收泛型的类,泛型参数“I”表示将需要编码的对象的类型,编码的结果是将信息转换成二进制流放入ByteBuf中。子类通过覆写其抽象方法encode来实现编码,如下所示:
protected abstract void encode(ChannelHandlerContext ctx, I msg, ByteBuf out) throws Exception;
从上面代码中可以看到,MessageToByteEncoder的输出对象out是一个ByteBuf实例,我们应该将泛型参数msg包含的信息写入到这个out对象中。
MessageToMessageEncoder同样也是一个可接收泛型类,泛型参数“I”表示将需要编码的对象的类型,编码的结果是将信息放到一个List中。子类通过覆写其抽象方法encode,来实现编码,如下所示:
protected abstract void encode(ChannelHandlerContext ctx, I msg, List
和MessageToByteEncoder不同的,MessageToMessageEncoder编码后的结果放到的out参数中的是一个List。例如,你一次发送2个报文,因此msg参数中实际上包含了2个报文,因此应该解码出两个报文对象放到List中。MessageToMessageEncoder提供的常见子类有如下几个:
编码解码器同时具有编码与解码功能,特点同时实现了ChannelInboundHandler和ChannelOutboundHandler接口,因此在数据输入和输出时都能进行处理。Netty提供提供了一个ChannelDuplexHandler适配器类,编码解码器的抽象基类 ByteToMessageCodec 、MessageToMessageCodec都继承于此类,类结构图如下:
ByteToMessageCodec内部维护了一个ByteToMessageDecoder和一个MessageToByteEncoder实例,可以认为是二者的集合,泛型参数I则是接受的编码类型,代码如下:
public abstract class ByteToMessageCodec extends ChannelDuplexHandler {
private final TypeParameterMatcher outboundMsgMatcher;
private final MessageToByteEncoder 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
MessageToMessageCodec内部维护了一个MessageToMessageDecoder和一个MessageToMessageEncoder实例,可以认为是二者的集合,泛型参数 INBOUND_IN和OUTBOUND_IN分别表示需要解码和编码的数据类型。代码如下:
public abstract class MessageToMessageCodec extends ChannelDuplexHandler {
private final MessageToMessageEncoder