Python--粘包问题及基于 socket 编程解决粘包问题

Python--粘包问题及基于 socket 编程解决粘包问题,第1张

文章目录
  • 一、粘包问题介绍与解决思路
  • 二、struct 模块介绍
    • 2.1 功能一:`pack()`方法
    • 2.2 功能二:`unpack()`方法
    • 2.3 基于 struct 模块--解决粘包问题的思路
  • 三、基于 socket 编程-解决粘包问题示例

一、粘包问题介绍与解决思路

服务端–连续接受三次消息,并且打印这三次消息

客户端–连续发送三个消息

import socket
from socket import SOL_SOCKET,SO_REUSEADDR


server = socket.socket()

server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)  # 就是它,在bind前加
server.bind(('127.0.0.1',8081))

server.listen(2)
conn, addr = server.accept()

data = conn.recv(1024)
print(data)
data1 = conn.recv(1024)
print(data1)
data2 = conn.recv(1024)
print(data2)

conn.close()
import socket

client = socket.socket()
client.connect(('127.0.0.1', 8081))

client.send(b'data1')
client.send(b'data2')
client.send(b'data3')

client.close()

会发现,我们一次性将数据全部收了过来,并不是一次一次的取来,原因就在于—服务端每次是 1024 个字节接收的

TCP协议的特点–会将数据量比较小并且时间间隔问题比较短的数据整合起来一起发送!~

所以,基于上述原因,我们可以将客户端每次接受的字节数修改成 5 个字节

import socket
from socket import SOL_SOCKET,SO_REUSEADDR


server = socket.socket()

server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)  # 就是它,在bind前加
server.bind(('127.0.0.1',8081))

server.listen(2)
conn, addr = server.accept()

data = conn.recv(5)
print(data)
data1 = conn.recv(5)
print(data1)
data2 = conn.recv(5)
print(data2)

conn.close()

问题产生的原因在于:recv 括号内的我们并不能预料到每次接受的数据的大小是多少,并且 recv 每次接收数据的的大小也有限制

解决思路:如果我们每次都能精准的获取本次要接受的数据的大小,就能够完美的接收

二、struct 模块介绍 2.1 功能一:pack()方法

pack()方法将任意长度的 数字 打包成新的数据,这个新数据的长度是固定

pack()方法 第一个参数是格式,第二个参数是整数(数据的长度)

—返回值是一个新的数据

import struct

data1 = 'hello world'
data2 = 'hello bao bei baby honey darling'
print(len(data1)) # 11
print(len(data2)) # 32
res1 = struct.pack('i', len(data1))
res2 = struct.pack('i', len(data2))
print(len(res1)) # 4
print(len(res2)) # 4
'结果为:'
11
32
4
4
2.2 功能二:unpack()方法

unpack()方法将固定长度的 数字 解包成打包前数据真实的长度

unpack()方法 第一个参数是格式,第二个参数是 pack()方法打包后生成的新数据

返回值是一个元组,元祖中放着打包前数据真实的长度

import struct

data1 = 'hello world'
data2 = 'hello bao bei baby honey darling'
res1 = struct.pack('i', len(data1))
res2 = struct.pack('i', len(data2))

x = struct.unpack('i', res1)
y = struct.unpack('i', res2)
print(x) # (11,)
print(y) # (32,)
2.3 基于 struct 模块–解决粘包问题的思路
  1. 先将真实的数据打包成固定长度的包;
  2. 先把固定长度的包发送过去;
  3. 接收后解包得到真实数据的长度;
  4. 接收真实数据

对于不同的模式打包后的新数据的长度不同,但是不论什么模式都有数据长度的限制

小问题:如果打包的数据量非常的巨大,就会导致无法打包

那么,转换思路,可以不直接打包原始的数据,而是打包一个数据的字典

data_dict = {
  'file_name' = '学习资料.zip',
  'file_describe' = '里面放的都是好东西,500g 的学习资料',
  'file_size' = 666666666666666
}

最终解决方案:

发送方:

  1. 首先构建一个数据字典–包括名称,简介,数据的大小
  2. 将字典打包成固定长度的包
  3. 将字典的包发送过去
  4. 发送 真实的字典数据
  5. 发送 真实的数据

接收方:

  1. 先接受字典的包,解析出字典的长度
  2. 接受 字典数据 , 解析出真实数据的各种信息,并且获得真实数据的长度
  3. 接受真实数据
三、基于 socket 编程-解决粘包问题示例

服务端:

import socket
import os
import struct
import json


server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)

conn, addr = server.accept()

data_dict = {
    'file_name':'xxx刺激.txt',
    'file_desc':'封控一个多月了',
    'file_size':os.path.getsize(r'04 struct模块.py')
}
# 1.先打包字典
dict_json_str = json.dumps(data_dict)
dict_bytes = dict_json_str.encode('utf8')
dict_package_header = struct.pack('i', len(dict_bytes))
# 2.发送报头
conn.send(dict_package_header)
# 3.发送字典
conn.send(dict_bytes)
# 4.发送真实数据
with open(r'04 struct模块.py', 'rb') as f:
    for line in f:
        conn.send(line)

客户端:

import socket
import struct
import json

client = socket.socket()
client.connect(('127.0.0.1', 8080))

# 1.先接收固定长度的字典的报头
dict_header_len = client.recv(4)
# 2.解析出字典的真实长度
dict_real_len = struct.unpack('i', dict_header_len)[0]
# 3.接收字典数据
dict_data_bytes = client.recv(dict_real_len)
dict_data = json.loads(dict_data_bytes)
print(dict_data)
# 4.循环接收文件数据 不要一次性接收
recv_size = 0
with open(dict_data.get('file_name'),'wb') as f:
    while recv_size < dict_data.get('file_size'):
        data = client.recv(1024)
        recv_size += len(data)
        f.write(data)

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/715605.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-25
下一篇 2022-04-25

发表评论

登录后才能评论

评论列表(0条)

保存