黏包现象及解决办法

黏包现象

1.服务端连续执行三次recv   # 服务端收3次
2.客户端连续执行三次send   # 客户端发3次
问题:服务端一次性接收到了客户端3次的消息,该现象称为'黏包现象'

黏包现象产生的原因:
	1.不知道每次的数据到底多大
  	 2.TCP也成为流式协议:数据像水流一样绵绵不绝没有间隔(TCP会针对数据量较小且发送间隔较短的多条数据一次性合并打包发送)
        
避免黏包现象的核心思路/关键点
	如何明确即将接收的数据具体有多少

image

struct模块

import struct

info = b'hello bin baby'
print(len(info))  # 打印数据长度14
res = struct.pack('i', len(info))  # 调用struct包括在I模式下转换info的长度
print(len(res))  # 长度为4(bytes),报头

real_len = struct.unpack('i', res)  # 根据固定长度的报头解析出真实数据的长度
print(real_len)  # (14,)是元组的形式,所以要使用14,需要元组索引0



desc = b'hello my baby I will take you to play big ball'
print(len(desc))  # 真实长度46
res = struct.pack('i', len(desc))
print(len(res))   # 打宝之后长度为4

real_len = struct.unpack('i', res)
print(real_len)   # (46,)

"""
解决黏包问题初次版本
客户端
	1.将真实数据转换成bytes类型并计算长度
	2.利用struct模块将真实长度制作一个固定长度的报头
	3.将固定长度的报头先发送给服务端,服务端只需要在recv括号内填写固定长度的报头数字即可
	4.然后再发送真实数据
	
服务端
	1.服务端先接收固定长度的报头
	2.利用struct模块反向解析出真实数据长度
	3.recv接收真实数据长度即可
	
"""


"""问题1:structs模块无法打包数据量较大的数据,就算换更大的模式也不行"""
import struct

res = struct.pack('i',212341532134)
print(res)   # 会直接报错
"""问题2:报头是否能传递更多的信息,比如电影大小,电影名称,电影评价,电影简介"""
"""终极解决方案:字典作为报头打包,效果更好,数字更小"""
data_dict = {
    'file_name': 'xxx老师电影.avi',
    'file_size': 123232313231233,
    'file_info': '内容精彩,不容错过',
    'file_desc': '私人珍藏'
}
import json

date_json = json.dumps(data_dict).encode('utf8')  # 将字典转json字符串再转二进制
print(len(date_json))   # 真实长度190
res = struct.pack('i',len(date_json))   # 用struct模块打包字典数据长度
print(len(res))  # 打包后的长度4
"""
黏包问题终极解决方案:
客户端:
	1.制作真实数据的信息字典(数据长度、数据简介、数据名称)
	2.利用struct模块制作字典的报头
	3.发送固定长度的报头(解析出来就是字典的长度)
	4.发送字典数据
	5.发送真实数据
服务端:
	1.接收固定长度的字典报头
	2.解析出字典的长度并接收
	3.通过字典获取到真实数据的各项信息
	4.接收真实数据长度
"""

黏包代码实战

客户端:

import socket
import struct
import json
import os

client = socket.socket()  # 产生客户端对象
client.connect(('127.0.0.1', 8081))  # 连接服务端
"""任何文件都是下列的思路图片,视频,文本"""
file_size = os.path.getsize(r'C:\pythonProject\面向对象\a.txt')  # 用os模块判断文件的数据大小
print(file_size)  # 1239
# 制作真实数据的字典数据
data_dict = {'file_name': 'a.txt',
             'file_size': file_size,
             'file_desc': '内容很长,需要慢慢看',
             'file_info': '这是私人珍藏'}
# 制作字典报头
data_dict_bytes = json.dumps(data_dict).encode('utf8')
data_dict_len = struct.pack('i', len(data_dict_bytes))
# 发送字典报头
client.send(data_dict_len)  # # 报头本身也是bytes类型 我们在看的时候用len长度是4
# 发送字典
client.send(data_dict_bytes)  # 被json格式转字符串转了二进制以后的字典
# 发送真实数据
with open(r'C:\pythonProject\面向对象\a.txt', 'rb') as f:   # 打开文件发送数据
    for i in f:  # 一行行发送 和直接一起发效果一样 因为TCP流式协议的特性
        client.send(i)   # 发送

服务端:

import socket
import json
import struct

server = socket.socket()  # 创建一个服务端对象
server.bind(('127.0.0.1', 8081))  # 创建服务端IP地址和端口
server.listen(5)  # 打开半连接池
sock, addr = server.accept()  # 等待客户端连接
# 接收固定长度的字典报头
date = sock.recv(4)
# 根据报头解析出来字典数据的长度
date_len = struct.unpack('i', date)[0]
# 接收字典数据
date_dict_bytes = sock.recv(date_len)
# 反序列化解码
date_dict = json.loads(date_dict_bytes)
# 获取真实数据的各项信息
tota_size = date_dict.get('file_size')
with open(date_dict.get('file_name'), 'wb') as f:
    f.write(sock.recv(tota_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)

image

UDP协议(了解)

1.UDP服务端和客户端'各自各玩各自的'
2.UDP不会出现多个消息发送合并
posted @ 2022-11-17 16:04  雪语  阅读(34)  评论(0编辑  收藏  举报