019-十九、网络编程Netty 粘包 & 拆包 & 编码 & 解码 & 序列化
前言
Netty
作为一个网络框架,对 TCP
连接中的问题都做了全面的考虑,比如粘包拆包导致的半包问题,如何编解码,如何实现私有协议,序列化等等。
本文主要针对这些问题做一个简单介绍,目的是想对整个 Netty
的编解码框架做一个全盘的审视,以确保在后面的源码学习中不会一叶障目不见泰山。
1. 粘包 & 拆包及解决方案 ByteToMessageDecoder
由于TCP
是面向字节流的,什么意思呢:虽然应用程序和 TCP
的交互是一次一个数据块(大小不等),但 TCP
把应用程序交下来的数据仅仅看成式一连串的无结构的字节流。TCP
并不知道所传送的字节流的含义。
因此 TCP
不保证接收方应用程序所收到的数据块和发送方应用程序所发出的数据块具有对应大小的关系(例如,发送方应用程序交给发送方的 TCP
共 10
个数据块,但接收方的 TCP
可能只用了 4
个就把收到的字节流交付上层的应用程序)。
同时,TCP
不关心应用进程一次把多长的报文发送到 TCP
的 缓存 中,而是根据对方给出的窗口值和当前网络阻塞的程度来决定一个报文段应包含多少个字节(UDP
发送的报文长度是应用进程给出的)。
如果应用进程传送到 TCP
缓存的数据块太长,TCP
就可以把他划分短一点再传送。如果应用程序一次只发来一个字节,TCP
也可以等待积累有足够多的字节后再构成报文段发送出去。
TCP
发送报文一般是 3
个时机:
1. 缓冲区数据达到 最大报文长度 MSS
;
- 由发送端的应用进程指明要求发送报文段,即
TCP
支持的推送(push
)操作; -
当发送方的一个计时器期限到了,即使长度不超过
MSS
,也发送。
以上引自《计算机网络—–谢希仁》。
说了这么多,TCP
的这种机制,会导致什么问题呢?粘包问题。有了粘包,就需要拆包。
一般解决粘包拆包问题有 4
种办法:
- 固定数据的长度,比如
100
字节,如果不够就补空格。 -
学习
HTTP
,FTP
等,使用回车换行符号。 -
将消息分为
head
和body
,head
中包含body
长度的字段,一般head
的第一个字段使用int
值来表示body
长度。 -
使用更复杂的应用层协议(等于没说 =_= !)。
Netty
作为一个网络框架,直接和 TCP
打交道,自然考虑了这个问题。而解决这个问题的主要实现就是抽象类 ByteToMessageDecoder
,详见 《[Netty 解码器抽象父类 ByteToMessageDecoder 源码解析](https://www.ycbbs.vip/?p=366 "Netty 解码器抽象父类 ByteToMessageDecoder 源码解析")》
Netty 使用了模板设计模式,这个类只定义了共有行为,具体解码实现还是子类,比如上面提到的 4 种方式。
2. 基于长度编解码器的具体实现
基于长度的实现有2个现成的类:
FixedLengthFrameDecoder
基于构造函数中的固定长度 该类很简单,构造方法中,传入一个整数,该解码器就会按照这个数字对累积区的字节进行切分。-
LengthFieldBasedFrameDecoder
基于流中动态的长度 该类比较复杂。构造函数参数多达6
个,在构建私有协议栈时大有用处。
3. 基于分割符的编解码器
同样有 2 个:
DelimiterBasedFrameDecoder
用户提供分割符。 该类比较简单,根据用户提供的分割符对累积区的内容进行分割。性能相对不是那么完美。-
LineBasedFrameDecoder
基于换行符,支持多种换行符\n
\r\n
速度相比自定义较快。 该类使用更简单,根据换行符进行拆包粘包。
4. google 的 ProtobufDecoder ProtobufEncoder 序列化介绍
Netty
中有很多序列化工具,比如 Jboss
的 Marshalling
,同时也支持 Java
标准的序列化。 但我们重点关注 google
的 protobuf
库。因为它的性能最高。
上面的 4 个解码器都是基于 ByteToMessageDecoder
,将粘包的字节转为用户需要的字节。而ProtobufDecoder
不是继承自 ByteToMessageDecoder
,而是继承自 MessageToMessageDecoder
,名字都不同。MessageToMessageDecoder
的作用是什么呢?
从名字上看,该类用于将两个消息进行转换(比如一种 POJO
转成另一种)。后面我们将花大篇幅讲述这个类库。
5. 其他的
1. TooLongFrameException
由于 Netty
是一个异步框架,所以需要在字节可以解码之前在内存中缓冲他们。因此不能让解码器缓冲大量的数据以至于耗尽可用的内存。为了解决这个问题,Netty
提供了 TooLongFrameException
类,其将由解码器在帧超出指定的大小限制时抛出异常。
你可以设置一个最大的阈值,当超过该阈值,这抛出异常。
2. 写大型数据的 FileRegion
有时候你可能需要写一个大型的数据,如果不停的写入,可能导致 OOM
,所以在写大型数据时,需要准备好处理到远程节点的连接时慢速连接的情况,这种情况会导致内存释放的延迟。
我们可以使用 NIO
的零拷贝特性,这种特性消除了将文件内容从文件系统移动到网络栈的复制过程。而我们所需要做的就是使用一个 FileRegion
接口的实现。 官方定义:
通过支持零拷贝的文件传输的 Channel
来发送的文件区域。
6. 总结
本文并没有刨析源码,主要是针对 Netty
中现有的或者设计的编解码,序列化等工具做一个介绍,方便后面有条不紊的按照这个路线研究他们的具体实现。
写完了如果写得有什么问题,希望读者能够给小编留言,也可以点击[此处扫下面二维码关注微信公众号](https://www.ycbbs.vip/?p=28 "此处扫下面二维码关注微信公众号")
看完两件小事
如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:
- 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
- 关注公众号 「方志朋」,公众号后台回复「666」 免费领取我精心整理的进阶资源教程
本文著作权归作者所有,如若转载,请注明出处
转载请注明:文章转载自「 Java极客技术学习 」https://www.javajike.com