解决粘包问题
程序员文章站
2022-09-14 10:34:44
解决粘包问题 在OSI七层模型中,我们可以看到,数据包从应用层产生,会在应用层生成一个头文件+数据的包传递给下一层,在下一层中它会认识这个包就是一个整体,然后会在这上面再重新添加一个包,直到物理层发送电信号,到了服务端一层层的解包,(https://www.processon.com/view/5b ......
解决粘包问题
在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()
上一篇: 用原生js-对ajax封装