TCP粘包问题

TCP特性

TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。

  • 面向连接:⼀定是⼀对⼀才能连接,不能⼀个主机同时向多个主机发送消息,即⼀对多是⽆法做到的;
  • 可靠的:⽆论⽹络中出现了怎样的变化,TCP 都可以保证数据⼀定能够到达接收端,数据在传输过程中不会消失(TCP 通过序列号和包重传确认机制保证数据包的有序和一定被正确发到目的地);
  • 字节流:数据传输像流水一样是没有边界的,所以⽆论我们消息有多⼤都可以进⾏传输;
    消息是有序的,当前⼀个消息没有收到的时候,即使它先收到了后⾯的字节,那么也不能扔给应⽤层去处理,同时对重复的报⽂会⾃动丢弃。

产生原因

  • 粘包,所谓粘包就是连续给发送端发送两个或者两个以上的数据包,接收端在一次收取中,可能收到的数据包可能是几个包(包括一个)加上某个包的部分,或者干脆就是几个完整的包在一起。
  • 半包,则是可能收到的数据只是一个包的部分。

进一步具体讲:

  • TCP发送方:TCP本身传输的数据包大小有限制,如果应用发出的消息包过大,TCP会把应用消息包拆分为多个TCP数据包发送出去;如果应用发送数据包太小,TCP为了减少网络请求次数的开销,它会等待多个消息包一起,打成一个TCP数据包一次发送出去。
  • TCP接收方:TCP 缓冲区里的数据都是字符流的形式,没有明确的边界,因为数据没边界,所以应用从TCP缓冲区中读取数据时就没办法指定一个或几个消息一起读,而只能选择一次读取多大的数据流,而这个数据流中就可能包含着某个消息包的一部分数据。

解决方法

定长消息

规定每个数据包的大小,如64k,每次只取64k,如果不够64k就等待满足后取出

缺点:这种方式只有在确定包等于64k时才推荐使用,如果小于64k,需要在数据末尾填充占位符,浪费带宽;如果大于64k要分包发送,并且额外再写处理逻辑

特殊标志作为结束符

在数据包中遇到规定好的特殊的符号值时就认为到一个包的末尾了。

缺点:如果包中需要包含这个特殊符号值时,只能做转义处理

包头 + 包体

为数据包添加一个包头,包头记录着数据包的长度

缺点:会浪费传输包头的带宽

这种方法是我目前正在使用的方法,取数据时先取出包头,再根据包头取出所需要的数据

实现思路

首先定义一个缓存区,包头+包体不会存在大于此缓存区的情况,每次将接收的数据放入缓存区,再通过包头取出所需要的数据

定义未使用空间,每次取数据时,记录一个包大小之后未使用的空间,如果剩余空间小于未使用的空间,说明未使用的空间中还有数据,此时出现粘包现象,如下图:

tcp_sticking

处理逻辑如下:

tcp_sticking

代码实现参考Cpackage对象中的接收数据包部分内容


TCP粘包问题
https://carl-5535.github.io/2022/10/26/工作总结/TCP粘包问题/
作者
Carl Chen
发布于
2022年10月26日
许可协议