Socket 中级篇(四) TCP组包、粘包、拆包的原理

第一章、简介

1、这篇博客我想阐述什么?

       在利用Socket+Tcp通信中,如果不采用开源库(比如SuperSocket)来实现,那么就是自己写了。既然自己写的东西,必须考虑稳定性、有效性、可行性等等。

       直到今天,在用Socket/TCP做视频监控过程中,当网络较慢时,Socket/tcp通信,并不能一次性地把发送缓存区的数据发送出去。如果接受端不做处理,而是按接收缓存区收到多少数据,每次就拿这么一点数据,肯定是恢复不出来原来的数据。

      此外,如果要传大文件,还是要用TCP,以免文件损坏,整个文件都需要重新传。

2、SOCKET/TCP通信经常遇到的问题

2.1、粘包(详情见第二章)

2.2、拆包(详情见第二章)

2.3、理论上不会丢包。TCP是可靠的连接,协议里已经处理了丢包/误包重发的机制。

       传输的数据率大于可用带宽时,链路发生拥塞,tcp就会丢包。另外,如果链路的误码率比较高,tcp数据包出现误码,也会丢包。传输太快,丢包是必然的,可以做一个线程等待,发送请求后,等待一定的时间,然后再接收就可以了,返回的答复信息会在缓存中存在一段时间,所以等待是没有问题的。

2.4、理论上不会误包。

3、解决办法

3.1、发送端

      组包(详情见第二章)

 3.2、接收端

      将Socket.received()缓存区的数据,拿出来,放到一个队列里面,每次从队列按固定长度读取这些数据。

第二章、TCP粘包,拆包及解决方法 

请参考我的博客,已经在视频监控中,解决了粘包和拆包问题。

(七)WPFC# 视频监控画面的传输:使用Aforge类库打开USB摄像头循环采集每一帧图像给Socket传输,并解决粘包、拆包问题

  转https://blog.csdn.net/scythe666/article/details/51996268 以下是转发的部分内容

         我们都知道TCP属于传输层的协议,传输层除了有TCP协议外还有UDP协议。那么UDP是否会发生粘包或拆包的现象呢?答案是不会。UDP是基于报文发送的,从UDP的帧结构可以看出,在UDP首部采用了16bit来指示UDP数据报文的长度,因此在应用层能很好的将不同的数据报文区分开,从而避免粘包和拆包的问题。而TCP是基于字节流的,虽然应用层和TCP传输层之间的数据交互是大小不等的数据块,但是TCP把这些数据块仅仅看成一连串无结构的字节流,没有边界;另外从TCP的帧结构也可以看出,在TCP的首部没有表示数据长度的字段,基于上面两点,在使用TCP传输数据时,才有粘包或者拆包现象发生的可能。

1、粘包、拆包表现形式

现在假设客户端向服务端连续发送了两个数据包,用packet1和packet2来表示,那么服务端收到的数据可以分为三种,现列举如下:

第一种情况,接收端正常收到两个数据包,即没有发生拆包和粘包的现象,此种情况不在本文的讨论范围内。normal

第二种情况,接收端只收到一个数据包,由于TCP是不会出现丢包的,所以这一个数据包中包含了发送端发送的两个数据包的信息,这种现象即为粘包。这种情况由于接收端不知道这两个数据包的界限,所以对于接收端来说很难处理。one

第三种情况,这种情况有两种表现形式,如下图。接收端收到了两个数据包,但是这两个数据包要么是不完整的,要么就是多出来一块,这种情况即发生了拆包和粘包。这两种情况如果不加特殊处理,对于接收端同样是不好处理的。half_oneone_half

2、粘包、拆包发生原因

发生TCP粘包或拆包有很多原因,现列出常见的几点,可能不全面,欢迎补充,

1、要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。

2、待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。

3、要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。

4、接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。

等等。

3、粘包、拆包解决办法

通过以上分析,我们清楚了粘包或拆包发生的原因,那么如何解决这个问题呢?解决问题的关键在于如何给每个数据包添加边界信息,常用的方法有如下几个:

1、发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。

2、发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。

3、可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。

等等。

第三章、案例分析

以下是转发的部分内容

https://blog.csdn.net/snipergzf/article/details/50810013

问题的表述
问题的背景是这样的:有一个系统,那有后台服务器,也有移动端的客户端。当客户端上线时,服务器会将指定的数据库的数据发送给客户端,客户端解析后呈现。 
但是,有一天客户端开发跟我反映一个奇怪的问题:之前客户端一上线就收到服务器发来的数据,现在客户端收到了数据,但是解析出问题了。但是前后端的代码都没有改啊,只是要发送的数据是新添加的,并且比之前的测试数据要大。

原因的剖析
根据他的描述,很明显问题出在了要发送的数据上。由于之前是测试数据,并不会放过大的数据到后台数据库,现在放上去的是真实数据,要比测试数据大好多。

于是我就重新拾起了尘封已久的tcp/ip协议的书,也试着在网上寻找答案。最终我知道了我们这个问题叫做tcp协议的组包问题。

什么叫tcp的组包问题呢?简单的说就是tcp协议把过大的数据包分成了几个小的包传输,客户端要把同一组的数据包重新组合成一个完整的数据包。

具体点的解释:

首先我们要知道MTU(最大传输单元)。IP分片在以太网上,由于电气限制,一帧不能超过1518字节,除去以太网帧头14字节(mac地址等)和帧尾4字节校验,不考虑PPPoE协议(读者自行了解这是什么鬼)的8个字节,还剩1500字节,这个大小称为MTU(最大传输单元)。 
这1500还包含20字节IP头,8字节UDP头或者20字节TCP头。所以真正的一次发送的数据包,UDP为1500-28=1472字节,TCP为1500-40=1460字节。 
当然我们要知道tcp是可靠传输协议,以字节流的形式传输数据的,什么意思呢?就是他可以允许超过1452字节的数据进行传输,因为他会把它切分成多个包,然后用序号进行排序,从而表明了这几个包是同一组数据。 
但是UDP是不可靠传输,它一次性就只能发那么多了,超过的他就不允许发了,所以他效率高,发送一次就是一个完整的数据,接收方直接拿去解析就可以了。(UDP和TCP还有很多不同的优缺点,读者感兴趣可以自行了解)

所以了解了这些之后对于上面描述的问题现象也就不难解释了,由于我们后台服务器发送的数据包过大,比如有5000字节,那么tcp协议就会把它分成1460+1460+1460+620字节,分四个包发送给客户端,客户端单单解析一个数据包得到的数据肯定是不完整的,要是传输的数据是带一定的结构的话就会解析出错了。

发送端处理方法
那么知道了产生这个问题的原因,又该怎么解决这个问题呢。我在一个网站上看到有如下回答:链接 
一 可以每次发送同样大小的包,过大的包不予发送,过小的包,后面部分用固定的字符’\0’进行填充。 
二 将流按字符处理,抽出一个字符做转义字符(通常Java用’\’来做转义字符,比如”\n”表示换行)。 
三 在发送方发送一个包的时候,先将这个包的长度发送给对方(一般是4个字节表示包长),然后再将包的内容发送过去.接收方先接收4个字节,看看包的长度,然后按照长度来接收包。

于是我采用了最后一种解决办法,服务器其发送的数据中,前4个字节为需要发送的数据的字节数,如上所说,这样的做法其实就是在TCP协议之上又在封装了一层,只不过很简单明了
 

三、Tcp协议(以上标红的字体,都是在Tcp协议的基础上,再次进行组包、拆包

要想弄明白或实现TCP传输,必须弄明白Tcp协议,重而实现可靠性的传输。

https://wenku.baidu.com/view/935d940402020740be1e9b80.html?rec_flag=default&sxts=1541642910236

https://wenku.baidu.com/view/8898edfdc8d376eeaeaa31a8.html?re=view&rec_flag=default&sxts=1541643046482
 

  • 6
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Unity实现Socket通讯时,常常会遇到TCP粘包拆包的问题。下面我将介绍在Unity中如何解决这些问题。 TCP粘包是指在传输过程中,由于数据缓冲区的限制,多个小的数据包可能会被合并成一个大的数据包,导致数据的解析和处理出现问题。为了解决这个问题,可以通过以下两种方式来处理。 第一种方式是定长包头+包体的设计。即在数据包前面添加一个固定长度的包头,包头中包含了包体的长度信息。接收方在接收数据时,首先读取包头的长度信息,然后再根据长度信息读取相应长度的数据进行解析和处理。 第二种方式是使用特殊的字符序列作为包的分隔符。例如,在每个数据包的末尾添加一个换行符或其他不常用的字符作为分隔符。接收方在接收数据时,通过查找这个分隔符来确定包的结束位置,然后对数据进行解析和处理。 TCP拆包是指在传输过程中,一个大的数据包可能会被拆分成多个小的数据包,导致数据的解析和处理出现问题。为了解决这个问题,可以通过以下方式来处理。 可以在接收方使用缓冲区来接收数据,并且设置一个最大接收长度。当接收到的数据长度小于最大接收长度时,将数据放入缓冲区中,并在缓冲区中进行数据的拼接。当接收到的数据长度大于等于最大接收长度时,对缓冲区中的数据进行解析和处理,并清空缓冲区。 以上是Unity实现Socket通讯时解决TCP粘包拆包问题的方法。希望对你有帮助。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我爱AI

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值