怎么解决TCP网络传输「粘包」问题?

发布于2022-01-12 20:00:40
5个回答
admin
网友回答2022-01-12

要靠消息封装来解决,约定定长的消息头,消息头内标明消息数据长度。接收端通过消息缓存和消息头来完成消息提取。

admin
网友回答2022-01-12

设计方案一:定长发送

在进行数据发送时采用固定长度的设计,也就是无论多大数据发送都分包为固定长度(为便于描述,此处定长为记为LEN),也就是发送端在发送数据时都以LEN为长度进行分包。这样接收方都以固定的LEN进行接收,如此一来发送和接收就能一一对应了。分包的时候不一定能完整的恰好分成多个完整的LEN的包,最后一个包一般都会小于LEN,这时候最后一个包可以在不足的部分填充空白字节。

当然,这种方法会有缺陷。1.最后一个包的不足长度被填充为空白部分,也即无效字节序。那么接收方可能难以辨别这无效的部分,它本身就是为了补位的,并无实际含义。这就为接收端处理其含义带来了麻烦。当然也有解决办法,可以通过增添标志位的方法来弥补,即在每一个数据包的最前面增加一个定长的报头,然后将该数据包的末尾标记一并发送。接收方根据这个标记确认无效字节序列,从而实现数据的完整接收。2.在发送包长度随机分布的情况下,会造成带宽浪费。比如发送长度可能为 1,100,1000,4000字节等等,则都需要按照定长最大值即4000来发送,数据包小于4000字节的其他包也会被填充至4000,造成网络负载的无效浪费。

综上,此方案适在发送数据包长度较为稳定(趋于某一固定值)的情况下有较好的效果。

设计方案二:尾部标记序列

在每个要发送的数据包的尾部设置一个特殊的字节序列,此序列带有特殊含义,跟字符串的结束符标识””一样的含义,用来标示这个数据包的末尾,接收方可对接收的数据进行分析,通过尾部序列确认数据包的边界。

这种方法的缺陷较为明显:1.接收方需要对数据进行分析,甄别尾部序列。2.尾部序列的确定本身是一个问题。什么样的序列可以向””一样来做一个结束符呢?这个序列必须是不具备通常任何人类或者程序可识别的带含义的数据序列,就像“”是一个无效字符串内容,因而可以作为字符串的结束标记。那普通的网络通信中,这个序列是什么呢?我想一时间很难找到恰当的答案。

设计方案三:头部标记分步接收

这个方法是作者有限学识里最好的办法了。它既不损失效率,还完美解决了任何大小的数据包的边界问题。

这个方法的实现是这样的,定义一个用户报头,在报头中注明每次发送的数据包大小。接收方每次接收时先以报头的size进行数据读取,这必然只能读到一个报头的数据,从报头中得到该数据包的数据大小,然后再按照此大小进行再次读取,就能读到数据的内容了。这样一来,每个数据包发送时都封装一个报头,然后接收方分两次接收一个包,第一次接收报头,根据报头大小第二次才接收数据内容。(此处的data[0]的本质是一个指针,指向数据的正文部分,也可以是一篇连续数据区的起始位置。因此可以设计成data[user_size],这样的话。)

下面通过一个图(见附图)来展现设计思想。

由图看出,数据发送多了封装报头的动作;接收方将每个包的接收拆分成了两次。

这方案看似精妙,实则也有缺陷:1.报头虽小,但每个包都需要多封装sizeof(_data_head)的数据,积累效应也不可完全忽略。2.接收方的接收动作分成了两次,也就是进行数据读取的操作被增加了一倍,而数据读取操作的recv或者read都是系统调用,这对内核而言的开销是一个不能完全忽略的影响,对程序而言性能影响可忽略(系统调用的速度非常快)。

优点:避免了程序设计的复杂性,其有效性便于验证,对软件设计的稳定性要求来说更容易达标。综上,方案三乃上上策!

admin
网友回答2022-01-12

以上图的协议结构,来举个粟子:

1. 我们收到完整的一个协议串为hex码为:

232301314731424c35325037545231313535323001000000

2.对应的拆解一下,各项数值为

3. 而我们收到粘包的时,或者半包的情况是这样的。

3.1 场景1

一次收到完整两个数据串:

232301314731424c35325037545231313535323001000000232301314731424c35325037545231313535323001000000

3.2 场景2

一次收到一个半的数据串:

232301314731424c35325037545231313535323001000000232301314731424c35325037545231

第二次会收到剩余的数据串:

313535323001000000

4. 通过第三点的我们可以看到我们收到粘包,半包,大概就这些情况。一般处理的方法也很简单,就通过起始符处理分割一下就可以。这个协议在定义的时候,就考虑到。

如:遇到场景1 通过起始符进行分割,就可以拆成两个单独的包了。

如:遇到场景2 通过起始符进行分割,可以拿到第一个包。再继续等待后面的数据包过来,这样第二个包也就完整了。

这种具体代码中处理的时候,流程大概是这样:

4.1 读取流的数据,

4.2 检查数据格式

4.3 如果满足一个整包,就丢业务线程

4.4 如果不满足,将读标重置为读取之前就好.(当前也适当的间隔检索一下缓冲区,以防垃圾包太多过来让缓冲区爆了,程序挂了)

admin
网友回答2022-01-12

回答问题之前,先来纠正题主的一个错误概念,TCP是一种基于字节流的协议,根本不存在所谓的“包”,更不必说粘包。我想题主应该想问:TCP传输协议下,应用层数据发送和接收问题。


官方文档说的已经很清楚了,send和recv的返回值表示成功发送/接收的字节数。

  • man send

  • man recv

此时,不仅不会有「粘包」的错觉,甚至如何解决都知道了。数据没发完?继续发呗;没收完?继续收就行了。那么如何知道数据没有收完呢?其实约定个特殊字符作为结束符(比如HTTP协议以 作为结束标志)或者提前约定好数据长度就可以了。


实例:

下面看看项目开发中常用的方法,「设置定长消息」。先收取一个固定大小的包头信息,接着根据包头里面指定的包体大小来收取包体大小。具体的代码示例如下所示:


程序员,一定要多看官方文档!多看官方文档!多看官方文档!这可比某些所谓的博客要严谨的多。

本文为作者“一个程序员的奋斗史”悟空问答原创文章,未经允许转载、抄袭必究!
admin
网友回答2022-01-12

TCP粘包是指发送方发送的多个数据包到接收方后粘连在一起,导致数据包不能完整的提现发送的数据。

TCP协议

TCP是一个面向连接的传输层协议,不属于ISO制定的协议集。TCP协议在商业界和工业界的成功应用,使它成为事实上的网络标准,广泛应用于各种网络主机间的通信。

TCP目标是为用户提供可靠的端到端连接,保证信息有序无误的传输。TCP为确保可靠性采用了数据编号、校验和计算、数据确认等一系列措施。

TCP对传送的每个数据字节都进行编号,并请求接收方回传确认信息(ACK)。发送方如果在规定的时间内没有收到数据确认,就重传该数据。

  • 数据编号使接收方能够处理数据的失序和重复问题。
  • 数据误码问题通过在每个传输的数据段中增加校验和予以解决,接收方在接收到数据后检查校验和,若校验和有误,则丢弃该有误码的数据段,并要求发送方重传。
  • 流量控制也是保证可靠性的一个重要措施,若无流控,可能会因接收缓冲区溢出而丢失大量数据,导致许多重传,造成网络拥塞恶性循环。
  • TCP采用可变窗口进行流量控制,由接收方控制发送方发送的数据量。

这些可靠性保障措施为用户提供了高可靠性的网络传输服务,但也影响了传输效率。在实际工程应用中,只有关键数据的传输才采用TCP,而普通数据的传输一般采用高效率的UDP。

UDP不会出现粘包问题。UDP支持的是一对多的模式,不会使用块的合并优化算法,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(包含消息来源地址,端口等信息),接收端很容易就能进行区分处理了。

粘包出现原因

出现粘包现象的原因有很多方面,它既可能由发送方造成的,也可能是由接收方造成的。

发送方原因

TCP需要尽可能高效和可靠,默认采用Nagle算法,发送方往往要收集到足够多的数据后合并相连的小数据包,才发送一包数据,这样接收方就收到了粘包数据。但接收方并不知晓发送方合并数据包,并数据包的合并在TCP协议中是没有分界线的,就会导致接收方不能还原其本来的数据包。

接收方原因

TCP是基于“流”的。网络传输数据的速度可能会快过接收方处理数据的速度,这时候就会导致,接收方在读取缓冲区时,缓冲区存在多个数据包。在TCP协议中接收方是一次读取缓冲区中的所有内容,就不能反映原本的数据信息。

粘包情况有两种:

一种是粘在一起的包都是完整的数据包;

一种是粘在一起的包有不完整的包;

不是所有的粘包现象都需要处理

如果传输的数据为不带结构的连续流数据(如文件传输),就不必把粘连的包分开(简称分包)。但实际工程应用中一般为带结构的数据,这时就需要做分包处理。

在处理定长结构数据的粘包问题时,分包算法比较简单;

在处理不定长结构数据的粘包问题时,分包算法就比较复杂。

特别是粘在一起的包有不完整的包的粘包情况,一包数据内容被分在了两个连续的接收包中,处理起来难度较大。实际工程应用中应尽量避免出现粘包现象。

为了避免粘包现象,可采取以下几种措施:

(1)发送方引起的粘包可通过编程设置来避免。如:PUSH标志是TCP提供了强制数据立即传送的操作指令,TCP软件收到该操作指令后,就立即将本段数据发送出去,而不必等待发送缓冲区满。

缺点:虽然可以避免发送方引起的粘包,但关闭了Negle优化算法,降低了网络发送效率,影响应用程序的性能,一般不建议使用。

(2)接收方引起的粘包,可通过优化程序设计、精简接收进程工作量、提高接收进程优先级等措施来及时接收数据,尽量避免出现粘包现象。

缺点:只能减少出现粘包的可能性,但并不能完全避免粘包,当发送频率较高或某个时间段数据包到达接收方较快,接收方还是有可能来不及接收,导致粘包。

(3)由接收方控制,将一包数据按结构字段,人为控制分多次接收,然后合并,通过这种手段来避免粘包。

缺点:应用程序的效率较低,对实时应用的场合不适合。

一种比较周全的对策是:接收方创建一预处理线程,对接收到的数据包进行预处理,将粘连的包分开。另外,普通数据的传输采用UDP,而重要的数据采用TCP。由于UDP不是面向‘流’的,而且UDP是具有消息边界的。也就是说UDP的发送的每一个数据包都是独立的。所以UDP并不存在粘包的问题。

以上个人浅见,欢迎批评指正。喜欢的可以关注我,谢谢!

认同我的看法的请点个赞再走,再次感谢!

回到
顶部