当前位置:首页>开发>正文

怎么解决TCP网络传输“粘包”问题

2023-06-08 22:22:59 互联网 未知 开发

怎么解决TCP网络传输“粘包”问题?

怎么解决TCP网络传输“粘包”问题

udp socket不存在粘包问题, 但是存在乱序, 丢包, 重复到达的情况

假设在没丢包的情况下

又假设你的应用层缓冲区和想要取的字节足够大(至少长度要大于你要取出来的udp包)

udp的包可以完整干净的取出来, 包与包之间数据彼此独立(不粘包), 包内部数据不会乱序

就是说你发送端发送了两个包, 一个ABC三个字节, 另外一个是DEF三个字节, 只要没丢包没重复到达的情况下, 接收端收到的两个包肯定是一个[ABC], 另外一个是[DEF]


但是tcp的socket就有可能粘包(粘包是常态)

毕竟TCP是"流协议"(stream), 是"无记录边界(字节流)"的协议

仔细理解"流"的概念, 看看江河湖海里面的水流甚至你家水龙头里的自来水

里面的水流(字节)是不是连续的, 假设只有一个出口(peer)的前提下, 源源不断的字节流流出来, 无法断定这一段流里面是包含了多个数据包, 还是一个恰好完整的包, 还是只包含了半个包 (另外的半个包得继续等接下来的字节流.)

并且往往想要取到后面的水流, 你得把这之前的水流都取出来才可以(有序). 常规操作下不能直接取某一段(尤其是后面)的水流.

发送端同样发两个包, 一个ABC三个字节, 另外一个是DEF三个字节

这里假设接收端每次内核缓冲区有数据就读取全部数据, 且应用层缓冲区足够大

那么接收端

可能返回2次, [ABC][DEF], 这种情况就如同发送端发送的情况一样

可能返回3次, [AB][CD][EF]

也可能返回一次[ABCDEF], 比如发送的时候网络突然不太好, 一直在积压, 突然网络畅通了一次都发出去了

甚至可能返回6次, [A], [B], [C], [D], [E], [F]网络断断续续

这就是为啥大部分tcp都要自己搞tlv(往往定义个定长的包头, 包头里面带着变长的包体长度)来定义一个应用层数据包, 这样可以从源源不断的流中一个接一个区分(截取)出多个具体的数据包

首先,TCP是流协议,根本不存在所谓粘包一说。

简单地说,TCP保证发送方以什么顺序发字节流,接收方就一定能按这个顺序接收到,或者因为网络超时返回错误。这个是操作系统保证的,应用程序根本不用管也控制不了。

题主的问题是发送方应该以什么格式发送数据,接收方能正确解析出数据,这个叫应用层协议,你自己定,跟TCP完全无关。如果是发文件,最简单的你可以用http协议封装,如果你发的http协议数据是100%正确的,无论哪个接收方(nginx/tomcat/iis)保证能一字节不差地收下,因为http协议本身就带header和body,header里有Content-Length: 12345指定了body的大小,body才是文件本身。

你不用http协议,直接发文件数据,那么问题来了,接收方怎么知道应该收多少字节后文件结束?题主的方法是发送方暂停0.1s这样接收方如果0.1s没收到那么自己认为文件收完了,这种方式一看就是拼概率,假定是千兆网,根本不可能适应不同网络。

还有就是文档里明明白白说了,send和recv的返回值表示成功发送/接收的字节数,具体原文档说明如下:

send(2) Upon successful completion, the number of bytes which were sent is returned. Otherwise, -1 is returned and the global variable errno is set to indicate the error.

recv(2) These calls return the number of bytes received, or -1 if an error occurred.

看了文档,不仅不会产生「粘包」的错觉,甚至处理方法都唾手可得。没发完?继续发呀。没收完?继续收呀。怎么知道没收完?约定个特殊内容代表结束,或者约定先发个长度不就完了嘛。怎么?多收了?你想想是怎么知道多少才是多的?

最新文章