python之粘包/粘包的解决方案

python之粘包/粘包的解决方案

什么是粘包

粘包就是在数据传输过程中有多个数据包被粘连在一起被发送或接受

服务端:

import socket
import struct

# 创建Socket
Socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 绑定服务器和端口号
servers_addr = ('127.0.0.1', 8081)
Socket.bind(servers_addr)

# 监听客户端请求 最大连接数为5
Socket.listen(5)
print('服务器启动成功,等待客户端连接...')

# 接受数据
client_socket, client_addr = Socket.accept()
print('与客户端建立连接', client_addr)
client_socket.setblocking(False)
# 数据交换
while True:
    data = client_socket.recv(10880)  # 最大1024字节
    if len(data) < 1:
        print('关闭服务')
        break

    # 接受客户器端传来的数据
    print(data.decode())

    # 向客户端返回数据
    client_socket.sendall(data)
    break
Socket.close()

客户端:

import socket
import subprocess

# 获取cmd指令
cmd_from_client = 'ipconfig'
cmd_msg = subprocess.Popen(cmd_from_client,
                           shell=True,  # 使用shell命令
                           stdout=subprocess.PIPE,  # 管道一:输出结果
                           stderr=subprocess.PIPE  # 管道二:输出错误信息
                           )
msg_one = cmd_msg.stdout.read().decode('gbk')
msg_two = cmd_msg.stderr.read().decode('gbk')
msg = msg_one + msg_two


# 创建Socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 服务器地址和端口
server_address = ('localhost', 8081)

# 连接服务器
client_socket.connect(server_address)
print('已连接到服务器:', server_address)

while True:
    # 发送数据
    # message = input('>>>>')
    client_socket.sendall(msg.encode())

    # 接收响应
    response = client_socket.recv(1024)
    print('服务器响应:', response.decode())
    break
client_socket.close()

案例中使用了subprocess模块输出了ip信息,在服务端打印的数据中可以看到内容是能够正常输出的

image-20240120212503635但是根据客户端的控制台显示数据在返回时被截断了

其实原因很简单:

response = client_socket.recv(1024)

数据在服务端中能一次性的接收,但由于客户端只能接受1024,所以就不会从缓存中一下取完大于1024的那部分数据,其实不管是客户端还是服务端,recv()的缓存区大小都是可控的,但是发送方发送了一个 10KB 的数据包,而接收方使用 recv(1024) 只能一次接收最多 1KB 的数据,这样就需要多次调用 recv() 来接收完整的数据,可能会引发粘包问题

客户端
import socket

# 创建 Socket 对象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 服务器地址和端口
server_address = ('localhost', 8081)

# 连接服务器
client_socket.connect(server_address)
print('已连接到服务器:', server_address)

# 发送数据包
message1 = 'Hello'
message2 = 'World'

# 连续发送两个数据包
client_socket.sendall(message1.encode())
client_socket.sendall(message2.encode())

# 关闭连接
client_socket.close()
服务端
import socket

# 创建 Socket 对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 绑定服务器地址和端口
server_address = ('localhost', 8081)
server_socket.bind(server_address)

# 监听客户端请求
server_socket.listen(1)
print('等待客户端连接...')

while True:
    # 接受连接
    client_socket, client_addr = server_socket.accept()
    print('与客户端建立连接:', client_addr)

    # 接收数据
    data = client_socket.recv(1024)  # 接收数据包
    received_data = data.decode()

    # 处理接收到的数据
    print('接收到数据:', received_data)

理想情况:

等待客户端连接...
与客户端建立连接: ('127.0.0.1', 61127)
接收到数据: Hello
接收到数据: World

实际情况:

等待客户端连接...
与客户端建立连接: ('127.0.0.1', 61127)
接收到数据: HelloWorld

导致粘包的原因

1.缓冲区大小限制:在TCP传输中,由于数据过大,超出缓存区大小限制,导致接收方不能接收到所有的数据包,造成了数据包的截断或丢失

2.底层协议特性:底层传输协议如 TCP 是面向流的,不保留消息边界。TCP 协议会将数据流切分为适当大小的数据块进行传输,因此无法保证每个数据包的边界

3.数据发送速度过快:发送方连续发送数据包,而接收方无法及时处理,导致多个数据包在接收缓冲区中堆积

解决方案:struct模块

利用pack()方法将任意长度的 数字 打包成新的数据

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

  • pack()方法 第一个参数是格式,第二个参数是整数(数据的长度),返回值是一个新的数据
  • unpack()方法 第一个参数是格式,第二个参数是 pack()方法打包后生成的新数据,返回值是一个元组,元组中放着打包前数据真实的长度
import struct

msg_one = '你好'
msg_two = ('struct 是 Python 标准库中的一个模块,用于进行字节与数据类型之间的相互转换。它提供了'
           '一组函数来打包(pack)和解包(unpack)数据,使得数据在网络传输或文件存储时能够以二进制形式进行处理。')
total = len(msg_one) + len(msg_two)  # 106

# 将数据打包
res = struct.pack('i', total)

# 解包数据
un_res = struct.unpack('i', res)

print(len(res))  # 4
print(res)  # bytes类型:  b'j\x00\x00\x00'
print(un_res)  # 元组类型: (106,)

粘包问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据

客户端
import socket
import struct

# 创建 Socket 对象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 服务器地址和端口
server_address = ('localhost', 8081)

# 连接服务器
client_socket.connect(server_address)
print('已连接到服务器:', server_address)

# 发送数据包
msg = b'helloworld'
data = struct.pack('i', len(msg))


# 先发送报头
client_socket.send(data)

# 发送真实数据
client_socket.send(msg)
服务端
import socket
import struct

# 创建 Socket 对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 绑定服务器地址和端口
server_address = ('localhost', 8081)
server_socket.bind(server_address)

# 监听客户端请求
server_socket.listen(1)
print('等待客户端连接...')

while True:
    # 接受连接
    client_socket, client_addr = server_socket.accept()
    print('与客户端建立连接:', client_addr)

    # 接收数据
    data = client_socket.recv(1024)  # 接收数据包
    received_data = struct.unpack('i', data)
    data_len = received_data[0]

    real_data = client_socket.recv(data_len)

    # 处理接收到的数据
    print('接收到数据:', real_data.decode('utf8'))

根据该原理改进案例代码

客户端
import socket
import struct

# 创建Socket
Socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 绑定服务器和端口号
servers_addr = ('127.0.0.1', 8082)
Socket.bind(servers_addr)

# 监听客户端请求 最大连接数为5
Socket.listen(5)
print('服务器启动成功,等待客户端连接...')

# 接受数据
client_socket, client_addr = Socket.accept()
print('与客户端建立连接', client_addr)
# client_socket.setblocking(False)
# 数据交换
while True:
    # 接受报头
    header = client_socket.recv(4)  # 最大1024字节
    if len(header) < 1:
        print('关闭服务')
        break
    data_len = struct.unpack('i', header)[0]
    print(data_len)

    # 接受真实数据
    real_data = client_socket.recv(data_len)
    print(real_data.decode('gbk'))

    # 向客户端返回数据
    client_socket.send(real_data)
服务端
import socket
import struct
import subprocess

# 获取cmd指令
cmd_from_client = 'ipconfig'
cmd_msg = subprocess.Popen(cmd_from_client,
                           shell=True,  # 使用shell命令
                           stdout=subprocess.PIPE,  # 管道一:输出结果
                           stderr=subprocess.PIPE  # 管道二:输出错误信息
                           )
msg_one = cmd_msg.stdout.read().decode('gbk')
msg_two = cmd_msg.stderr.read().decode('gbk')
msg = msg_one + msg_two

# 创建Socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 服务器地址和端口
server_address = ('localhost', 8082)

# 连接服务器
client_socket.connect(server_address)
print('已连接到服务器:', server_address)

while True:
    # 先发报头
    data_len = struct.pack('i', len(msg))
    client_socket.send(data_len)

    # 发送数据
    client_socket.send(msg.encode('gbk'))

    # 接收响应
    response = client_socket.recv(data_len[0])
    print('服务器响应:', response.decode('gbk'))

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/336414.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode 热题 100 | 双指针(上)

目录 1 283. 移动零 2 11. 盛最多水的容器 3 15. 三数之和 菜鸟做题第一周&#xff0c;语言是 C 1 283. 移动零 解题思路&#xff1a; 两个指针一前一后遍历数组前者永远指向 0&#xff0c;后者永远在寻找非 0 数的路上后者找到一个非 0 数就和前者进行一个数值交换 …

Python爬虫从入门到入狱系列合集

我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448; 入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448; 虚 拟 环 境 搭 建 &#xff1a;&#x1f449;&…

linux下USB抓包和分析流程

linux下USB抓包和分析流程 在windows下抓取usb包时可以通过wireshark安装时安装USBpcap来实现usb抓包&#xff0c;linux下如何操作呢&#xff1f; 是基于usbmon&#xff0c;本博客简单描述基于usbmon在linux系统上对通过usb口进行发送和接收的数据的抓包流程&#xff0c;分别描…

Unity SnapScrollRect 滚动 匹配 列表 整页

展示效果 原理: 当停止滑动时 判断Contet的horizontalNormalizedPosition 与子Item的缓存值 相减,并得到最小值&#xff0c;然后将Content horizontalNormalizedPosition滚动过去 使用方式&#xff1a; 直接将脚本挂到ScrollRect上 注意&#xff1a;在创建Content子物体时…

Python初学者须知(10)初识条件判断

本系列博客主要针对的是Python初学者。Python语言简洁、强大的特性吸引了越来越多的技术人员将他们的项目转移到Python上。目前&#xff0c;Python已经成为计算机行业最流行的编程语言之一。笔者考虑到Python初学者的多元化&#xff08;Python学习者可能是对编程感兴趣的中学生…

[小程序]API、数据与事件

一、API ①事件监听API 以on开头&#xff0c;用来监听事件的触发&#xff08;如wx.inWindowResize&#xff09; ②同步API 以Sync结尾&#xff0c;且可以通过函数返回值获取&#xff0c;执行错误会抛出异常&#xff08;如wx.setStorageSync&#xff09; ③异步API 类似网页中的…

记录一个sql:查询商品码对应多个商品的商品码

目录 背景sql 语句总结 背景 一个项目中&#xff0c;商品表和商品码表是一对多的关系&#xff0c;但由于程序没有控制好&#xff0c;导致有些商品码对应有多个商品&#xff0c;为了修正数据&#xff0c;我们得把商品码对应多个商品的商品码找出来. sql 语句 goods_detail表结构…

【Spring 篇】MyBatis中的CRUD魔法:数据之美的四重奏

MyBatis&#xff0c;这个数据持久化的魔法师&#xff0c;以其优雅的SQL映射和简洁的配置文件&#xff0c;为我们呈现出一场CRUD&#xff08;Create, Read, Update, Delete&#xff09;的奇妙之旅。在这篇博客中&#xff0c;我们将深入探讨MyBatis中的增、删、改、查操作&#x…

回归预测 | Matlab基于OOA-SVR鱼鹰算法优化支持向量机的数据多输入单输出回归预测

回归预测 | Matlab基于OOA-SVR鱼鹰算法优化支持向量机的数据多输入单输出回归预测 目录 回归预测 | Matlab基于OOA-SVR鱼鹰算法优化支持向量机的数据多输入单输出回归预测预测效果基本描述程序设计参考资料 预测效果 基本描述 1.Matlab基于OOA-SVR鱼鹰算法优化支持向量机的数据…

Spring Security 优化鉴权注解:自定义鉴权注解的崭新征程

文章目录 1. 引言2. Spring Security基础2.1 Spring Security概述2.2 PreAuthorize注解 3. 自定义鉴权注解的优势3.1 业务语义更明确3.2 参数化鉴权更灵活3.3 可维护性更好 4. 实现自定义鉴权注解4.1 创建自定义注解4.2 实现鉴权逻辑4.3 注册自定义注解和逻辑4.4 使用自定义注解…

Vagrant创建Oracle RAC环境示例

利用Vagrant安装Oracle RAC&#xff08;默认为non-CDB模式&#xff09;&#xff0c;生成2台虚机&#xff0c;耗时约1小时。 node1: -----------------------------------------------------------------node1: INFO: 2024-01-11 18:25:54: Make create database commandnode1: …

有关Quick BI中lod_fixed函数中以MAX()作为过滤条件报错

一、Quick BI中的lod_fixed函数 lod_fixed{维度1[,维度2]...:聚合表达式[:过滤条件]} 作用&#xff1a;使用指定维度进行计算而不引用任何其他维度。其中&#xff0c; 维度1[,维度2]...&#xff1a;声明维度&#xff0c;指定聚合表达式要连接到的一个或多个维度。使用逗号分…

【HarmonyOS】掌握布局组件,提升应用体验

从今天开始&#xff0c;博主将开设一门新的专栏用来讲解市面上比较热门的技术 “鸿蒙开发”&#xff0c;对于刚接触这项技术的小伙伴在学习鸿蒙开发之前&#xff0c;有必要先了解一下鸿蒙&#xff0c;从你的角度来讲&#xff0c;你认为什么是鸿蒙呢&#xff1f;它出现的意义又是…

DP活动:以太网HMI线下培训RA6M3 HMI Board[MQTT Squareline LVGL]

以太网HMI线下培训-环境准备 这是官方社群的文档&#xff1a;【腾讯文档】以太网线下培训&#xff08;HMI-Board&#xff09;所有教程都在这~ https://docs.qq.com/doc/DY0FIWFVuTEpORlNn R A 6 M 3 H M I − B o a r d \textcolor{#4183c4}{RA6M3 HMI-Board} RA6M3HMI−Board…

鼠标移动高亮边框

这个其实我也没有很明白&#xff0c;写的比较粗糙。 说一下步骤&#xff1a; 1.在界面上放上几排的div&#xff0c;要求做成卡片网格布局。 2.每一个卡片年内放置一个div&#xff0c;写文字或者其他都可以&#xff0c;要求不设置高度使用position: absolute; inset: 1px;将元素…

lattice Diamond Programmer程序下载

Lattice Diamond Programmer Diamond Programmer程序下载1 Diamond Programmer启动2 Diamond Programmer程序烧写3 Cannot Identify Device错误解决 Diamond Programmer程序下载 Diamond Programmer适用于Lattice公司的FPGA器件与CPLD器件的程序下载&#xff0c;其下载步骤如下…

【flutter】完全自定义样式模态对话框

示例完成结果展示&#xff1a; 示例组件代码&#xff1a; context&#xff1a;上下文 title&#xff1a;提示标题&#xff0c;null时不显示 content&#xff1a;提示内容&#xff0c;null时不显示 cancelText&#xff1a;取消按钮文字&#xff0c;null时不显示取消按钮 confirm…

每日一练【最大连续1的个数】

一、题目描述 给定一个二进制数组 nums 和一个整数 k&#xff0c;如果可以翻转最多 k 个 0 &#xff0c;则返回 数组中连续 1 的最大个数 。 二、题目解析 本题同样是利用滑动窗口的解法。 首先进入窗口&#xff0c;如果是1&#xff0c;就直接让right&#xff0c;但是如果是…

Android双击图片放大移动图中双击点到ImageView区域中心,Kotlin

Android双击图片放大移动图中双击点到ImageView区域中心&#xff0c;Kotlin 初始化状态&#xff0c;ImageView里面只是显示一张fitcenter被缩放的原图&#xff0c;当手指在图片上双击后&#xff08;记录双击点位置&#xff1a;mCurX&#xff0c;mCurY&#xff09;画一个红色小圆…

html5实现好看的年会邀请函源码模板

文章目录 1.设计来源1.1 邀请函主界面1.2 诚挚邀请界面1.3 关于我们界面1.4 董事长致词界面1.5 公司合作方界面1.6 活动流程界面1.7 加盟支持界面1.8 加盟流程界面1.9 加盟申请界面1.10 活动信息界面 2.效果和源码2.1 动态效果2.2 源码目录结构 源码下载 作者&#xff1a;xcLei…