首页 > 其他分享 >黏包现象及struct模块

黏包现象及struct模块

时间:2022-11-18 21:55:29浏览次数:43  
标签:struct 黏包 len dict 模块 报头 长度 data

目录

黏包现象

黏包现象

1.服务端连续执行三次recv
2.客户端连续执行三次send
执行上述两端的操作,服务端一次性接收到客户端三次的消息,该现象称为‘粘包现象’,如下图:

黏包现象如下:

1.执行上述两端的操作,服务端一次性接收到客户端三次的消息:发送端需要等缓冲区满才发送出去,造成黏包(发送数据时间间隔很短,数据很小,会合并一起,产出黏包)

image

2.同时将接收的字节数改为5个字节,服务端接收客户端的字节不够,就会朝着第二次客户端发送的字节拿一个字节,第二次打印就会从上次断开的地方接着打印(接收方不及时接收缓冲区的包,造成多个包接收)

image

3.要是想解除上述现象,服务端接收的字节数就必须根据客户端发送的数据大小

image

黏包现象的产生的原因:

1.不知道每次的数据的大小
2.TCP也称为流式协议,数据像水流一样绵绵不绝没有间断,一条直线的传,不会间断。(TCP会针对数据量较小且发送间隔较短的多条数据一次性合并打包发送)

总结:黏包现象只发生在TCP协议中:
1.从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。
2.实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的

存在的问题:
	程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这种方式会放大网络延迟带来的性能损耗

如何避免黏包现象的核心思路/关键点

如何明确服务端每次接收的数据字节的数量?如何将长度变化的数据全部制作成固定长度的数据>>>>引入了struct模块

struct模块

我们可以借助一个模块,这个模块可以把要发送的数据长度转换成固定长度的字节。这样客户端每次接收消息之前只要先接受这个固定长度字节的内容看一看接下来要接收的信息大小,那么最终接受的数据只要达到这个值就停止,就能刚好不多不少的接收完整的数据了。

​ struct模块可以将非固定长度的数字转为固定长度(len=4)的数字,语法结构为:struct.pack('i', len(数据变量名)),其中'i'是固定的参数。打包的过程也称为'报头'。

​ 案例1

import struct    # 引入模块

info = b'hello my friend'
1.数据真实的长度(bytes)
# print(len(info))  #  15

2.# 将数据打包成固定的长度。i 是固定的打包模式
res = struct.pack('i',len(info))   

3.# 打包之后的长度为(bytes)  也称为报头
print(len(res))   # 4

4.根据固定长度的报头解析出真实数据的长度
real_len = struct.unpack('i',res)

5.打印解析真实数据的长度
print(real_len)   # (15,)    

​ 案例2

import struct    # 引入模块

desc = b'hello my sister want to buy some fruit'
# 1. 数据真实的长度(bytes)
print(len(desc))   # 38

# 2. # 将数据打包成固定的长度。
res1 = struct.pack('i',len(desc))

# 3.打印打包之后的长度为(bytes)
print(len(res1))  # 4   报头

# 4.解析出真实数据的长度
real_desc  = struct.unpack('i',res1)
print(real_desc)  # (38,)

解决黏包现象

解决黏包问题的初次版本

客户端
	1.将真实数据转为bytes类型并计算长度
	desc = b'hello my sister want to buy some fruit'
	# 1. 数据真实的长度(bytes)
print(len(desc))   # 38
	2.利用struct模块将真实长度制作一个固定长度的报头
	res1 = struct.pack('i',len(desc))
	3.将固定长度的报头先发送给服务端,服务端只需要在recv括号内填写固定长度的报头数据即可
	4.然后发送真实数据
	
服务端
	1,服务端先接收固定长度的报头
	2.利用struct模块反向解析出真实数据长度
	3.recv接收真实数据长度即可
	

字节有限制:
问题1:struct模块无法打包数据量较大的数据,就算换更大的模式也不行。
	允许的字节范围为:-2147483647<真实长度>2147483647(字节)
import struct
res = struct.pack('i',12313213123)  
print(res)  # struct.error: argument out of range
	
问题2:报头是否传递更多的信息,比如电影的大小、电影的名字、电影评价、电影简介

解除黏包问题的终极版本

解决上述两个问题:使用字典作为报头,效果更好
data_dict = {
    'file_name':'xxx老师教学',
    'file_size':1245323445325433455434654365465,
    'file_info':'教学课程还可以',
    'file_desc':'可以来听一听'
}
import json
data_json = json.dumps(data_dict)
print(len(data_json.encode('utf8')))   # 真实字典的长度   202
res = struct.pack('i',len(data_json.encode('utf8')))
print(len(res))   # 4

客户端
	1.制作真实数据的信息字典(数据长度、数据简介、数据名称)
	2.利用struct模块制作字典的报头
	3.发送固定长度的报头(解析出来的是字典的长度)
	4.发送字典数据
	5.发送真实数据
服务端
	1.接收固定长度的字典报头
	2.解析出字典的长度并接收
	3.通过字典获取真实数据的各项信息
	4.接收真实数据长度

上述就基本解决了黏包的问题,但是上述的解决了方案,却忽视了struct模块能压缩数字是有限的,具体如下图:

image

struct模块是用i模式来压缩的,不过无论用什么模式,这个数字都有上限。所以解决方案就是下载之前就要看被下载的文件的详细信息

黏包代码实操

服务端

import socket
import struct
import json

server = socket.socket()
server.bind(('127.0.0.1',8080))
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  # 初始接收了0字节的数据
with open(data_dict.get('file_name'),'wb') as f:
    while recv_size<total_size:  # 当接收量不在小于文件总大小,则停止接收
        data = sock.recv(1024)    # 每次接收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',8080))

"""任何文件都是下列思路 图片  视频  文本。。。"""
# 1.获取真实数据大小
file_size = os.path.getsize(r'D:\PycharmProjects\day36\A老师视频合集.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'D:\PycharmProjects\day36\A老师视频合集.txt','rb') as f:
    for line in f:  # 一行行发送 ,和直接一起发送效果是一样的,因为TCP流式协议的特性
        client.send(line)  # 最终发送文件本身

import time
time.sleep(10)

UDP协议(了解)

1.UDP服务端和客户端‘各自玩各自’
2.UPD不会出现多个消息发送合并
服务端代码:
import socket

sever = socket.socket(type=socket.SOCK_DGRAM)
sever.bind(('127.0.0.1', 8081))

while True:
    data, addr = sever.recvfrom(1024)
    print('客户端地址>>>:', addr)
    print('上述地址罚送的消息>>>:', data.decode('utf8'))
    msg = input('>>>').strip()
    sever.sendto(msg.encode('utf8'), addr)
    
客户端1:
import socket

client = socket.socket(type=socket.SOCK_DGRAM)
sever_addr = ('127.0.0.1', 8081)

while True:
    msg = input('>>>').strip()
    client.sendto(msg.encode('utf8'), sever_addr)
    data, addr = client.recvfrom(1024)
    print(data.decode('utf8'), addr)
    
客户端2:
import socket

client = socket.socket(type=socket.SOCK_DGRAM)
sever_addr = ('127.0.0.1', 8081)

while True:
    msg = input('>>>').strip()
    client.sendto(msg.encode('utf8'), sever_addr)
    data, addr = client.recvfrom(1024)
    print(data.decode('utf8'), addr)

标签:struct,黏包,len,dict,模块,报头,长度,data
From: https://www.cnblogs.com/zhanglanhua/p/16904991.html

相关文章

  • 同步、异步与阻塞、非阻塞的概念、创建进程的多种方式及multiprocessing模块、进程间
    目录一、同步与异步同步异步二、阻塞与非阻塞阻塞非阻塞三、综合使用1.同步阻塞:2.同步非阻塞:3.异步阻塞:4.异步非阻塞:四、创建进程的多种方式进程的创建multiprocessing模块......
  • IoT物联网无线通信模块该如何选择?
    目前大多数物联网(IoT)的节点都是使用ZigBee技术来进行组网的,然后通过gateway(网关)来连接网络。但是ZigBee模块的优势并不明显,也有很多公司正在开发新的无线模块来代替它,例如lo......
  • 华普物联HP-ERS-T200,振动传感器监测方案,工业级双串口通信服务器,RS485/232转以太网双
    随着科技的进步,现在工业化设备正逐步走向复杂化、高速化、自动化。为了掌握设备运行状态、避免发生事故,对生产中的关键机组实行在线监测和故障诊断,也越来越引起人们的重视......
  • 光模块有什么用?什么是SFP光模块?
    光模块(opticalmodule)由光电子器件、功能电路和光接口等组成,光电子器件包括发射和接收两部分。光模块主要应用在光通信、数据中心等地方的。那么,光模块到底是什么呢?光模块......
  • Ansible ad-hoc模式及常用模块
    Ansibleadhoc模式基本语法:ansible{主机名/主机地址/主机组}[-m模块名][-a模块参数]-m模块名指定使用的模块名称-a模块参数列表指定模块执行操作时的参数,参......
  • Python - typing 模块
    typing模块的作用类型检查,防止运行时出现参数和返回值类型不符合。作为开发文档附加说明,方便使用者调用时传入和返回参数类型。该模块加入后并不会影响程序的运行,不会......
  • python模块 - copy模块
    copy模块用于对象的拷贝操作。该模块只提供了两个主要的方法:copy.copy与copy.deepcopy,分别表示浅复制与深复制。b=a.copy():浅拷贝,a和b是一个独立的对象,但他......
  • 振弦采集模块主动上传测量数据( UART)
    振弦采集模块主动上传测量数据(UART) 默认情况下VMXXX模块总是以从机身份与主机完成数据交互,在这种主从结构中,VMXXX从不主动上传数据,可通过修改自动上传寄存器(AT......
  • windows 进程 Shell Infrastructure Host 占用CPU,以及大量使用电源。
    表现:风扇疯狂的转,声音超大,电脑发热严重。解决:进入任务管理器,可以看到进程ShellInfrastructureHost高耗电,占用CPU很多。解决方案见:shellinfrastructureh......
  • 【luffy】课程模块
    目录1.补充celery2.课程板块相关表分析及创建2.1分析2.2创建2.3ForeinKey的on_delete的选择和相关参数理解2.4数据录入3.前端页面4.课程页面接口4.1需求4.2课程......