加入收藏 | 设为首页 | 会员中心 | 我要投稿 核心网 (https://www.hxwgxz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 教程 > 正文

TCP粘包、拆包与通信协议详解

发布时间:2019-10-17 23:18:50 所属栏目:教程 来源:田守枝
导读:在TCP编程中,我们使用协议(protocol)来解决粘包和拆包问题。本文将详解TCP粘包和半包产生的原因,以及如何通过协议来解决粘包、拆包问题。让你知其然,知其所以然。 1 TCP粘包、拆包图解 由于TCP传输协议面向流的,没有消息保护边界。一方发送的多个报文

将消息区分为消息头和消息体,在消息头中,我们使用一个整形数字,例如一个int,来表示消息体的长度。而消息体实际实际要发送的二进制数据字节。以下是一个基本格式:

  1.  header    body 
  2. +--------+----------+ 
  3. | Length |  Content | 
  4. +--------+----------+ 

在变长协议中:

  • 发送方,发送数据之前,需要先获取需要发送内容的二进制字节大小,然后在需要发送的内容前面添加一个整数,表示消息体二进制字节的长度。
  • 接收方,在解析时,先读取内容长度Length,其值为实际消息体内容(Content)占用的字节数,之后必须读取到这么多字节的内容,才认为是一个完整的数据报文。

提示:Netty中提供了LengthFieldPrepender给实际内容Content进行编码添加Length字段,接受方使用LengthFieldBasedFrameDecoder解码。

3.4 序列化

序列化本质上已经不是为了解决粘包和拆包问题,而是为了在网络开发中可以更加的便捷。在变长协议中,我们看到可以在实际要发送的数据之前加上一个length字段,表示实际要发送的数据的长度。这实际上给我们了一个很好的思路,我们完全可以将一个对象转换成二进制字节,来进行通信,例如使用一个Request对象表示请求,使用一个Response对象表示响应。

序列化框架有很多种,我们在选择时,主要考虑序列化/反序列化的速度,序列化占用的体积,多语言支持等。下面列出了业界流行的序列化框架:

TCP粘包、拆包与通信协议详解

提示:xml、json也属于序列化框架的范畴,上面的表格中并没有列出。

一些网络通信的RPC框架通常会支持多种序列化方式,例如dubbo支持hessian、json、kyro、fst等。在支持多种序列化框架的情况下,在协议中通常需要有一个字段来表示序列化的类型,例如,我们可以将上述变长协议的格式改造为:

  1. +--------+-------------+------------+ 
  2. | Length |  serializer |   Content  | 
  3. +--------+-------------+------------+ 

这里使用1个字节表示Serializer的值,使用不同的值代表不同的框架。

发送方,选择好序列化框架后编码后,需要指定Serializer字段的值。

接收方,在解码时,根据Serializer的值选择对应的框架进行反序列化;

3.5 压缩

通常,为了节省网络开销,在网络通信时,可以考虑对数据进行压缩。常见的压缩算法有lz4、snappy、gzip等。在选择压缩算法时,我们主要考虑压缩比以及解压缩的效率。

我们可以在网络通信协议中,添加一个compress字段,表示采用的压缩算法:

  1. +--------+-----------+------------+------------+ 
  2. | Length | serializer|  compress  |   Content  | 
  3. +--------+-----------+------------+------------+ 

通常,我们没有必要使用一个字节,来表示采用的压缩算法,1个字节可以标识256种可能情况,而常用压缩算法也就那么几种,因此通常只需要使用2~3个bit来表示采用的压缩算法即可。

另外,由于数据量比较小的时候,压缩比并不会太高,没有必要对所有发送的数据都进行压缩,只有再超过一定大小的情况下,才考虑进行压缩。如rocketmq,producer在发送消息时,默认消息大小超过4k,才会进行压缩。因此,compress字段,应该有一个值,表示没有使用任何压缩算法,例如使用0。

3.6 查错校验码

一些通信协议传输的数据中,还包含了查错校验码。典型的算法如CRC32、Adler32等。java对这两种校验方式都提供了支持,java.util.zip.Adler32、java.util.zip.CRC32。

  1. +--------+-----------+------------+------------+---------+ 
  2. | Length | serializer|  compress  |   Content  |  CRC32  | 
  3. +--------+-----------+------------+------------+---------+ 

这里并不对CRC32、Adler32进行详细说明,主要是考虑,为什么需要进行校验?

有人说是因为考虑到安全,这个理由似乎并不充分,因为我们已经有了TLS层的加密,CRC32、Adler32的作用不应该是为了考虑安全。

一位同事的观点,我非常赞同:二进制数据在传输的过程中,可能因为电磁干扰,导致一个高电平变成低电平,或者低电平变成高电平。这种情况下,数据相当于受到了污染,此时通过CRC32等校验值,则可以验证数据的正确性。

另外,通常校验机制在通信协议中,是可选的配置的,并不需要强制开启,其虽然可以保证数据的正确,但是计算校验值也会带来一些额外的性能损失。如Mysql主从同步,虽然高版本默认开启CRC32校验,但是也可以通过配置禁用。

3.7 小结

本节通过一些基本的案例,讲解了在TCP编程中,如何通过协议来解决粘包、拆包问题。在实际开发中,通常我们的协议会更加复杂。例如,一些RPC框架,会在协议中添加唯一标识一个请求的ID,一些支持双向通信的RPC框架,如sofa-bolt,还会添加一个方向信息等。当然,所谓复杂,无非是在协议中添加了某个字段用于某个用途,只要弄清楚这些字段的含义,也就不复杂了。

(编辑:核心网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读