粘包/拆包 问题说明

  假设客户端分别发送了两个数据包给服务端,由于服务端一次读取到的字节数不确定,会存在 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 编解码特点: 

跨语言,支持多语言,编码后的消息更小,更加有利于存储和传输,编解码性能高,支持不同协议版本的前向兼容,支持定义可选和必选字段。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1.TCP是流传输,所以本质上不该称之为粘包,发送者和接收者都是自顾自的,要一段一段发得选UDP 现象是,你发出3个2048字节的包,发送时会被优化成4096/2048,或者跟之前的包合并,但是取出时却很随意,除了空载时的首个包,其他有可能拆成1-4096大小若干个包 2.“粘包”和“分包”很头疼,但是只要抓住第一个包,问题就解决了一大半 幸运的是,TCP发出的包不是乱序的,这有点像你按顺序写出字节集一样,只要你抓住头部的定义,就能轻而易举解构数据 为此,发送时,首个包要跟前一段数据流有时间间隔,好让之前的Recv操作完成(当然,条件允许可以Recv完成后反馈,发送端收到反馈消息再继续下一波) 3.定义协议结构,各有各的办法,以下代码仅作参考 .版本 2     pocket = 取空白字节集 (#pk_size)     DataAddr = 取变量指针 (pocket) + 8     pk_sign = 取字节集数据 (到字节集 (“P_KT”), #整数型, )     写数值ptr (DataAddr, #pk_sign, pk_sign)     写数值ptr (DataAddr, #pk_crch, CRC32all)     写数值ptr (DataAddr, #pk_crc32, CRC32all)     写数值ptr (DataAddr, #pk_SN, 集_SN)     写数值ptr (DataAddr, #pk_remain, size)     CRC32pk = CRC32_PTR (DataAddr, #pk_size)     写数值ptr (DataAddr, #pk_crch, CRC32pk) 4.使用哈希表存储分包数据 我认为哈希表存储的方式是线程安全的,客户句柄是唯一的,一个客户甚至分不到一条线程,该句柄对应的数据地址是唯一的,所以不会出现两条线程同时操作一个内存地址的情形 当然,出现碰撞时,插入链表这个操作不是线程安全的,这个以后优化 5.星光极速模块我只稍微改了一下,把原先字节集操作改成指针操作 6.没有选择HP-socket的原因是太庞大了,用来做服务端可以,但是如果作为客户端即使是静态库,编译之后也很大 7.目前涉分包组包的代码不多,其他的运用过程中不断改进 关于哈希表的部分,我专门开了个帖子 https://bbs.125.la/forum.php?mod=viewthreadtid=14659403

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值