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