• 解决粘包问题


    解决粘包问题

    在OSI七层模型中,我们可以看到,数据包从应用层产生,会在应用层生成一个头文件+数据的包传递给下一层,在下一层中它会认识这个包就是一个整体,然后会在这上面再重新添加一个包,直到物理层发送电信号,到了服务端一层层的解包,(https://www.processon.com/view/5b124aa3e4b068c2520f1292)

    那么如果我们想解决粘包的问题,首先最重要的就是要知道这个数据包到底有多大,那么应该怎么知道呢?

    struct模块

    struct模块是Python中内置模块,它用来在C语言中的结构体与Python中的字符串之间进行转换,数据一般来自网络或文件

    常用方法

    struct.pack(fmt,v1)
    返回的是一个字符串,是参数按照fmt数据格式组合而成
    
    struct.unpack(fmt.string)
    按照给定数据格式解开(通常都是由struct.pack进行打包)数据,返回值是一个tuple
    

    格式符

    具体请参考:(https://blog.csdn.net/djstavav/article/details/77950352)

    我们目前能用到的就是fmt=i,固定返回的是4个bytes,那么我们就可以和OSI七层模型一样,在还没有发送自己的真实数据之前,先告诉客户端我会发送多大的数据,也就是先发一个头部文件过去,然后客户端就开始进入循环,一直把这个数据接收完才可以,看以下代码

    简单版本

    # 服务端
    
    # 导入模块
    import socket
    import subprocess
    import struct
    
    # 创建socket对象,监听连接
    server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    server.bind(('127.0.0.1',8009))
    server.listen(5)
    
    while True:
        # 获取客户端连接对象以及IP地址
        conn,client_addr = server.accept()
        print('客户端地址:',client_addr)
        try:
            cmd = conn.recv(1024)  # 接收1024bytes
            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)  # 统计总大小
            
            # 制作固定长度的报头
            header = struct.pack('i',total_size)
            # 发送报头给客户端
            conn.send(header)
            # 发送真实的数据
            conn.send(stdout)
            conn.send(stderr)
        except ConnectionResetError as e:  # 如果客户端突然断开连接,则break
            break
        conn.close()  # 断开本次连接
    server.close()
    
    # 客户端
    import socket
    import struct
    client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    client.connect(('127.0.0.1',8009))  # 连接服务器地址
    
    while True:
        cmd = input('>>>').strip()
        if not cmd:continue
        client.send(cmd.encode('utf-8'))  # 以UTF-8的编码发送
    
        header = client.recv(4)  # 接收头部固定4个bytes
    
        total_size = struct.unpack('i',header)[0]  # 获取真实数据的长度
    
        recv_size = 0  # 接收的大小
        recv_data = b''  # 字符串的拼接
        while recv_size < total_size:  # 如果接收的大小<总长度
            res = client.recv(1024)  # 接收bytes
            recv_data += res  # 拼接
            recv_size += len(res)  # 获取当前已接收真实数据的长度
        print(recv_data.decode('GBK'))  # GBK解码
    client.close()  # 关闭本地连接
    

    终极版本

    # 服务端
    # 导入模块
    import socket
    import subprocess
    import json
    import struct
    
    # 创建对象、绑定、监听
    server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    server.bind(('127.0.0.1',8889))
    server.listen(5)
    print('starting......')
    
    # 与客户端建立连接
    while True:
        conn,addr = server.accept()
        print('客户端地址:',addr)
        while True:
            try:
                # 1.收命令
                cmd = conn.recv(1024)
                # 2.执行命令,得到命令的结果
                obj = subprocess.Popen(cmd.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
                stdout = obj.stdout.read()
                stderr = obj.stderr.read()
                # 3.把命令的结果返回给客户端
    
                # 3.1制作固定长度的报头
                header_dict = {
                    'filename':'a.txt',
                    'md5':'dd0a833f9512f1145d9f6869c42ec902',
                    'total_size':len(stdout)+len(stderr)
                }
                # print(len(header_dict))
                header_json = json.dumps(header_dict)  # 序列化文件
                header_bytes = header_json.encode('utf-8')  # 转换成为二进制字节码
                # print(header_bytes)
                # 3.2先发送报头的长度
                header = struct.pack('i',len(header_bytes))  # 将转换的字节码通过pack进行打包得到4个bytes
                print(len(header))
                conn.send(header)
                conn.send(header_bytes)
                print('已经发送文件')
                # 3.3再发送真实的数据
                conn.send(stdout)
                conn.send(stderr)
            except ConnectionResetError:
                break
        conn.close()
    server.close()
    
    # 客户端
    # 导入模块
    import socket
    import struct
    import json
    
    # 创建套接字对象、连接服务器端
    client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    client.connect(('127.0.0.1',8889))
    
    while True:
        # 1.发送命令
        cmd = input('>>>>>').strip()
        if not cmd:continue
        client.send(cmd.encode('utf-8'))
        # 2.拿到命令的结果并打印
        # 2.1先收报头的长度
        obj = client.recv(4)  # 接收4个bytes
        header_size = struct.unpack('i',obj)[0]  # 获取报头的长度,即header_dict的长度
        # 2.2再收报头
        header_bytes = client.recv(header_size)  # 将header_dict收到后进行转码,得到了json文件
        # 2.3从报头中解析出对真实数据的描述信息
        header_json = header_bytes.decode('utf-8')
        # print(len(header_json))
        print('client已经收到文件')
        header_dict = json.loads(header_json)  # 从内存中拿到json文件
        print(header_dict)
        total_size = header_dict['total_size']  # 获取真实数据的大小
        # 3.接收真实的数据
        recv_size = 0  # 已经接收的数据
        recv_data = b''  # 字符拼接
        while recv_size < total_size:
            res = client.recv(1024)
            recv_data += res
            recv_size += len(res)
        print(recv_data.decode('gbk'))
    client.close()
    

    FTP文件传输

    所谓的FTP文件传输,就是输入一条get 1.txt命令,然后自动从服务器端下载了1.txt文件,代码和上面的比较相似

    简单版本

    说明一点:本操作在linux系统上操作,windows端的pycharm连接本地的Linux服务器,在服务器内执行操作,so请注意文件路径
    
    # 服务端
    import socket
    import os
    import json
    import struct
    
    # 设置路径,以后要写到配置文件中的
    SHARE_DIR = r'/py_study/day21-粘包/文件传输test/server/share'
    
    server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    server.bind(('127.0.0.1',10000))
    server.listen(5)
    
    while True:
        conn,addr = server.accept()
        print('客户端地址:',addr)
        while True:
            try:
                # 1.收命令
                obj = conn.recv(1024)  # 收到b'get 1.mp4'
                if not obj:break
                # 2.解析命令,提取相应的命令参数
                cmd = obj.decode('utf-8').split()  # ['get','1.mp4']
                filename = cmd[1]  # 文件名
                # 3.以读的方式打开文件,读取内容发送给客户端
                # 3.1制作固定长度的报头
                header_dict = {
                    'filename':filename,
                    'md5':'xxxxxxxx',
                    'file_size':os.path.getsize(r'%s/%s'%(SHARE_DIR,filename))
                }
                header_json = json.dumps(header_dict)
                header_bytes = header_json.encode('utf-8')
                # 3.2发送报头的长度
                conn.send(struct.pack('i',len(header_bytes)))
                # 3.3再发送报头
                conn.send(header_bytes)
                # 4.发送真实的数据
                with open('%s/%s'%(SHARE_DIR,filename),'rb') as f:
                    for line in f:
                        conn.send(line)  # 逐行发送
            except ConnectionResetError:
                break
        conn.close()
    server.close()
    
    # 客户端
    import socket
    import struct
    import json
    
    DOWNLOAD_DIR = r'/py_study/day21-粘包/文件传输test/client/download'
    client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    client.connect(('127.0.0.1',10000))
    
    while True:
        # 1.发送命令
        cmd = input('>>>').strip()
        if not cmd:continue
        client.send(cmd.encode('utf-8'))
        # 2.以写的方式打开一个新文件,接收服务端发来的文件的内容写入客户端的新文件
        # 2.1先接收报头的长度
        obj = client.recv(4)
        header_size = struct.unpack('i',obj)[0]
        # 2.2再接收报头
        header_bytes = client.recv(header_size)
        # 3从报头中解析出对真实数据的描述信息
        header_json = header_bytes.decode('utf-8')
        header_dict = json.loads(header_json)
        print(header_dict)
        total_size = header_dict['file_size']
        filename = header_dict['filename']
        # 4.接收真实的数据
        with open('%s/%s'%(DOWNLOAD_DIR,filename),'wb') as f:
            recv_size = 0
            while recv_size < total_size:
                res = client.recv(1024)
                f.write(res)
                recv_size += len(res)
                print('总大小:%s        已下载大小:%s'%(total_size,recv_size))
    
    client.close()
    

    终极版本

    # 服务端
    import socket
    import os
    import json
    import struct
    
    # 设置路径,以后要写到配置文件中的
    SHARE_DIR = r'/py_study/day21-粘包/文件传输优化test/server/share'
    
    def get(cmd,conn):
        filename = cmd[1]  # 文件名
        # 3.以读的方式打开文件,读取内容发送给客户端
        # 3.1制作固定长度的报头
        header_dict = {
            'filename': filename,
            'md5': 'xxxxxxxx',
            'file_size': os.path.getsize(r'%s/%s' % (SHARE_DIR, filename))
        }
        header_json = json.dumps(header_dict)
        header_bytes = header_json.encode('utf-8')
        # 3.2发送报头的长度
        conn.send(struct.pack('i', len(header_bytes)))
        # 3.3再发送报头
        conn.send(header_bytes)
        # 4.发送真实的数据
        with open('%s/%s' % (SHARE_DIR, filename), 'rb') as f:
            for line in f:
                conn.send(line)
    
    def put():
        pass
    
    def run():
        server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server.bind(('127.0.0.1', 11115))
        server.listen(5)
    
        while True:
            conn,addr = server.accept()
            print('客户端地址:',addr)
            while True:
                try:
                    # 1.收命令
                    obj = conn.recv(1024)  # 收到b'get 1.mp4'
                    print('收到数据')
                    if not obj:break
                    # 2.解析命令,提取相应的命令参数
                    cmds = obj.decode('utf-8').split()  # ['get','1.mp4']
                    if cmds[0] == 'get':
                        get(cmds,conn)
                    elif cmds == 'put':
                        pass
    
                except ConnectionResetError:
                    break
            conn.close()
        server.close()
    
    if __name__ == '__main__':
        run()
        
    # 客户端
    import socket
    import struct
    import json
    
    DOWNLOAD_DIR = r'/py_study/day21-粘包/文件传输优化test/client/download'
    
    
    
    def get(client,cmds):
    
            # 2.以写的方式打开一个新文件,接收服务端发来的文件的内容写入客户端的新文件
            # 2.1先接收报头的长度
            print('进入get方法')
            obj = client.recv(4)
            print('发送数据成功')
            header_size = struct.unpack('i',obj)[0]
            print('解包完成')
            # 2.2再接收报头
            header_bytes = client.recv(header_size)
            print('再次接收报头成功')
            # 3从报头中解析出对真实数据的描述信息
            header_json = header_bytes.decode('utf-8')
            header_dict = json.loads(header_json)
            print(header_dict)
            total_size = header_dict['file_size']
            filename = header_dict['filename']
            # 4.接收真实的数据
            with open('%s/%s'%(DOWNLOAD_DIR,filename),'wb') as f:
                recv_size = 0
                while recv_size < total_size:
                    res = client.recv(1024)
                    f.write(res)
                    recv_size += len(res)
                    print('总大小:%s        已下载大小:%s'%(total_size,recv_size))
    
    def put():
        pass
    
    def run():
        client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        client.connect(('127.0.0.1',11115))
        while True:
            # 1.发送命令
            cmd = input('>>>').strip()
            if not cmd:continue
            client.send(cmd.encode('utf-8'))
    
            cmds = cmd.split()
            if cmds[0] == 'get':
                print('get方法')
                get(client,cmds)
                print('get方法执行完成')
            elif cmds[0] == 'put':
                pass
        client.close()
    if __name__ == '__main__':
        run()
    
  • 相关阅读:
    算法竞赛入门经典训练指南
    git保护--git分支创建
    解决多个iframe嵌套而造成的多个滚动条问题
    css如何让div元素铺满整个屏幕
    第一个用python3写的爬虫小例子
    用JS获取当前页面的URL以及截取其中的字段
    css处理超出文本截断问题的两种情况(多行或者单行)
    约数的个数
    成绩排序
    八进制
  • 原文地址:https://www.cnblogs.com/xiaoyafei/p/9149462.html
Copyright © 2020-2023  润新知