Netty编解码器、TCP粘包拆包问题处理、Netty心跳检测机制
- Netty编解码器
- 编码器
- 解码器
- 编解码器
- Netty提供的现成编解码器
- TCP粘包拆包问题处理
- Netty心跳检测机制
Netty编解码器
网络传输是以字节流的形式传输的,而我们的应用程序一般不会直接对字节流进行处理,而是把字节流解析成有一定结构的数据格式再去处理。因此我们在发送数据时,要对我们的数据进行编码,把它转换成二进制字节流的形式,然后在网络中进行传输,当我们接收到对方传输过来的二进制字节流时,又需要对其进行解码,解析成应用程序能够处理的数据格式。
比如客户端向服务端发送数据的场景:
这个编码和解码的动作,可以由用户自己去实现,比如我们使用Java原生的NIO进行开发时,就不得不自己去实现一套编解码的逻辑。而我们使用了Netty之后,Netty给我们封装好了各种编解码器,我们只要使用对应的编解码器,即可以很简单的对数据进行编解码,进一步简化了网络程序的开发。
编码器
Netty提供了两个编码器抽象类,我们可以选择性的继承它们以实现自己定制的编码器,这两个编码器抽象类分别是MessageToByteEncoder和MessageToMessageEncoder。
- MessageToByteEncoder<I>:将消息编码为字节
- MessageToMessageEncoder<T>:将消息编码为消息
它们都继承了ChannelOutboundHandlerAdapter,因此它们都是用于处理出站事件的。
MessageToByteEncoder的抽象方法encode(ChannelHandlerContext ctx, I msg, ByteBuf out)把特定的类型的消息msg编码成二进制,消息类型由泛型“I”指定。我们继承MessageToByteEncoder后,要实现这个encode方法,encode方法接收指定类型的消息对象msg,然后我们对其进行编码后要放入到ByteBuf类型的out对象中。
MessageToMessageEncoder的抽象方法encode(ChannelHandlerContext ctx, I msg, ByteBuf out)把特定的类型的消息msg编码成另外一种消息,消息类型由泛型“I”指定。我们继承MessageToMessageEncoder后,要实现这个encode方法,encode方法接收指定类型的消息对象msg,然后我们对其进行编码后要放入到List类型的out对象中。
比如我们可以通过MessageToMessageEncoder把一个对象转换成json格式的字符串,然后通过MessageToByteEncoder把json字符串加密后编码成二进制字节流。
解码器
Netty提供了两个解码器抽象类,我们可以选择性的继承它们以实现自己定制的解码器,这两个解码器抽象类分别是ByteToMessageDecoder和MessageToMessageDecoder<I>。
- ByteToMessageDecoder:将字节解码为消息
- MessageToMessageDecoder<I>:将一种消息类型解码为另一种
它们都继承了ChannelInboundHandlerAdapter,因此它们都是用来处理入站事件的。
ByteToMessageDecoder的抽象decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)方法中ByteBuf类型参数in可以获取到接收进来的字节流,我们需要把它解码成特定的对象,然后放入List类型的out中。
MessageToMessageDecoder中的抽象方法decode(ChannelHandlerContext ctx, I msg, List<Object> out)把特定类型的对象msg解码成另一种类型的对象,然后放入out中。
比如我们接收到上面这个例子发送的编码后的二进制字节流,我们可以通过ByteToMessageDecoder解码后解密成json字符串,然后通过MessageToMessageDecoder把json字符串转成对象。
编解码器
Netty还提供了集编码解码一体的编解码器抽象类,它们分别是ByteToMessageCodec和MessageToMessageCodec,它们同时处理入站事件和出站事件。但是我们一般很少使用,更多的是把编码和解码分开两个类,这样才能最大化代码的可重用性和可扩展性。
Netty提供的现成编解码器
上面这些都是抽象类,是需要我们自己继承后实现编解码逻辑的,而Netty还有更牛逼的直接现成的编解码器类提供给我们,我们把它们组装到ChannelPipeline中即可使用。
根据不同的协议或不同的功能,Netty提供了对应的编码器或解码器,有处理SSL或TLS协议的、有处理HTTP协议的、有做HTTP压缩的、有处理WebSocket协议的等等。这里就不一一细说了,我们只要知道Netty都提供了哪些功能的内置编解码器,再按需的去了解即可。
TCP粘包拆包问题处理
由于TCP是一个面向二进制流的协议,是传输层协议,并不理解应用层传下来的业务数据,但是TCP又会根据MSS对数据进行分包处理,于是会出现下面四种情况:
- 第一种情况是TCP分包分的刚刚好,D1和D2两个独立的数据包分开发送,这是没有问题的。
- 第二种情况就是TCP把D1和D2两个数据包连在一起被TCP一同发送,此时就发生了粘包问题。
- 第三种情况和第四种情况都是其中一个数据包被分成了两半,与另一个数据包一起一同发送,此时就发生了拆包问题。
当发生TCP粘包拆包问题时,接收数据的一方需要通过某种机制将被拆分或粘连的数据包还原成独立的数据包。
Netty提供了多个解码器可以处理TCP粘包拆包问题:
- LineBasedFrameDecoder:利用回车换行符做分包处理
- DelimiterBasedFrameDecoder:利用指定的特殊分隔符做分包处理
- FixedLengthFrameDecoder:以固定长度来分包
- LengthFieldBasedFrameDecoder:消息头中记录消息总长度,通过消息头中记录的消息体长度进行分包
这几个都是上面说过的Netty提供的现成解码器。
除此以外我们还可以自己实现TCP粘包拆包的处理:
- 我们可以通过实现自己的MessageToByteEncoder将消息编码成二进制时,先写出消息长度再写出消息体
- 然后实现ByteToMessageDecoder(或ReplayingDecoder),在解码时先读取一个int,得到消息体长度,然后再读取指定长度的字节流解码成出消息体。
Netty心跳检测机制
Netty提供了一个IdleStateHandler可以用于进行心跳检测,我们可以设置读超时时间或写超时时间,比如我们设置了读超时时间,IdleStateHandler在指定时间内没有从通道中读取到数据,就会触发一个IdleStateEvent事件,IdleStateEvent中有一个state字段记录是什么类型的超时时间(读超时、写超时、读写超时)。
我们可以在ChannelPipeline添加一个IdleStateHandler,然后我们再继承SimpleChannelInboundHandler实现自定义的入站处理放于IdleStateHandler的后面,就可以在发生读超时或写超时事件时,在userEventTriggered(ChannelHandlerContext ctx, Object evt)方法中接收到IdleStateHandler发出的IdleStateEvent对象,我们就可以进行相应处理,比如读超时事件则进行探活处理或者关闭连接等操作。