黏包现象
1.何为黏包现象
黏包现象就是客户端连续多次执行send,服务端连续执行相同次数的recv想对应接收客户端发送过来的数据,但是服务端却一次性接受了客户端发来的数据,这就是黏包现象
2.黏包现象产生的原因
1.每次发送的数据大小未知
2.TCP流式协议,就是TCP会针对数据量较小且发送间隔较短的多条数据进行合并一次性打包发送
3.解决黏包问题的方法
要知道每次发送的数据大小,并告诉服务端
struct模块
可以将数据打包成固定的长度,基于struct模块我们可以解决黏包问题
解决黏包问题流程
客户端
1.先将真实数据转成bytes类型并计算长度
2.使用struct模块制作一个固定长度的报头
3.将制作的固定长度报头发送给服务器
4.最后再发送真实数据
服务端
1.先接受固定长度的报头
2.使用struct模块反向解析出真实数据长度
3.接收真实数据
代码
import struct
info = b'hello big baby'
print(len(info)) # 数据真实的长度(bytes) 14
res = struct.pack('i', len(info)) # 将数据打包成固定的长度 i是固定的打包模式
print(len(res)) # 打包之后长度为(bytes) 4 报头
real_len = struct.unpack('i', res)
print(real_len) # (14,) 根据固定长度的报头 解析出真实数据的长度
desc = b'hello my baby I will take you to play big ball'
print(len(desc)) # 数据真实的长度(bytes) 46
res1 = struct.pack('i', len(desc))
print(len(res1)) # 打包之后长度为(bytes) 4 报头
real_len1 = struct.unpack('i', res1) # 反向解析真实长度
print(real_len1) # (46,) 根据固定长度的报头 解析出真实数据的长度
这样做确实可以解决黏包问题,但是有缺陷,因为struct模块无法打包数据量较大的数据,更换模式也不行、
struct.pack('i',1111111111111)
struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围
这个时候我们可以使用字典作为报头打包(这也是黏包问题的终极解决方案)
data_dict = {
'file_name': 'xxx.mp4',
'file_size': 123132131232342342423423423423432423432,
'file_info': '内容很精彩 千万不要错过',
'file_desc': '一代神作 私人珍藏'
}
import json
data_json = json.dumps(data_dict)
print(len(data_json.encode('utf8'))) # 真实字典的长度 228
res = struct.pack('i', len(data_json.encode('utf8')))
print(len(res))
这个时候的流程是
客户端
1.制作真实数据的信息字典(数据长度、数据简介、数据名称)
2.利用struct模块制作字典的报头
3.发送固定长度的报头(解析出来是字典的长度)
4.发送字典数据
5.发送真实数据
服务端
1.接收固定长度的字典报头
2.解析出字典的长度并接收
3.通过字典获取到真实数据的各项信息
4.接收真实数据长度
黏包代码实战
服务端
import socket
import struct
import json
server = socket.socket()
server.bind(('127.0.0.1', 8081))
server.listen(5)
sock, addr = server.accept()
# 1.接收固定长度的字典报头
data_dict_head = sock.recv(4)
# 2.根据报头解析出字典数据的长度
data_dict_len = struct.unpack('i', data_dict_head)[0]
# 3.接收字典数据
data_dict_bytes = sock.recv(data_dict_len)
data_dict = json.loads(data_dict_bytes) # 自动解码再反序列化
# 4.获取真实数据的各项信息
# total_size = data_dict.get('file_size')
# with open(data_dict.get('file_name'), 'wb') as f:
# f.write(sock.recv(total_size))
'''接收真实数据的时候 如果数据量非常大 recv括号内直接填写该数据量 不太合适 我们可以每次接收一点点 反正知道总长度'''
total_size = data_dict.get('file_size')
recv_size = 0
with open(data_dict.get('file_name'), 'wb') as f:
while recv_size < total_size:
data = sock.recv(1024)
f.write(data)
recv_size += len(data)
print(recv_size)
客户端
import socket
import os
import struct
import json
client = socket.socket()
client.connect(('127.0.0.1', 8081))
'''任何文件都是下列思路 图片 视频 文本 ...'''
# 1.获取真实数据大小
file_size = os.path.getsize(r'/Users/jiboyuan/PycharmProjects/day36/xx老师合集.txt')
# 2.制作真实数据的字典数据
data_dict = {
'file_name': '有你好看.txt',
'file_size': file_size,
'file_desc': '内容很长 准备好吃喝 我觉得营养快线挺好喝',
'file_info': '这是我的私人珍藏'
}
# 3.制作字典报头
data_dict_bytes = json.dumps(data_dict).encode('utf8')
data_dict_len = struct.pack('i', len(data_dict_bytes))
# 4.发送字典报头
client.send(data_dict_len) # 报头本身也是bytes类型 我们在看的时候用len长度是4
# 5.发送字典
client.send(data_dict_bytes)
# 6.最后发送真实数据
with open(r'/Users/jiboyuan/PycharmProjects/day36/xx老师合集.txt', 'rb') as f:
for line in f: # 一行行发送 和直接一起发效果一样 因为TCP流式协议的特性
client.send(line)
import time
time.sleep(10)
标签:struct,黏包,len,dict,报头,data,size
From: https://www.cnblogs.com/zyg111/p/16900510.html