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

粘包问题

时间:2024-03-20 09:46:22浏览次数:19  
标签:cmd send server 问题 json client msg 粘包

粘包问题

  • 须知:只有TCP有粘包现象,UDP永远不会粘包

一、什么是粘包问题

  • 什么时候会发生粘包问题?
    • 当TCP传输和接收的数据并非我们规定的最大数据量时,就会发生粘包
    • 我们日常传输的数据几乎不可能等于我们规定的数据量,所以我们必须要解决这个问题
  • 为什么只有TCP会发生粘包问题?
    • TCP是面向流的协议,就是TCP为了提高传输效率发送数据时往往都是收集到足够多的数据后才发送一个段
    • 大白话讲就是TCP会收集一定的数据然后再发送,但是接收方并没有办法知道发来的数据要按怎么样的方式切分,而无法读取数据
    • UDP则不会使用合并算法,且接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息)
    • 这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。

[小结]什么是粘包问题

  • 客户端发送需要执行的代码
  • 服务端接收到客户端传过来的代码
    • 服务端调用方法执行代码并拿到执行后的结果
    • 服务端将执行后的结果进行返回
  • 客户端接收到服务端返回的结果并做打印输出

二、粘包问题的演示

  • 我们来使用客户端发送一段cmd命令给服务端,在服务端运行并返回数据给客户端

  • 客户端接收数据并打印

  • 由于返回的数据长度大于1024所以无法全部显示

  • 服务端

import socket
import subprocess

# 创建服务对象
server = socket.socket(type=socket.SOCK_STREAM)

# 设定默认的IP和端口号
IP = '127.0.0.1'
PORT = 8081

server.bind((IP, PORT))

server.listen(5)

while True:
    conn, addr = server.accept()
    while True:
        # 检测可能出现的异常并进行处理
        try:
        # 取到 客户端发来的cmd命令
            cmd_from_client = conn.recv(1024)

            # 不允许传入的命令为空
            if not cmd_from_client:
                break

            # 对接收的命令进行解码
            cmd_from_client = cmd_from_client.decode('gbk')
            # 执行客户端传过来的cmd命令并获取结果
            msg_server = subprocess.Popen(cmd_from_client,
                                          shell=True,  # 执行shell命令
                                          stdout=subprocess.PIPE,  # 管道一
                                          stderr=subprocess.PIPE,  # 管道二
                                          )
            true_res = msg_server.stdout.read()  # 读取正确结果
            false_res = msg_server.stderr.read()  # 读取错误结果

            conn.send(true_res)
            conn.send(false_res)
        except ConnectionResetError as e:
            break
    conn.close()
  • 客户端
import socket

while True:
    client = socket.socket(type=socket.SOCK_STREAM)

    # 设定默认的IP和端口号
    IP = '127.0.0.1'
    PORT = 8081

    client.connect((IP, PORT))
    cmd_send_to_server = input('请输入想要实现的cmd命令:').strip()

    if not cmd_send_to_server:
        break
    cmd_send_to_server = cmd_send_to_server.encode('utf-8')

    client.send(cmd_send_to_server)

    msg_from_server = client.recv(1024)

    msg_from_server = msg_from_server.decode('gbk')

    print(msg_from_server)

    client.close()

  • 客户端收到的返回的不完整的结果

  • cmd中的结果

  • 可以看出客户端接收的结果并不完全

三、解决方案

[1]解决问题的思路

  • 拿到数据的总大小 recv_total_size
  • recv_size = 0 ,循环接收,每接收一次,recv_size += 接收的长度
  • 直到 recv_size = recv_total_size 表示接受信息完毕,结束循环

[2]解决方案

(1)基础版

  • 直接使用struct模块打包数据的长度,传输到客户端,方便对数据的处理

  • 服务端

import socket
import subprocess
import struct

server = socket.socket(type=socket.SOCK_STREAM)

IP = '127.0.0.1'
PORT = 8083

server.bind((IP, PORT))

server.listen(5)

while True:
    conn, addr = server.accept()
    cmd_from_client = conn.recv(1024)
    cmd_from_client = cmd_from_client.decode('utf-8')

    msg_server = subprocess.Popen(cmd_from_client,
                                  shell=True,
                                  stdout=subprocess.PIPE,
                                  stderr=subprocess.PIPE,
                                  )
    true_msg = msg_server.stdout.read()
    false_msg = msg_server.stderr.read()

    total_len = len(true_msg) + len(false_msg)

    total_len_pack = struct.pack('i', total_len)

    conn.send(total_len_pack)

    conn.send(true_msg)
    conn.send(false_msg)
    conn.close()
    break
server.close()
  • 客户端
import socket
import struct

while True:
    client = socket.socket(type=socket.SOCK_STREAM)

    IP = '127.0.0.1'
    PORT = 8083

    client.connect((IP, PORT))

    cmd_send_to_server = input('输入你想要实现的cmd命令:')

    cmd_send_to_server = cmd_send_to_server.encode('utf8')

    client.send(cmd_send_to_server)

    cmd_from_server_pack = client.recv(4)

    cmd_from_server_unpack = struct.unpack('i', cmd_from_server_pack)

    total_len = cmd_from_server_unpack[0]

    txt = b''
    while total_len > 0:
        msg = client.recv(1024)
        txt += msg
        total_len -= 1024
    print(txt.decode('gbk'))
    break

(2)进阶版

  • 思路

    1. 将数据的名字,长度等信息放入字典中
    2. 通过json模块将字典转换成json字符串并编码成二进制数据(需要传输的数据:json字符串)
    3. 使用struct模块将json字符串的长度打包成四位二级制数据(需要传输的数据:struct包)
    4. 将原始数据(不是二进制格式的转换成二进制)进行传输(需要传输的数据:原始数据)
    5. 客户端可以通过解struct包和json字符串的二进制数据得到之后每一部分数据的长度,并通过循环取出每个部分
  • 服务端

import socket
import struct
import subprocess
import json

server = socket.socket()

server.bind(('127.0.0.1', 8080))

server.listen(5)

while True:
    conn, addr = server.accept()

    msg_from_client = conn.recv(1024)

    msg_from_client = msg_from_client.decode('utf-8')

    if msg_from_client == 'q':
        conn.send(b'q')
        break
    msg_server = subprocess.Popen(msg_from_client,
                                  shell=True,
                                  stdout=subprocess.PIPE,
                                  stderr=subprocess.PIPE,
                                  )
    true_msg = msg_server.stdout.read()
    false_msg = msg_server.stderr.read()

    total_len = len(true_msg) + len(false_msg)

    msg_dict = {
        'name': 'cmd',
        'total_len': total_len
    }

    msg_dict_json = json.dumps(msg_dict)

    msg_dict_json_b = msg_dict_json.encode('utf-8')

    struct_pack = struct.pack('i', len(msg_dict_json_b))

    conn.send(struct_pack)
    conn.send(msg_dict_json_b)
    conn.send(true_msg)
    conn.send(false_msg)

  • 客户端
import json
import socket
import struct

while True:
    client = socket.socket()

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

    while True:
        cmd_send_to_server = input('请输入想要实现的cmd命令:')
        if not cmd_send_to_server:
            print('命令不能是空白!')
            continue
        cmd_send_to_server = cmd_send_to_server.encode('utf-8')
        break
    client.send(cmd_send_to_server)

    struct_pack = client.recv(4)

    if struct_pack == b'q':
        break
    struct_unpack = struct.unpack('i', struct_pack)

    dict_json_b = client.recv(struct_unpack[0])

    dict_json = dict_json_b.decode('utf-8')

    msg_dict = json.loads(dict_json)

    total_len = msg_dict['total_len']

    txt = b''
    while total_len > 0:
        msg = client.recv(1024)
        txt += msg
        total_len -= 1024
    print(txt.decode('gbk'))

client.close()

四、案例

  • 一个文件夹中的将一个视频文件放入另一个文件夹中

  • 服务端

    conn, addr = server.accept()

    # conn.send(json_file_list_b)

    msg_from_client_b = conn.recv(1024)

    if msg_from_client_b == b'q':
        conn.send(b'q')
        break

    msg_from_client = msg_from_client_b.decode('utf-8')

    if msg_from_client not in file_list:
        conn.send('f'.encode('utf-8'))
        continue

    file_path = os.path.join(os.getcwd(), msg_from_client)

    with open(file_path, 'rb') as fp:
        video_data = fp.read()

    video_len = len(video_data)

    headers = {
        'file_name': msg_from_client,
        'video_len': video_len
    }

    headers_json = json.dumps(headers)

    headers_json_b = headers_json.encode('utf-8')

    struct_package = struct.pack('i', len(headers_json_b))

    conn.send(struct_package)

    conn.send(headers_json_b)

    conn.send(video_data)

  • 客户端
    print('以下是文件列表:\n')
    # 将操作的目录转到目标文件夹
    os.chdir(r'D:\Users\21277\Desktop\home')
    # 将文件夹中的所有文件名放入列表
    file_list = os.listdir(os.getcwd())
    # 将文件名打印出来
    for file_name in file_list:
        print(file_name)

    print('\n')

    client = socket.socket()

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

    # json_file_list_b = client.recv(1024)

    # file_list = json.loads(json_file_list_b.decode('utf-8'))
    #
    # for file_name in file_list:
    #     print(file_name)

    while True:
        msg_send_to_server = input('请输入想要传输的文件:')
        if not msg_send_to_server:
            print('不能是空白!')
            continue
        msg_send_to_server = msg_send_to_server.encode('utf-8')
        break

    client.send(msg_send_to_server)

    struct_package = client.recv(4)

    if struct_package == b'q':
        break
    if struct_package == b'f':
        print('未找到该文件!请重新输入文件名。')
        continue

    headers_json_len = struct.unpack('i', struct_package)[0]

    headers_json_b = client.recv(headers_json_len)

    headers = json.loads(headers_json_b.decode('utf-8'))

    video_len = headers['video_len']

    video_data = b''
    while video_len > 0:
        video_data += client.recv(1024)
        video_len -= 1024

    new_file_name = input('请重命名文件(不重命名则跳过):')
    if not new_file_name:
        new_file_name = msg_send_to_server.decode('utf-8')
    else:
        new_file_name = new_file_name + '.mp4'

    with open(os.path.join(r'D:\Users\21277\Desktop\new', new_file_name), 'wb') as fp:
        fp.write(video_data)

    client.close()

标签:cmd,send,server,问题,json,client,msg,粘包
From: https://www.cnblogs.com/taoyuanshi/p/18084479

相关文章

  • 倾斜摄影三维模型的模型合并的问题分析
    倾斜摄影三维模型的模型合并的问题分析   倾斜摄影是一种通过无人机或其他航空平台获取大范围地表影像和点云数据的技术,可以生成高分辨率、高精度的三维模型。在实际应用中,常常需要将不同区域的倾斜摄影三维模型进行合并,以便进行全局分析和应用。然而,模型合并过程中存在......
  • LiveGBS流媒体平台GB/T28181常见问题-与海康NCG大华VIS等国标平台对接如何判断自身是
    LiveGBS与海康NCG大华VIS等国标平台对接如何判断自身是上级还是下级?1、背景2、判定上级或是下级3、LiveGBS作为上级4、LiveGBS作为下级5、搭建GB28181视频直播平台1、背景国标项目实施的过程中,经常要与海康、大华、华为、宇视等国标视频平台对接,此时LiveGBS是作为下......
  • parse_str解析问题
    php解析raw格式数据: $raw_params=file_get_contents("php://input");收到原数据格式,得到类似:operator_token=10ac2753e78abxxxxx62d1f70fc2aaca&secret_key=bff7e71e45bad3ccexxx76e309&operator_player_session=RXZvR3dIMmpDUDZIL3hHZm0vdHRQZz09Ojo0iswLEYe3w/7P+F......
  • Python 机器学习 HMM模型三种经典问题
    ​ 隐马尔可夫模型(HiddenMarkovModel,HMM)是一个强大的工具,用于模拟具有隐藏状态的时间序列数据。HMM广泛应用于多个领域,如语音识别、自然语言处理和生物信息学等。在处理HMM时,主要集中于三个经典问题:评估问题、解码问题和学习问题。三个问题构成了使用隐马尔可夫模型时的基础......
  • 配置云服务器遇到的问题总结
    发现网上很多教程都是没毛用的,所以总结一下背景买了个华为云的服务器,想自己写个服务器本地ping不通云服务器核心原因:安全策略墙了解决方案:登录华为云官网www.huaweicloud.com点击“控制台”找到自己的云服务器往下滑找到更改安全组新建安全组......
  • 【优化布局】机器学习求解4G网络无人机布局优化问题【含Matlab源码 4113期】
    ......
  • AOSP平台编写Android-ebpf程序(tracepoint)的一些map定义和使用问题,导致map和prog无法
     前言本片文章并不主要讲解在AOSP平台ebpf程序的整个编写流程,只是一些的map的定义使用问题,如有需要可查看,aosp平台的整个下载流程,以及简单的程序的编译和如何push到手机运行,这位up是我在ebpf领域探索的领路人,本站ID:LiujiaHuan13,如果有需要up本人后面会考虑写一篇aosp程序书写......
  • Qt+vs2019+PCL1.12.1+VTK9.1环境搭建中的相关问题
    目录1.VS中双击Ui文件无法打开2.VTK9.0以后在QtDesigner中找不到QVTKWidget组件3.无法打开源文件"QVTKOpenGLNativeWidget.h"4.无法打开源文件"QOpenGLWidget"5.QWidget:MustconstructaQApplicationbeforeaQWidget6.无法打开源文件"QtWidgets/QApplicati......
  • nicelog--强大的Java日志组件,排查问题的利器
    ​ nicelog是一个功能强大的Java日志组件,它可以自动、手动收集日志,并通过traceId将日志关联起来,可极大提高排查问题的速度。官网:https://www.yuque.com/knifeblade/opensource/niceloggitee:https://gitee.com/knifeedge/niceloggithub:https://github.com/knife-blade/nicelog......
  • 如何解决 WinForm窗体标题字符数限制 导致的显示不全问题?
    现在需要对窗体标题进行居中显示,通过在标题内容前增加空格的方式达到该目的。实测是发现窗口标题的字符数量受到操作系统限制网上查询的最大标题字符数是260个字符实测最大字符数为587个下面的代码可以勉强解决“由于最大字符数受到操作系统的限制导致最大化时显示不全”的问......