• python网络编程-粘包问题的解决


    我们使用tcp协议的时候有时会出现一些问题,

    就比如我同时发送了3次数据,但是在另外一边缺只收到了一次,它把三次数据都和在了一起,

    服务端(接收)

    import socket
    
    
    server = socket.socket()
    server.bind(('127.0.0.1',18080))  # 绑定ip和端口
    server.listen(2)    # 半连接池
    
    
    
    conn,addr = server.accept() # 等别人来
    data = conn.recv(1024)      # 如果conn 没了,就会报错
    print('第一次接收--')
    print(data.decode('utf-8'))
    
    data = conn.recv(1024)      # 如果conn 没了,就会报错
    print('第二次接收---')
    print(data.decode('utf-8'))
    
    data = conn.recv(1024)      # 如果conn 没了,就会报错
    print('第三次接收---')
    print(data.decode('utf-8'))

    客户端(发送)

    import socket
    
    client = socket.socket()
    client.connect(('127.0.0.1',18080))     # 连接
    
    client.send(b'11')    # 发送第一次
    client.send(b'22')    # 第二次发送
    client.send(b'33')    # 第三次发送

    输出结果:

    >>>
    第一次接收-- 112233 第二次接收--- 第三次接收---

    这个就是tcp粘包问题

    解决这个问题的关键就是设置recv的长度,recv就是获取数据的长度,所以我们需要得到被发送数据的长度,但是我们又有可能不知道数据长度的长度,除非我们能固定这个数据的长度。这里我们可以引入一个模块:他就能固定数据的长度

    struct 模块

    struct 模块
        # 对数据进行打包处理,被打包后的数据是固定长度,,我们只需要接收这个固定长度就好,然后把这个包解开,就能得到真正的数据的长度
        pack # 打包数据
        unpack  # 解包数据

    这时我们就不止能发送数据了,还可以把数据的数据信息(名称,数据长度,大小),都打包到一个字典当中,我们直接把字典序列化,然后把字典的报头(数据长度),和字典一起传过去就好了。

    服务端(发送)
    1:生成一个字典
    2:制作字典的报头
    JSON序列化
    编码
    统计长度
    3:发送字典的报头
    4:发送字典
    5:发送真实的数据
    
    客户端(接收):
    1:先接受固定长度,4
    个字节的报头(几个字节取决于struct打包的报头长度)
    2:解析获取的字典数据长度
    unpack(...)[0]
    3:接受字典数据
    解码
    反序列化
    4:接收真实数据

    这样一来我们就成功解决了粘包问题


    代码

    客户端(接收)

    import socket
    import struct
    import json
    
    client = socket.socket()
    client.connect(('127.0.0.1',8080))
    
    while True:
        msg = input('>>>:').encode('utf-8')
        if len(msg) == 0:
            continue
        client.send(msg)
        # 1.先接受字典报头
        header_dict = client.recv(4)
    
        # 2.解析报头 获取字典的长度
        dict_size = struct.unpack('i',header_dict)[0]  # 解包的时候一定要加上索引0
    
        # 3.接收字典数据
        dict_bytes = client.recv(dict_size)
        dict_json = json.loads(dict_bytes.decode('utf-8'))
    
        # 4.从字典中获取信息
        print(dict_json)    # 这个是字典
        recv_size = 0       # 这个是设置下载总量的初始值,下载多少就加多少
        real_data = b''     # 这个是下载的东西,下载了什么,就把他加进去
            # 如果一直加这个数据的花,这个数据如果很大,会不会把内存弄爆炸?
        while recv_size < dict_json.get('file_size'):  # 如果已经接受的数据总量不大于被接受数据的总量时,跳出循环(接收循环)
        # while recv_size < dict_json.['file_size']:  # 不推荐用这个方法,最好用get,这个可能会报错
            data = client.recv(1024)    # 一次性接收多大
            real_data += data           # 把数据加上去
            recv_size += len(data)      # 把已经下载的数据长度加在下载数据长度上面
        print(real_data.decode('gbk'))  # 打印已经下载好的数据

    服务端(发送)

    import socket
    import subprocess
    import struct
    import json
    
    server = socket.socket()
    server.bind(('127.0.0.1', 8080))
    server.listen(5)
    
    while True:
        conn, addr = server.accept()
        while True:
            try:
                cmd = conn.recv(1024)
                if len(cmd) == 0: break
                cmd = cmd.decode('utf-8')
                obj = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                res = obj.stdout.read() + obj.stderr.read()
    
                d = {'file_size': len(res)}  # 这个是文件大小
                # d = {'name':'jason','file_size':len(res),'info':'asdhjkshasdad'}
                json_d = json.dumps(d)  # 格式化
    
                # 1.先制作一个字典的报头
                header = struct.pack('i', len(json_d))
                print(header,'---',len(header))
                # 2.发送字典报头
                conn.send(header)  # 发送字典的文件大小
    
                # 3.发送字典
                conn.send(json_d.encode('utf-8'))  # 发送字典
    
                # 4.再发真实数据
                conn.send(res)
    
                # conn.send(obj.stdout.read())
                # conn.send(obj.stderr.read())
            except ConnectionResetError:
                break
        conn.close()
  • 相关阅读:
    【读书笔记-《Android游戏编程之从零开始》】2.Hello,World!
    【读书笔记-《Android游戏编程之从零开始》】1.Android 平台简介与环境搭建
    .Net HttpClient 模拟登录微信公众平台发送消息
    C# DateTime的ToString()方法的使用
    SQL 2008R2 日期转换
    Dojo学习(一)—Hello Dojo
    【博客开篇】服务器配置:Windows2008R2+PHP5.6+SQLServer2008(X64)
    【翻译Autofac的帮助文档】1.入门指南
    申请免费的SSL证书(Win7,PowerShell,Let's Encrypt)
    Rms操作设置office系统文档权限
  • 原文地址:https://www.cnblogs.com/pscly/p/11348110.html
Copyright © 2020-2023  润新知