首页 > 其他分享 >粘包问题

粘包问题

时间:2024-01-16 20:15:57浏览次数:22  
标签:struct server 问题 json client msg 粘包 size

粘包问题

(1)粘包问题介绍

  • 粘包问题是在计算机网络中,特别是在使用面向流的传输协议(例如TCP)时经常遇到的一种情况。它主要涉及到数据在传输过程中的组织和接收问题。

  • 当使用TCP协议进行数据传输时,发送方往往会将要传输的数据切分成小的数据块,并通过网络发送给接收方。然而,底层的网络传输通常并不保证接收方能够按照发送方的数据块边界精确地接收数据。这就可能导致多个小数据包在传输过程中被合并成一个大的数据块,这种情况被称为粘包问题。

(2)解决办法

  • 粘包问题可能会导致接收方难以正确解析和处理接收到的数据,因为数据的边界变得模糊不清。为了解决粘包问题,通常采用以下一些方法:
  1. 消息定界符: 在数据包中使用特殊字符或序列作为消息的结束标志,接收方根据这个标志来切分数据包。
  2. 消息长度固定: 在每个数据包中包含消息的固定长度信息,接收方根据长度来切分数据包。
  3. 使用消息头: 在数据包的开头添加一个消息头,包含消息的长度等信息,接收方根据消息头来切分数据包。

TCP协议解决粘包问题基础

  • 利用 struct 模块将传输过去的数据的总长度 打包 + 到头部进行发送

(1)服务端模版

from socket import socket
import subprocess
import struct

# 创建套接字
server = socket()

# 绑定地址和端口
server.bind(('127.0.0.1', 8090))

# 监听连接
server.listen(5)

while True:
    # 接受客户端连接
    conn, client_addr = server.accept()

    while True:
        # 接收客户端发送的命令
        cmd_from_client = conn.recv(1024)

        # 如果接收到的数据长度为0,说明客户端已经断开连接,跳出循环
        if len(cmd_from_client) == 0:
            break

        # 在子进程中执行客户端发送的命令
        msg_server = subprocess.Popen(cmd_from_client.decode('utf-8'),
                                      shell=True,
                                      stdout=subprocess.PIPE,
                                      stderr=subprocess.PIPE,
                                      )

        # 读取执行成功的结果(stdout)和执行失败的结果(stderr)
        true_msg = msg_server.stdout.read()  # 读取到执行成功的结果,是二进制数据类型
        false_msg = msg_server.stderr.read()  # 读取到执行失败的结果,是二进制数据类型

        # 计算总结果的大小
        total_size_from_server = len(true_msg) + len(false_msg)

        # 将总大小打包成4字节的二进制数据,并发送给客户端
        total_size_from_server_pack = struct.pack('i', total_size_from_server)
        conn.send(total_size_from_server_pack)

        # 将执行成功的结果和执行失败的结果发送给客户端
        conn.send(true_msg)
        conn.send(false_msg)

    # 关闭与客户端的连接
    conn.close()

(2)客户端模版

from socket import socket
import struct

# 创建套接字
client = socket()

# 连接到服务器
client.connect(('127.0.0.1', 8090))

while True:
    # 输入要发送的信息
    msg = input('请输入信息:>>>').strip()

    # 如果消息为空,继续输入
    if not msg:
        continue

    # 将消息编码成UTF-8并发送给服务器
    msg = msg.encode('utf-8')
    client.send(msg)

    # 接收服务器发送的总结果大小,4字节的二进制数据
    recv_total_size_msg = client.recv(4)

    # 解包得到总结果大小
    recv_total_size = struct.unpack('i', recv_total_size_msg)[0]

    # 初始化已接收的数据大小
    recv_size = 0

    # 循环接收数据直到达到总结果大小
    while recv_size < recv_total_size:
        # 接收服务器发送的部分结果,每次最多接收1024字节
        msg_from_server = client.recv(1024)

        # 更新已接收的数据大小
        recv_size += len(msg_from_server)

        # 将接收到的二进制数据解码成GBK编码的字符串并打印
        msg_from_server = msg_from_server.decode('gbk')
        print(msg_from_server, end='')

    else:
        print('命令结束')

    # 关闭与服务器的连接
    client.close()

TCP协议解决粘包问题进阶

  • 通过json模式,模版修改参数直接套用

(1)服务端模版

from socket import socket
import subprocess
import struct
import json

# 创建套接字
server = socket()

# 绑定地址和端口
server.bind(('127.0.0.1', 8085))

# 监听连接
server.listen(5)

while True:
    # 接受客户端连接
    conn, client_addr = server.accept()

    while True:
        # 接收客户端发送的命令
        cmd_from_client = conn.recv(1024)
        print(f'来自客户端的消息:>>> {cmd_from_client.decode("utf-8")}')

        # 如果接收到的数据长度为0,说明客户端已经断开连接,跳出循环
        if len(cmd_from_client) == 0:
            break

        # 在子进程中执行客户端发送的命令
        msg_server = subprocess.Popen(cmd_from_client.decode('utf-8'),
                                      shell=True,
                                      stdout=subprocess.PIPE,
                                      stderr=subprocess.PIPE,
                                      )

        # 读取执行成功的结果(stdout)和执行失败的结果(stderr)
        true_msg = msg_server.stdout.read()  # 读取到执行成功的结果,是二进制数据类型
        false_msg = msg_server.stderr.read()  # 读取到执行失败的结果,是二进制数据类型

        # 计算总结果的大小
        total_size_from_server = len(true_msg) + len(false_msg)

        # 构建消息头的字典
        headers_dict = {
            'file_name': 'a.txt',  # 文件名
            'total_size': total_size_from_server,  # 总大小
            'md5': 'md5'  # MD5校验值
        }

        # 将消息头转为JSON格式的字符串
        json_data_str = json.dumps(headers_dict)
        # 将JSON字符串转为字节数据
        json_data_bytes = json_data_str.encode('utf-8')

        # 将JSON数据的长度打包成4字节的二进制数据,并发送给客户端
        json_data_size_pack = struct.pack('i', len(json_data_bytes))
        conn.send(json_data_size_pack)

        # 发送JSON数据给客户端
        conn.send(json_data_bytes)

        # 发送执行成功的结果和执行失败的结果给客户端
        conn.send(true_msg)
        conn.send(false_msg)

    # 关闭与客户端的连接
    conn.close()

(2)客户端模版

import json
from socket import socket
import struct

# 创建套接字
client = socket()

# 连接到服务器
client.connect(('127.0.0.1', 8085))

while True:
    # 输入要发送的信息
    msg = input('请输入命令:>>>').strip()
    # 如果消息为空,继续输入
    if not msg:
        print('消息不能为空')
        continue
    # 将消息编码成UTF-8并发送给服务器
    msg = msg.encode('utf-8')
    client.send(msg)

    # 接收服务器发送的JSON数据大小,4字节的二进制数据
    json_data_size_unpack = client.recv(4)

    # 解包得到JSON数据的大小
    json_data_size = struct.unpack('i', json_data_size_unpack)[0]

    # 接收服务器发送的JSON数据,根据JSON数据大小
    json_data_bytes = client.recv(json_data_size)

    # 将JSON数据解码成字符串
    header_dict_str = json_data_bytes.decode('utf-8')

    # 将JSON字符串转为字典
    header_dict = json.loads(header_dict_str)

    # 获取总结果的大小
    recv_total_size = header_dict['total_size']

    # 初始化已接收的数据大小
    recv_size = 0

    # 循环接收数据直到达到总结果大小
    while recv_size < recv_total_size:
        # 接收服务器发送的部分结果,每次最多接收1024字节
        msg_from_server = client.recv(1024)

        # 更新已接收的数据大小
        recv_size += len(msg_from_server)

        # 将接收到的二进制数据解码成GBK编码的字符串并打印
        msg_from_server = msg_from_server.decode('gbk')
        print(msg_from_server)

    else:
        print('命令结束')

    # 关闭与服务器的连接
client.close()

struct模块补充

struct 模块是 Python 标准库中的一个模块,用于处理 C 语言中的结构体(struct)和二进制数据的转换。它提供了一组函数,可以将二进制数据与 Python 数据类型进行相互转换。

以下是 struct 模块的主要函数:

  1. struct.pack(format, v1, v2, ...)

    • 将数据按照指定的格式 format 打包成一个二进制字符串。v1, v2, ... 是要打包的数据。
  2. struct.unpack(format, string)

    • 将二进制字符串按照指定的格式 format 解包,返回一个包含解包后数据的元组。
  3. struct.calcsize(format)

    • 返回按照指定格式 format 打包的字符串的长度(字节数)。

这些函数中的 format 参数是一个字符串,它指定了数据的布局和编码方式。常用的格式化字符包括:

  • 'i' 表示一个有符号整数(4字节)。
  • 'I' 表示一个无符号整数(4字节)。
  • 'h' 表示一个有符号短整数(2字节)。
  • 'H' 表示一个无符号短整数(2字节)。
  • 'f' 表示一个单精度浮点数(4字节)。
  • 'd' 表示一个双精度浮点数(8字节)。

以下是一个简单的例子,演示了 struct.packstruct.unpack 的基本用法:

import struct

# 将数据打包成二进制字符串
packed_data = struct.pack('i', 1)

# 打印打包后的二进制数据
print("Packed Data:", packed_data)
# Packed Data: b'\x01\x00\x00\x00'

# 将二进制字符串解包成数据
unpacked_data = struct.unpack('i', packed_data)

# 打印解包后的数据
print("Unpacked Data:", unpacked_data)
# Unpacked Data: (1,)

这个例子中,'i' 指定了打包和解包的格式,为一个有符号整数。

(1)补充

  1. header 定义了一个头部字典,包含文件大小和文件名等信息。
  2. head_bytes 将头部字典通过 json.dumps 转换为JSON格式的字符串,然后通过 bytes 函数转换为字节串,使用UTF-8编码。
  3. head_len_bytes 获取头部字节串的长度,然后使用 struct.pack 将长度打包成4字节的二进制数据。
  4. print 语句: 打印原始数据、JSON序列化后的数据和压缩后的数据。
import json
import struct

# 定义一个头部字典
header = {'file_size': 1073741824000, 'file_name': 'a.txt'}

# 将头部字典转换为字节串
head_bytes = bytes(json.dumps(header), encoding='utf-8')

# 获取头部字节串的长度,并将长度打包成4字节的二进制数据
head_len_bytes = struct.pack('i', len(head_bytes))

# 打印原始数据
print(f"这是原本的数据 :>>>> {header}")
# 这是原本的数据 :>>>> {'file_size': 1073741824000, 'file_name': 'a.txt'}

# 打印JSON序列化后的数据
print(f"这是JSON序列化后的数据 :>>>> {head_bytes}")
# 这是JSON序列化后的数据 :>>>> b'{"file_size": 1073741824000, "file_name": "a.txt"}'

# 打印压缩后的数据(头部字节串的长度)
print(f"这是压缩后的数据 :>>>> {head_len_bytes}")
# 这是压缩后的数据 :>>>> b'2\x00\x00\x00'

标签:struct,server,问题,json,client,msg,粘包,size
From: https://www.cnblogs.com/ssrheart/p/17968423

相关文章

  • 使用zipFile读取文件时遇到的问题及解决(KeyError: "There is no item named 'xxx' in
    问题描述在Windows上跑一段代码时,遇到如下问题:KeyError:"Thereisnoitemnamed'CDR_Data\\\\CDR.Corpus.v010516\\\\CDR_DevelopmentSet.PubTator.txt'inthearchive"原因分析这是一段Python代码,代码中使用到了zipfile库,它首先创建了一个ZipFile对象,然后在调用read()......
  • C99标准前后对于二维数组的动态声明问题
    html:toc:true写在前面:出于作者不了解C99以前标准中对二维数组的动态声明而导致的一场考场事故,作者写下这篇文章,,以便其他同学在遇到类似问题时不要犯同样的错误,同时作为对自己的警醒.本文主要是关于对于二维数组动态声明问题在不同C标准下方法的探讨,将给出一个简......
  • Elastic实战:彻底解决spring-data-elasticsearch日期、时间类型数据读取报错问题
    0.引言在使用spring-data-elasticsearch读取es中时间类型的数据时出现了日期转换报错,不少初学者会在这里困惑很久,所以今天我们专门来解读该问题的几种解决方案。1.问题分析该问题的报错形式一般是:Failedtoconvertfromtype[java.lang.String]totype[java.util.Date]f......
  • 一个累计扣款的问题
    问题:从左往右扣款,不足的继续扣,扣够了停止(这个……太难描述了,财务的童鞋大概是懂的哈……) 函数公式解决:I2公式 =IF(SUM(J2:$N2),A2,MAX(,SUM(A2:$F2)-$G2))N2公式 =MAX(,F2-G2)N2公式下拉:直接用F2减去G2,用Max处理负数,当出现负数里结果显示0。I2公式右拉下拉至I2:M6单元格区域:如......
  • Oracle ASMLIB配置iSCSI设备导致的问题
     OracleASMLIB配置iSCSI设备导致的问题 出于测试目的或者环境不重要等目的,如果使用iSCSI设备并且使用ASMLIB配置管理,可能会遇到如下两个问题。都是我遇到的问题。OracleLinux7上边正常安装配置ASMLIB后,如果进行重启会导致扫描不到之前创建标记的盘,需要手工使用oracleasm......
  • SourceTree提交快捷键(Ctrl+Enter)无效问题问题
    之前用SourceTree,Ctrl+Enter快速commit。最近更新版本到3.4.16后,突然发现不能用了。更新大概是跨了几个小版本。。。在atlassian论坛里发现,这个果然就是更新的变更(我不是一个人.jpg)。目前大概有两个方法解决:回退到3.4.15或之前的版本。从旧的安装包(3.4.15)里提取两个dll进......
  • 粘包
    粘包【一】什么是粘包须知:只有TCP有粘包现象,UDP永远不会粘包【二】什么是粘包问题客户端发送需要执行的代码服务端接收到客户端传过来的代码服务端调用方法执行代码并拿到执行后的结果服务端将执行后的结果进行返回客户端接收到服务端返回的结果并做打印输出【1】......
  • SqlSugar常见问题汇总
    1、已有打开的与此Command相关联的DataReader,必须首先将它关闭。ThereisalreadyanopenDataReaderassociatedwiththisConnectionwhichmustbeclosedfirst.或者出现connectionisclosed出现这个错一般是线程安全引起的解决方案: https://www.donet5.com/Home/......
  • crontab 环境变量的问题
    1、大致功能就是找出未在fstab中挂载的磁盘,并发送到云平台告警。#!/bin/bashsource/etc/profileremote_address="http://1.1.1.1:6666"user=xxxxpass=xxxxexcept_block=("/dev/sr0""/dev/cdrom""/dev/dvd")blkid_output=$(blkid-sUUID)all_blo......
  • paxos协议之衍生协议:Raft协议的简述、协议模型、一致性算法、脑裂问题处理、选举流程
    raft简述raft协议中节点有三种状态leader、follower、candidate(候选人),leader复制日志的管理、客户端的新增更新请求,然后复制到follower节点,如果leader出现故障则follower就会重新选举,新增等操作若被follower所接收则会进行重定向转给leader,follower只负责客户端的读请求。有两......