网络编程——实现一个简单的FTP断点续传相关功能
一、实现一个socket 服务端 ,通过客户端上传本地文件到服务端指定目录
服务端代码:
import socketserver,os,json import shutil #用于更改文件名 class ftpServer(socketserver.BaseRequestHandler): def handle(self): ''' self.request是客户端的套接字对象 socket 接字对象处理数据相关逻辑 :return: ''' # 接收命令 cmd_str = self.request.recv(1024).decode() cmd = json.loads(cmd_str) self.excute_cmd(cmd) # 写入数据到服务端磁盘 def write_file(self,message,exist_size,total_size,server_filemd5_path,server_filename_path,mode): response = {'msg': message,'size':exist_size} self.request.sendall(json.dumps(response).encode()) f = open(server_filemd5_path,mode) while exist_size < total_size: data = self.request.recv(1024) f.write(data) # 此处写到内存 f.flush() # 这里才是写道磁盘 exist_size += len(data) # while 循环完成后 可以在此处做合法校验 # 校验本地保存文件的md5是否和客户端 事先上传的md5 值相等 f.close() print('写入成功') # 保存完文件后将md5的文件名改为客户端上传文件的原文件名 if not os.path.exists(server_filename_path): shutil.move(server_filemd5_path, server_filename_path) print('上传完成') else: pass # 保留,后续增加 文件已存在 在文件名最后追加1,比如文件名1(1).txt # 获取客户端前置信息并做对应数据处理 def excute_cmd(self,cmd): # 获取上传文件的md5 file_md5 = cmd['file_md5'] # 获取上传文件的原文件名 file_name = cmd['file_name'] # 事先获取即将要上传的文件的大小 upload_file_size = cmd['size'] # 定义md5命名的文件在服务端的保存位置,服务端路径+客户端文件的md5值作为文件名 server_filemd5_path = os.path.join(os.getcwd(), 'source', file_md5) # 定义非md5命名的文件在服务端的保存位置,服务端路径+客户端原文件名 server_filename__path = os.path.join(os.getcwd(), 'source', file_name) # 判断文件路径是否存在 file_exist = os.path.exists(server_filemd5_path) # 统一定义服务端返回值格式 # response = {'code': '100x','size':xxxx} size 可选 # 接收到客户端请求下载后,判断其文件是否存在,然后通知客户端开始上传 # 然后开始接收客户端上传的文件 if not file_exist: msg = '开始完整上传' mode = 'wb' done_size =0 self.write_file(msg,done_size,upload_file_size,server_filemd5_path,server_filename__path,mode) else: msg='开始断点续传' # 获取在服务端已经保存的文件的大小 done_size = os.stat(server_filemd5_path).st_size print(done_size) mode = 'ab' self.write_file(msg,done_size,upload_file_size,server_filemd5_path,server_filename__path,mode) if __name__ == '__main__': ftp = socketserver.ThreadingTCPServer(('192.168.0.105.',8013),ftpServer) ftp.serve_forever()
客户端代码:
import socket,json,hashlib,os # 文件加密方法 def file_add_md5(filepath): m = hashlib.md5() # 这里传入的是文件的完整路径,打开文件后 循环每一行数据进行加密 with open(filepath,'rb') as f: for line in f: m.update(line) # return m.hexdigest() #返回字符串类型的十六进制数据 # 展示进度条相关 def display_upload(currsize,totla_size): val = (currsize*100)//totla_size print('\r上传进度%s%%|%s'%(val,'>'*(val//2)),end='') # 执行命令并获取本地文件对应信息 def excute_cmd(file_path): file_md5 = file_add_md5(file_path) # os传入完整路径 获取文件名 file_name = os.path.basename(file_path) file_size = os.stat(file_path).st_size cmd_dict = {'cmd': 'upload', 'file_name': file_name, 'size': file_size, 'file_md5': file_md5} # dict 转json字符串,再转byte类型 cmd_dict_byte = json.dumps(cmd_dict).encode() # 发送数据 client.sendall(cmd_dict_byte) # 客户端等待服务端的响应: upload_type_byte = client.recv(1024).decode() # 将json字符串转换字典 resp = json.loads(upload_type_byte) # code = resp['code'] # exist_size = resp['size'] upload(resp,file_path,file_size) # 上传文件的逻辑 def upload(resp,file_path,file_size): # # # 开始上传文件 方法1 ,通过逐行遍历文件,然后发送 # with open(file_path,'rb') as f: # for line in f: # client.sendall(line) # 开始上传文件 方法2 ,循环read 固定长度数据,然后发送这部分数据 exist_size = resp['size'] f = open(file_path,'rb') # 跳转至指定位置进行读取 f.seek(exist_size) print(resp['msg']+file_path) while exist_size<file_size: data = f.read(1024) client.sendall(data) exist_size+=len(data) display_upload(exist_size,file_size) print('upload success') f.close() if __name__ == '__main__': while True: # 创建一个客户端的 对象 client = socket.socket() # 客户端对象向服务端 发起连接 client.connect(('192.168.0.105', 8013)) cmd = input('请输入要上传的文件:') pic = {'1':'1.png','2':'2.mp4'} file_path = pic.get(cmd) excute_cmd(file_path)