【一】什么是粘包问题
- 想象你正在通过一条很窄的管道(网络连接)发送一串珠子(数据包)。在管道的另一端,有人(接收方)正在接收这些珠子。为了让对方知道每串珠子的开始和结束,你决定在每串珠子之间放一小块纸条(数据包的边界标记)。
- 但是,如果这些珠子和纸条在管道中相互挤压,可能会导致纸条丢失或珠子串连在一起,使得接收方无法区分哪些珠子是一组。这就好比在网络传输过程中,原本独立的数据包因为紧密相连,而在接收端被误认为是一个连续的数据流,这就是所谓的“粘包”。
- 在现实的网络通信中,由于TCP协议是基于流的,它只保证数据的顺序和完整性,而不保证数据包之间的界限。因此,如果发送方连续快速发送多个数据包,接收方可能会一次性接收到这些数据包的合并结果,而无法区分它们原来的界限。这就需要通过某些机制(如在每个数据包前添加长度信息)来明确标记数据包的界限,以避免粘包问题。
【二】TCP协议解决粘包问题思路
-
将数据拆分出来一个头部,头部内容写了数据的大小,以及其他信息
-
这里以一个传输视频的功能作为例子
-
服务端
# 创建对象
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定IP,端口
server.bind(('127.0.0.1', 8080))
# 创建半连接池
server.listen(5)
# 获取连接对象
obj, addr = server.accept()
# 接收文件名
file_name = obj.recv(1024).decode('utf8')
# 由一个字典装头部信息
header = {
'file_name': file_name, # 文件名称
'file_size': os.path.getsize(file_name) # 文件大小
}
# 用json把header变成json字符串格式
header_json = json.dumps(header)
# 转换成二进制
header_bytes = header_json.encode('utf-8')
# 计算头部长度
header_h = bytes(str(len(header_bytes)).encode('utf8')).zfill(2)
# 发送头部长度给客户端
obj.send(header_h)
# 发送头部数据给客户端
obj.send(header_bytes)
with open(file_name, 'rb') as fp:
data = fp.read()
obj.send(data)
obj.close()
server.close()
- 客户端
import socket
import json
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
file_name = input('请输入文件名:>>>>').strip()
client.send(file_name.encode('utf8'))
new_file_name = f'new_{file_name}'
header_size = int(client.recv(2).decode('utf8'))
header_json = client.recv(header_size).decode('utf8')
header = json.loads(header_json)
file_size = header.get('file_size')
res_size = 0
with open(new_file_name, 'wb') as fp:
while res_size < file_size:
data = client.recv(1024)
fp.write(data)
res_size += len(data)
client.close()
- 服务端将文件的信息如文件名,文件大小存放在字典里作为头部信息
- 并且把头部的长度也发送给客户端
- 客户端收到头部的长度后,再继续以头部的长度精准接收到头部信息,也就是服务端发送的第二个内容
- 然后客户端再起一个变量来记录收到数据的大小,直到收到数据的大小等于头部信息里面的文件大小,才会断开连接
- 这样就解决了粘包问题