解决粘包问题
发布日期:2021-05-14 21:58:28 浏览次数:14 分类:博客文章

本文共 9578 字,大约阅读时间需要 31 分钟。

解决粘包问题

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

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

struct模块

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

常用方法

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

格式符

具体请参考:()

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

简单版本

# 服务端# 导入模块import socketimport subprocessimport 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 socketimport structclient = 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:  # 如果接收的大小

终极版本

# 服务端# 导入模块import socketimport subprocessimport jsonimport 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 socketimport structimport 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 socketimport osimport jsonimport 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 socketimport structimport jsonDOWNLOAD_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 socketimport osimport jsonimport 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():    passdef 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 socketimport structimport jsonDOWNLOAD_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():    passdef 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()
上一篇:linux常用命令
下一篇:Python网络编程

发表评论

最新留言

不错!
[***.144.177.141]2025年04月26日 19时31分49秒