现象
发送方跟接收方都可能发生粘包现象。
发送方:
TCP为了提高传输效率,会收集足够多的数据后才一起发送;另外一条数据过长,TCP会进行拆分发送。于是导致如下三种情况:
1. 多条合并为一条发送
2. 长数据被拆分成片段发送
3. 长数据被拆分成片段跟短数据合并发送
接收方:
接收方收到的数据会保存在网络缓冲区中,如果应用层提取数据不够快就会导致缓存中多条数据粘在一起
处理
注意:不是所有时候都要去处理粘包,比如类似于文件传输。
发送方的处理方式
可以关闭Nagle算法,通过TCP_NODELAY选项来关闭。缺点是TCP传输效率降低
Nagle算法主要做两件事:1)只有上一个分组得到确认,才会发送下一个分组;2)收集多个小分组,在一个确认到来时一起发送。正是Nagle算法造成了发送方有可能造成粘包现象。
接收方的处理方式
TCP中接收方无法处理!
应用层的处理方式
应用层的处理简单易行!并且不仅可以解决接收方造成的粘包问题,还能解决发送方造成的粘包问题。
解决办法:循环处理,应用程序从接收缓存中读取分组时,读完一条数据,就应该循环读取下一条数据,直到所有数据都被处理完成,但是如何判断每条数据的长度呢?
1. 格式化数据,每条数据都要实现约定好格式(开始符、结束符),显而易见在数据内部中不能出现开始符和结束符!
2. 发送长度:发送每条数据时,将数据的长度一并发送,例如规定数据的前4位是数据的长度,应用层在处理时可以根据长度来判断每个分组的开始和结束位置。
封包处理粘包问题
所谓封包,就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容,包头其实上是个大小固定的结构体,其中有个结构体成员变量表示包体的长度,这是个很重要的变量,其他的结构体成员可根据需要自己定义。根据包头长度固定以及包头中含有包体长度的变量就能正确地拆分出一个完整的数据包。对于拆包,目前最常用的是以下两种方式:
(1)动态缓冲区暂存方式
动态是指当需要缓冲的数据长度超出缓冲区的长度时会增大缓冲区长度
大概过程描述如下:
为每一个连接动态分配一个缓冲区,同时把此缓冲区和SOCKET关联,常用的是通过结构体关联当接收到数据时首先把此段数据存放在缓冲区中判断缓存区中的数据长度是否够一个包头的长度,如不够,则不进行拆包操作根据包头数据解析出里面代表包体长度的变量判断缓存区中除包头外的数据长度是否够一个包体的长度,如不够,则不进行拆包操作取出整个数据包,这里的"取"的意思是不光从缓冲区中拷贝出数据包,而且要把此数据包从缓存区中删除掉,删除的办法就是把此包后面的数据移动到缓冲区的起始地址(环形缓冲可以优化这里)
这种方法有两个缺点:
为每个连接动态分配一个缓冲区增大了内存的使用.
有三个地方需要拷贝数据,一个地方是把数据存放在缓冲区,一个地方是把完整的数据包从缓冲区取出来,一个地方是把数据包从缓冲区中删除.第二种拆包的方法会解决和完善这些缺点.可以采用环形缓冲(定义两个指针,分别指向有效数据的头和尾,在存放数据和删除数据时只是进行头尾指针的移动),但是还是不能解决第一个缺点以及第一个数据拷贝,只能解决第三个地方的数据拷贝(这个地方是拷贝数据最多的地方)。第2种拆包方式会解决这两个问题。
(2)利用底层的缓冲区来进行拆包
由于TCP也维护了一个缓冲区,所以我们完全可以利用TCP的缓冲区来缓存我们的数据,这样一来就不需要为每一个连接分配一个缓冲区了。另一方面我们知道recv或者wsarecv都有一个参数,用来表示我们要接收多长长度的数据.利用这两个条件我们就可以对第一种方法进行优化。
直接用底层的缓冲区直接进行拆包,固然可以免于拷贝到缓冲区,但是这样每次接收,都要做逻辑处理,对于频繁接收数据的场景,这种方法效率并不高。而如果我们一次性接收到很多数据,然后利用动态缓冲区暂存,进行异步处理。一次接收,多次处理,效率反而会更高。

TCP在传输过程中可能出现粘包,主要是由于Nagle算法导致的数据合并。发送方可以通过关闭Nagle算法避免,而接收方处理粘包通常需要在应用层进行,例如通过设定数据格式或发送长度来区分不同数据。应用层的封包处理是一种常见解决方案,包括添加包头以指示数据长度。
1275

被折叠的 条评论
为什么被折叠?



