如何让接收端知道发送端将要发送的字节流长度?

描述

粘包现象展示

一上来就枯燥的文字,难免容易犯困,所以我先演示一下粘包现象:

字符串字符串

我们的预期是,服务端打印出hello,I am Toranto这两个字符串,两个字符串经编码之后的字节总长度为17个字节,但是我们看结果:

字符串

ps:b''是表示字节的意思,和我们之前用的f''(format)格式化用法是一样的,单、双、三引号一样。可以了解一下:

字符串

好了回归粘包现象,我们看到上图,我们采用两次接收数据的方式,两次都接收20个字节,但是发送端发送的两次总字节只有17个字节长度,所以,tcp基于流式的协议,会先发送第一段字节到一个缓冲区,如果时间间隔很短,则tcp会等第二段字也发送到这个缓冲区再统一发送到接收端这边,两次内容的粘合没有达到我们预期的效果,这就是粘包现象。

粘包

粘包是一种现象,而且只有tcp会出现粘包现象,udp不会,这是基于tcp的

流式传输

导致的,tcp是传输数据流。

tcp会将数据量较小,且发送时间间隔较短的数据一起打包发送,那么这里所讲的时间较短是相比网络延迟来说的。比如我们两次发送间隔为0.00001s,那么网络延迟为0.001s,这个时候两次的数据就会打包发送,这是一种优化机制,但也就是这个优化机制导致粘包现象。

首先我们需要了解一下socket收发数据的原理:

字符串


发送端可以是1K1K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据,也就是说,应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。

UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。怎样定义消息呢?可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。

例如基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看来,根本不知道该文件的字节流从何处开始,在何处结束。

所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

粘包解决

很遗憾,socket并没有给我们提供内置解决方法。

那我们需要如何解决?

问题的根源在于,接收端不知道发送端将要发送的字节流的长度。

我们可以基于此点想解决办法,如何让接收端知道发送端将要发送的字节流长度(提前获知)?

我们可以在发送端写一个提前告知的代码,并且在接收端循环判定,是否达到预定字节流长度,达到了再一并打印出来:

发送端和接收端(关键)要结合起来看:

字符串字符串

接下来我把源码放上来,我加了一些判断条件:

字符串字符串字符串

演示一下结果:

字符串字符串



审核编辑:刘清

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分