您当前的位置:首页 > 计算机 > 编程开发 > Python

完美解决Python套接字编程时TCP断包与粘包问题

时间:01-06来源:作者:点击数:

首先,来看一个代码,使用TCP协议,发送端发送一句话,接收端接收并显示,运行完全正常。

图片

接下来,把客户端代码稍微修改一下,连续发送多个数据,

图片

按照正常的想法,在服务端输出的信息应该是分为多行的,这样才和客户端对应。然后运行结果并不是想象的那样子。从运行结果来看,应该是服务端把收到的数据放在缓冲区里了,有了足够多的数据之后才处理。

recv()方法的参数用来确定一次从缓冲区中最多读取多少字节的数据,为了清楚其含义,稍微修改代码,

图片

学过计算机网络的朋友一般会听说过Nagle算法。在使用TCP协议进行传输时,会在有效数据前面增加大量头部信息来保证可靠传输,如果发送的有效数据非常短,增加头部带来的额外开销就非常大。为了优化和减少带宽占用,避免大量小包堵塞网络,发送端会在发送大量小包时积累一定数量的数据之后组成一个大包晚些时间再发送(粘包),在发送大包时会根据情况切分成多个包发送(断包)。同理,接收端在接收大包时有可能会进行截断以免缓冲区放不下(断包),接收连续多个小包时会在缓冲区暂存一段时间合并成大包再处理(粘包),也就是所谓Nagle算法。

Nagle算法的优化在大部分情况下都是非常好的,但也会给接收端带来一定的麻烦,必须要正确识别和读取一个完整的包之后再处理,以免后面的功能代码无法正常工作。这需要额外写更多代码来正确读取一个完整的包,例如发送端先告知接收端要发送的数据长度,或者双方约定好数据的起始标记和结束标记。

如果到网上(甚至一些书上)搜索资料,会说禁用Nagle算法就可以了,也就是设置套接字属性启用TCP_NODELAY,非常简单。既然如此,那就赶紧用起来吧。

在Python中,标准库socket封装了套接字编程需要的功能,创建套接字之后可以使用setsockopt来设置当前套接字的各种属性,其中就包括禁用断包和粘包的延迟从而禁用Nagle算法。

图片

结果显示,这个选项根本没有起作用。那会不会是需要在通信双方都启用TCP_NODELAY呢?于是把客户端也设置一下,重新运行程序,发现还是没有用。

图片

继续查资料,会有人说,要真正禁用Nagle算法只把TCP_NODELAY设置为True是不够的,还需要把接收端的接收缓冲区大小设置为0才行。原来是这样啊,那就赶紧修改代码吧,事实证明还是没有用的。

图片

也有资料显示,通信双方需要协商一下,为避免接收端粘包时误把下一条信息的一部分合并到当前信息尾部,可以协商一个起始标记和结束标记,接收端根据接收的信息来查找这些标记并进行正确的切分。这听起来是个好思路,但真正用起来的时候难度还是很大的,感兴趣的朋友可以尝试一下。

再一个思路也是在传输大量数据时经常使用的,就是发送端首先告诉对方接下来要发送的数据长度,然后再发送实际数据。接收端首先接收一个整数来表示接下来要接收的数据总长度,然后使用循环来接收数据,直到恰好接收完刚才约定的数据长度为止。为了避免发生粘包,接收端需要动态调整缓冲区大小来控制每次接收的数据,防止接收多了。

现在的问题就是如何确保把数据长度有效传递给对方了,可以使用Python标准库struct把整数序列化为字节串发送给对方,而这个字节串的长度固定为4,这样的话,接收端使用recv(4)接收到这个字节串再反序列化为整数就可以了。

图片

上面这个思路是完美的,也是优先推荐使用的,但是需要在编写程序之前就确定好代码思路和框架。

如果在编写代码时没有遵循这个思路,都是直接进行发送和接收导致了粘包的发生,又不想对代码进行大幅度的修改,可以考虑在发送完一段完整意义的数据之后加一个很小的延时,这样接收端不会等待更多数据后一起处理。虽然这样可以实现功能,但这个小延时的积累是非常大的,非常不适合服务端代码的设计,要慎重使用。

图片

在本文最后,给出一个多线程版本的Socket程序,供参考。

图片
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门