黏包现象
我们先来看一个案例:
server端:
server = socket.socket() server.bind(('127.0.0.1', 8081)) server.listen(5) sock, addr = server.accept() data1 = sock.recv(1024) print(data1) data2 = sock.recv(1024) print(data2) data3 = sock.recv(1024) print(data3) sock.close() server.close()
client端:
import socket client = socket.socket() client.connect(('127.0.0.1', 8081)) client.send(b'hello kevin') client.send(b'jason say hei') client.send(b'jerry say goodbye')
服务端收到的结果如下:
上述现象称之为黏包现象:
1.服务端连续执行三次recv
2.客户端连续执行三次send
结果:服务端一次性接收到了客户端三次的消息,该现象称为"黏包现象"。
黏包现象产生的原因:
1.接收方不知道消息之间的界限,不知道一次性提取多少字节的数据
2.TCP也称为流式协议:数据像水流一样绵绵不绝没有间隔(TCP会针对数据量较小且发送间隔较短的多条数据一次性合并打包发送)
黏包的解决方案
避免黏包现象的核心思路\关键点:
如何明确即将接收的数据具体有多大
如何将长度变化的数据全部制作成固定长度的数据?
接下来我们介绍一个模块:
struct模块
该模块可以把一个类型,如数字,转成固定长度的bytes
struct.pack('i',1111111111111) struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围
struct模块作用:将数据打包成固定的长度
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,) 根据固定长度的报头 解析出真实数据的长度
解决黏包问题初级版本
客户端
1. 将真实数据转成bytes类型并计算长度
2. 利用struct模块将真实长度制作一个固定长度的报头
3. 将固定长度的报头先发送给服务端,服务端只需要在recv括号内填写固定长度的报头数字即可
4. 然后再发送真实数据
服务端
1. 服务端先接收固定长度的报头
2. 利用struct模块反向解析出真实数据长度
3. recv接收真实数据长度即可
但是上述方案也存在一些问题:
问题1:struct模块无法打包数据量较大的数据,就算换更大的模式也不行
res = struct.pack('i', 12313213123) print(res) # struct.error: argument out of range
问题2:报头能否传递更多的信息,比如要传递一个电影,电影大小、电影名称、电影评价、电影简介
终极解决方案:字典作为报头打包:效果更好,数字更小
客户端
1. 制作真实数据的信息字典(数据长度、数据简介、数据名称)
2. 利用struct模块制作字典的报头
3. 发送固定长度的报头(解析出来是字典的长度)
4. 发送字典数据
5. 发送真实数据
服务端
1.接收固定长度的字典报头
2.解析出字典的长度并接收
3.通过字典获取到真实数据的各项信息
4.接收真实数据长度
黏包问题代码实操
client端:
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)
server端:
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)
标签:struct,黏包,len,现象,dict,长度,报头,data From: https://www.cnblogs.com/chen-ao666/p/16899767.html