假设客户端分别发送了两个数据包给服务端,由于服务端一次读取到的字节数不确定,会存在 4种情况:1:服务端分两次读取到了两个独立的数据包,不发生 粘包 拆包
2:服务端一次接收到了两个数据包, 两个粘合在一起,被称为 粘包
3:服务端分别两次读取到了两个数据包,第一次读取到了 完整的 D1包和 D2包的部分内容,第二次读取到了 D2的剩余内容,这被称为 TCP拆包。
4:服务端分别两次读取到了两个数据包,第一次读取到了 D1包的部分内容,第二次读取到了 D1包的剩余内容 和 D2包的整包。
如果 服务端 TCP接收滑窗非常小,而数据包 D1和D2比较大,
很有可能发生第五种可能,服务端多次才能将 D1 和D2包接收完全,期间发生多次拆包。
发生的原因
1:应用程序 write写入的字节大小 大于套接口发送缓冲区大小.
2:进行MSS大小的TCP分段
3:以太网帧的payoload大于MTU进行分片
解决策略
由于底层的TCP无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,
这个问题只能通过上层的应用协议栈设计来解决,根据业界的主流协议的的解决方案:
1:消息定长,例如每个报文的大小为固定长度 200字节,如果不够,空位补空格;
2:在包尾增加回车换行符进行分割,例如FTP协议
3:将消息 分为 消息头 和消息体,消息头中包含表示消息体总长度的字段,
通常设计思路为 消息头的第一个字段使用 32int来表示消息的长度
4:更复杂的应用层协议
LineBasedFrameDecoder 和 StringDecoder 的原理分析
LineBasedFrameDecoder 的工作原理是它依次便利 ByteBuffer中的可读字节,判断看 是否有 \n 或\r\n ,
如果有,就以此为结束位置,从可读索引到位置结束区间的字节 就组成的一行,它是以换行符为结束标志的解码器,支持携带结束符或者不携带结束符两种解码方式,同时支持配置单行的最大度,如果连续读取到最大长度后仍然没有发现换行符 就会抛出异常,同是忽略掉之前读到的异常码流
StringDecoder的功能非常简单,就是将接收到的对象转换成字符串,然后继续调用后面 Handler。
LineBasedFrameDecoder+StringDecoder 组合就是按行切换的 文本解码器,它被设计用来支持 TCP 的粘包和拆包。
Java序列化的目的主要有两个:
1: 网络传输 2:对象持久化
Java序列化从 JDK1.1已经提供,只需要实现 java.io.Serializable 并生成序列 ID即可
缺点:
1: 无法跨语言,由于Java序列化技术是Java语言内部的私有协议,其他语言并不支持,对于用户来说,他完全是黑盒。对于Java序列化后的字节数组,别的语言无法进行反序列化。
2:序列化后的码流太大 (JDK序列化机制编码后的二进制数组大小比二进制编码大几倍)在同等情况下,编码后的字节数组越大,存储的时候就越占空间,存储的硬件成本就越高,并且在网络传输时更占宽带,导致系统的吞吐量降低。
3:序列化性能太低 同等情况下,Java序列化的性能是二进制编码的很多倍。
编解码框架选择:
1:Google的 Protobuf 它的特点是: 结构化数据存储格式(XML,JSON 等)
2:高效的编解码性能
3:语言无关,平台无关,扩展性好
4:官方支持Java,C++和 Python 三种语言
Protobuf 具有 数据描述文件和代码生成机制,利用数据描述文件对数据结构进行说明的优点有:
1:文本化的数据结构描述语言,可以实现语言和平台无关,特别适合异构系统间的集成
2:通过标识字段的顺序,可以实现协议的前向兼容
3:自动代码生成,不需要手工编写同样数据结构的 C++ 和Java 版本
4:方便管理和维护,相比于 代码,结构化的文档更容易管理和维护
Facebook的 Thrift
对于 Facebook来说,创造Thrift是为了解决Facebook各系统间大数据量的传输通信
以及系统之间语言环境不同需要跨平台的特性,因此Thrift可以支持多种程序语言
Thrift可以作为高性能的通信中间件使用,它支持数据对象 序列化和多种类型的 RPC服务。
Thrift适用于静态的数据交换,需要先确定好它的数据结构,
当数据结构发生变化时,必须重新编辑IDL文件,生成代码和编译。
Thrift适用于搭建大型数据交换或 存储的通用工具,对于大型系统中的内部数据传输,
相对于 Json 和 XML在性能和传输大小上都有明显的优势
Thrift主要有 5部分组成
1:语言系统以及 IDL编译器,负责由用户给定的IDL文件生成相应语言的接口代码;
2:TProtocol: RPC协议层,可以选择多种不同的对象序列化方式,如 Json 和 Binary
3:TTransport:RPC 的传输层,同样可以选择不同的传输层实现,如socket,NIO,MemoryBuffer
4:TProcessor:作为协议层和用户提供的服务实现之间的纽带,负责调用服务实现的接口
5:TServer:聚合 TProtocol,TTransport,和 TProcessor等对象
Thrift支持三种比较典型的编解码方式:
1:通用的二进制编解码
2:压缩二进制编解码
3:优化的可选字段压缩编解码
JBoss Marshalling 介绍
JBoss 是一个Java对象的序列化 API包,修正了 JDK 自带的序列化包的很多问题,但又保持跟 java.io.Serializable 接口的兼容,同时,增加了一些可调的参数和附加的特性,并且这些参数和特性可通过工厂类进行配置,相比于传统的Java序列化机制,它的优点是:
1:可插拔的类解析器,提供更加便捷的类加载定制策略,通过一个接口即可实现定制
2:可插拔的对象替换技术,不需要通过继承的方式。
3:可插拔的预定义类缓存表,可以减小序列化的字节数组长度,提升常用类型的对象序列化性能。
4:无须实现 jaa.io.Serializable 接口,即可实现Java序列化
5:通过缓存技术提升对象的序列化性能。
Google Protobug 编解码特点:
跨语言,支持多语言,编码后的消息更小,更加有利于存储和传输,编解码性能高,支持不同协议版本的前向兼容,支持定义可选和必选字段。