zoukankan      html  css  js  c++  java
  • Python 的黏包问题

    Client 端内的代码:
    
    #Author:BigBao
    #Date:2018/7/4
    import socket
    import struct
    client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    client.connect(('127.0.0.1',8012))
    while True:
        cmd=input('>>>: ').strip()
        if not cmd:continue
        client.send(cmd.encode('utf-8'))
        # 这里我们先收取报头,4个字节
        header=client.recv(4)
        total_size=struct.unpack('i',header)[0]
        # 接收真实的数据,直到收干净为止
        recv_size=0
        res=b''
        while recv_size < total_size:
            recv_data=client.recv(1024)
            res+=recv_data
            recv_size+=len(recv_data)
        print(res.decode('gbk'))
    client.close()
    Server  端的代码
    
    #Author:BigBao
    #Date:2018/7/4
    import socket
    import subprocess
    import struct
    
    server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    server.bind(('127.0.0.1',8012))
    server.listen(5)
    
    while True: # 建立连接循环
        conn,client_addr=server.accept()
        while True:   # 建立通信循环
            try:
                cmd=conn.recv(1024)
                if not cmd:break
                obj=subprocess.Popen(cmd.decode('utf-8'),shell=True,
                                     stdout=subprocess.PIPE,
                                     stderr=subprocess.PIPE)
                stdout=obj.stdout.read()
                stderr=obj.stderr.read()
                # 发送固定长度的报文头,目前报文头里值包含数据的大小,接下来制定报头
                total_size=len(stdout)+len(stderr)
                conn.send(struct.pack('i',total_size))  # 这里发送过去的数据是一个二进制数据,且长度为4,但是这里要是total_size 过大的话,这里就不行了,会报错struct.error: argument out of range
                conn.send(stdout)
                conn.send(stderr)
            except ConnectionResetError:
                break
            conn.close()
    server.close()

    上面我们说到了自定义报头的时候struct.pack('i',total_size) 这个方法在传输的数据过大的时候他就会报错,所以并不能完全的解决我们的黏包问题,只适用在我们传输小数据量的时候。

    接下来看一下Python的最终解决黏包问题的解决方案:

    #Author:BigBao
    #Date:2018/7/6
    import struct
    import json
    # obj=struct.pack('i',555565555)
    # print(obj,len(obj))
    
    # i 格式打包的个数是有限的
    '''
    解决黏包问题的主要思想就是:
    在我们发送数据流的时候,我们先给对方发送这个数据流的长度大小,也就是说
    我们需要自定一个固定大小的报头,然后发送给对方,这个报头根据我们的需求来定义,
    这个报头的内容比如说可以有:数据的长度,文件的名称(数据是文件的情况下)、md5值等等
    按照上面的需求,我们一般把报头定义成一个字典格式,但是我们如何把一个字典发送给对方呢,
    这个时候我们就要使用json,我们把字典转换成json串,json.dumps我们得到的是一个字符串格式的对象。
    我们不能直接发送字符串,所以我们需要给他编码一下 obj.encode('utf-8').
    比如说我们看一下下面的例子,
    '''
    header_dic={
        'filename':'xxx.mp4',
        'total_size':2342342374829374928374323423423449238749238742938749238749234923648763481256481736491287418375109837512653189237489123751928307,
        'md5':'8f6fbf8347faa4924a76856701edb0f3'
    }
    header_json=json.dumps(header_dic)   # 这样我们得到一个json对象,我们可以查看一下这是一个字符串类型
    # print(header_json,type(header_json))
    header_bytes=header_json.encode('utf-8')   # 我们给他编码一下,得到一个bytes的对象
    # 这里我们要注意我们能做成报头的大前提是。报头的大小固定,这里我们要是随意修改一下header_dir 中的total_size 的话,
    # 这里len(header_bytes)的大小就会发生变化,所以这里我们就要用到struct模块的pack功能了
    obj=struct.pack('i',len(header_bytes))
    print(obj,len(obj))   # 这里我们发现我们修改字典中的total_size 的话,这里的len(obj) 不会发生变化,我们看到他的大小一直都是4.满足我们的需求
    
    print(struct.unpack('i',obj))
    这里我们得到的是一个集合(209,) 所以我们取[0] 位置的数据就是报头的长度
    print(struct.unpack('i',obj)[0]) #1、这里我们先把报头的长度发送给对方 #2、然后我们发送报头 #3、最后发送数据
    #Author:BigBao
    #Date:2018/7/4
    import socket
    import subprocess
    import struct
    import json
    
    server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    server.bind(('127.0.0.1',8012))
    server.listen(5)
    print('服务端启动')
    while True: # 建立连接循环
        conn,client_addr=server.accept()
        while True:   # 建立通信循环
            try:
                cmd=conn.recv(1024)
                if not cmd:break
                obj=subprocess.Popen(cmd.decode('utf-8'),shell=True,
                                     stdout=subprocess.PIPE,
                                     stderr=subprocess.PIPE)
                stdout=obj.stdout.read()
                stderr=obj.stderr.read()
                # 制定报头:这里我们目前能用到的是header_dic里的total_size
                header_dic={
                    'total_size':len(stdout)+len(stderr),
                    'filename':'xxx.mp4',
                    'md5sum':'8f6fbf8347faa4924a76856701edb0f3'
                }
                # 发送报头的长度
                header_json=json.dumps(header_dic)
                header_bytes=header_json.encode('utf-8')
    
                conn.send(struct.pack('i',len(header_bytes)))
                # 发送报头
                conn.send(header_bytes)
                # 发送数据
                conn.send(stdout)
                conn.send(stderr)
            except ConnectionResetError:
                break
        conn.close()
    server.close()
    #Author:BigBao
    #Date:2018/7/4
    import socket
    import struct
    import json
    client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    client.connect(('127.0.0.1',8012))
    while True:
        cmd=input('>>>: ').strip()
        if not cmd:continue
        client.send(cmd.encode('utf-8'))
        # 我们先接受报头的长度的字节格式
        obj=client.recv(4)
        header_size=struct.unpack('i',obj)[0] # 这里unpack 我们得到的是一个集合(类似是这样的  (209,)   所以我们应该去第一个位置的值,这个值就是报头的长度),我们得到了报头的长度,然后我们在下面就接收这么长的字节数就可以得到报头对象了
    
        # 接受报头,从上面我们得到了报头的长度,所以我们recv(报头的长度)就可以得到报头对象了
        header_bytes=client.recv(header_size)
        header_json=header_bytes.decode('utf-8')
        header_dic=json.loads(header_json)
        total_size=header_dic['total_size']   # 这样我们就得到了字节流的长度了
    
        # 接收真实的数据,直到收干净为止
        recv_size=0
        res=b''
        while recv_size < total_size:
            recv_data=client.recv(1024)
            res+=recv_data
            recv_size+=len(recv_data)
        print(res.decode('gbk'))
    client.close()

    根据老师的文章写的:

    http://www.cnblogs.com/linhaifeng/articles/6129246.html#_label12

  • 相关阅读:
    shell流程控制
    shell编程变量介绍与表达式详解
    shell编程简介
    反向代理与负载均衡
    存储库之mongodb,redis,mysql
    请求库之requests,selenium
    解析库之re、beautifulsoup、pyquery
    爬虫基本原理
    Django 函数和方法的区别
    Django 知识补漏单例模式
  • 原文地址:https://www.cnblogs.com/smail-bao/p/9295370.html
Copyright © 2011-2022 走看看