粘包拆包问题在数据链路层、网络层以及传输层都有可能发生。
传输层有两个协议我们都很熟悉:UDP 和 TCP,UDP有消息保护边界,不会发生粘包拆包问题,因此粘包拆包问题只发生在TCP协议中。
现象
假设客户端向服务端连续发送了两个数据包,用 packet1 和 packet2 来表示,那么服务端收到的数据可能有四种:
- 第一种情况——正常传输,服务端按顺序正常收到两个包,即未出现粘包和拆包的现象。
- 第二种情况——粘包。服务端只收到一个数据包,由于 TCP 保证送达的特性,所以这一个数据包包含了客户端发送的两个数据包的信息,这种现象就是粘包。除非客户端发送的数据包有明确的规则,否则服务端不知道两个包的界限,难以处理数据。
- 第三种情况——拆包,服务端收到了三个数据包,package1数据包被拆分为两个数据包:package1.1和package1.2,这种现象就是拆包,服务端收到拆开的数据包也不知道如何处理。
- 第四种情况——拆包和粘包综合在一起,一些大的数据包被拆分为小的数据包,小的数据包与其他数据包粘在一起,这种现象是将上面的粘包和拆包综合在一块。
原因
TCP 是一个面向“流”的协议,所谓流就是没有界限的一长串二进制数据。TCP 作为传输层协议并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行数据包的划分,所以在业务上认为是一个完整的包,但可能会被 TCP 拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就会出现粘包拆包的问题。
例如,TCP缓冲区是1024个字节大小,如果应用一次请求发送的数据量比较小,没达到缓冲区大小,TCP则会将多个请求合并为同一个请求进行发送,站在业务上来看这就是“粘包”;
如果应用一次请求发送的数据量比较大,超过了缓冲区大小,TCP就会将其拆分为多次发送,这就是“拆包”,也就是将一个大的包拆分为多个小包进行发送。
解决方法
- 添加包首部。 发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
如下图,在每个包前面加上包的实际长度。
- 封装为固定长度。 发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
下图每个包的固定长度为2,接收端很容易进行区分。
- 设置边界。 可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。
如下图,在每个包的后面加上特殊字符:/
总结
TCP 是一个面向“流”的协议,所谓流就是没有界限的一长串二进制数据。在实际的传输过程中,TCP 会根据网络情况将数据包进行拆分或者拼装,如果业务没有定义一个明确的界限规则,在应用层的业务上就会出现粘包拆包的现象。
针对 TCP 粘包拆包的现象,常见的解决思路如下:
(1)发送端给每个数据包添加包首部。
(2)发送端将每个数据包封装为固定长度(。
(3)可以在数据包之间设置边界。