python3 tcp的粘包现象和解决办法解析

494 阅读4分钟

最近在socket网络编程中遇到粘包问题。特此记录:

1.Q:什么是TCP粘包问题? TCP粘包就是指发送方发送的若干包数据到达接收方时粘成了一包,从接收缓冲区来看,后一包数据的头紧接着前一包数据的尾,出现粘包的原因是多方面的,可能是来自发送方,也可能是来自接收方。

2.Q:造成TCP粘包的原因

(1)发送方原因

  • TCP默认使用Nagle算法(主要作用:减少网络中报文段的数量),而Nagle算法主要做两件事:1. 只有上一个分组得到确认,才会发送下一个分组收集多个小分组, 2. 在一个确认到来时一起发送Nagle算法造成了发送方可能会出现粘包问题

(2)接收方原因

  • TCP接收到数据包时,并不会马上交到应用层进行处理,或者说应用层并不会立即处理。实际上,TCP将接收到的数据包保存在接收缓存里,然后应用程序主动从缓存读取收到的分组。这样一来,如果TCP接收数据包到缓存的速度大于应用程序从缓存中读取数据包的速度,多个包就会被缓存,应用程序就有可能读取到多个首尾相接粘到一起的包。

3.Q:什么时候需要处理粘包现象? 如果发送方发送的多组数据本来就是同一块数据的不同部分,比如说一个文件被分成多个部分发送,这时当然不需要处理粘包现象 如果多个分组毫不相干,甚至是并列关系,那么这个时候就一定要处理粘包现象了

4.Q:如何处理粘包现象?

(1)发送方 对于发送方造成的粘包问题,可以通过关闭Nagle算法来解决,使用TCP_NODELAY选项来关闭算法。

(2)接收方 接收方没有办法来处理粘包现象,只能将问题交给应用层来处理。

(2)应用层 应用层的解决办法简单可行,不仅能解决接收方的粘包问题,还可以解决发送方的粘包问题。

  • 解决办法:循环处理,应用程序从接收缓存中读取分组时,读完一条数据,就应该循环读取下一条数据,直到所有数据都被处理完成,但是如何判断每条数据的长度呢?
  • 格式化数据:每条数据有固定的格式(开始符,结束符),这种方法简单易行,但是选择开始符和结束符时一定要确保每条数据的内部不包含开始符和结束符。
  • 发送长度:发送每条数据时,将数据的长度一并发送,例如规定数据的前4位是数据的长度,应用层在处理时可以根据长度来判断每个分组的开始和结束位置。

5.Q:UDP会不会产生粘包问题呢?

  • TCP为了保证可靠传输并减少额外的开销(每次发包都要验证),采用了基于流的传输,基于流的传输不认为消息是一条一条的,是无保护消息边界的(保护消息边界:指传输协议把数据当做一条独立的消息在网上传输,接收端一次只能接受一条独立的消息)。

  • UDP则是面向消息传输的,是有保护消息边界的,接收方一次只接受一条独立的信息,所以不存在粘包问题。

  • 举个例子:有三个数据包,大小分别为2k、4k、6k,如果采用UDP发送的话,不管接受方的接收缓存有多大,我们必须要进行至少三次以上的发送才能把数据包发送完,但是使用TCP协议发送的话,我们只需要接受方的接收缓存有12k的大小,就可以一次把这3个数据包全部发送完毕。

下面主要介绍了python3 tcp的粘包现象和解决办法解析,需要的朋友可以参考下

服务器端

import socket
sk = socket.socket()
sk.bind(("127.0.0.1", 6666))
sk.listen()
conn, address = sk.accept()
def my_send(msg):
  bs = msg.encode("utf-8")
  len_str = format(len(bs), "04d") # 定长4位 #
  conn.send(len_str.encode("utf-8"))
  conn.send(bs)
my_send(input(">>>:").strip())
my_send(input(">>>:").strip())

其中涉及formate 特殊用法 juejin.cn/post/684490… 客户端

import socket
import time
 
sk = socket.socket()
sk.connect(("127.0.0.1", 6666))
 
time.sleep(10) # 制造粘包情况
msg = sk.recv(1024).decode("utf-8") # 粘包现象
print(msg)

在10秒内在服务端连续两次回车输入aaaa和bbb 会在客户端发现一起显示,本应该分开显示,但是却粘连在一起显示 执行结果: 0004aaaa0003bbb 有粘包的现象。

解决粘包,客户端代码调整

import socket
import time
sk = socket.socket()
sk.connect(("127.0.0.1", 6666))
time.sleep(10)
def my_recv():
  len_str = int(sk.recv(4).decode("utf-8"))
  msg = sk.recv(len_str)
  print(f"来自服务端的消息:{msg.decode('utf-8')}")
my_recv()
my_recv()

执行结果: 来自服务端的消息:aaaa 来自服务端的消息:bbb 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

参考:golang处理粘包问题

blog.csdn.net/yue7603835/…

www.cnblogs.com/seven-007/p…

www.cnblogs.com/sun-1038783…