网络编程之FTP云盘程序
#ftp_server.py import socket,json,hashlib,struct,os,socketserver #md5加密类 class getMd5(object): # 普通加密用于账户密码加密方法 @classmethod def makeMd5(cls,msg): md5=hashlib.md5(b'salt!@#$') md5.update(msg.encode('utf-8')) return md5.hexdigest() # 图片加密用于数据校验方法 @classmethod def noEncodeMd5(cls,msg): md5 = hashlib.md5(b'salt!@#$') md5.update(msg) return md5.hexdigest() #数据校验类 class DataVer(object): # 接收数据校验方法,guanDao信息管道,RecvData接收的数据 @classmethod def RecvDataVer(cls,guanDao,recvData): print('开始MD5校验...') md5_data_len = struct.unpack('i', guanDao.recv(4))[0] # 解包 md5_data = guanDao.recv(md5_data_len).decode('gbk') # 解码得到客户端加密数据字符串 if md5_data == getMd5.noEncodeMd5(recvData): # 对比加密字符串 guanDao.send('ok'.encode('gbk')) print('数据校验成功,数据正确!!!!') else: print('数据校验失败,数据错误') # (断点续传)发送数据相同性校验方法 @classmethod def ddxcRecvDataVer(cls, guanDao, file_path): client_md5DataInfoDicJsonStr_len = struct.unpack('i', guanDao.recv(4))[0]# 解包 client_md5DataInfoDic_JsonStr = guanDao.recv(client_md5DataInfoDicJsonStr_len).decode('gbk')# 解码得到客户端加密数据字符串 client_md5DataInfo_Dic = json.loads(client_md5DataInfoDic_JsonStr) # 得到客户端文件信息的字典 loaded_len=client_md5DataInfo_Dic['client_Data_len']#服务端要读取的长度 with open(file_path,'rb')as f: server_data=f.read(loaded_len)#读服务端数据 if getMd5.noEncodeMd5(server_data)==client_md5DataInfo_Dic['client_md5Data']:#对比服务端数据加密数据与客户端加密数据对比 guanDao.send(struct.pack('i',1)) print('数据校验成功,可以进行断点续传') return loaded_len else: guanDao.send(struct.pack('i',0)) print('数据校验失败,不可以进行断点续传') return 0 # 发送数据校验方法,guanDao信息管道,sendData发送的数据 @classmethod def SendDataVer(cls,guanDao,sendData): print('开始MD5数据校验...') MD5_data = getMd5.noEncodeMd5(sendData) # 加密数据字符串 guanDao.send(struct.pack('i', len(MD5_data))) # 打包 guanDao.send(MD5_data.encode('gbk')) # 编码发送加密数据字符串 if guanDao.recv(1024).decode('gbk') == 'ok': # 对比返回数据 print('数据校验成功,数据正确!!!!') else: print('数据校验失败,数据错误') # (断点续传)接收数据相同性校验方法 @classmethod def ddxcSendDataVer(cls, guanDao, sendData,data_fromServer_len):#sendData 数据 data_fromServer_len数据长度 MD5_data = getMd5.noEncodeMd5(sendData) # 加密数据字符串 #发送数据相关信息字典 sendData_info_dic={ 'MD5_data':MD5_data, 'data_fromServer_len':data_fromServer_len } sendData_infoDic_jsonstr=json.dumps(sendData_info_dic)#字典变成json字符串 guanDao.send(struct.pack('i', len(sendData_infoDic_jsonstr))) # 打包发送json字符串长度 guanDao.send(sendData_infoDic_jsonstr.encode('gbk')) # 编码发送json字符串长度 #程序流程类 class ProgramProcess(object): # 主页面方法 @classmethod def MainPage(cls,conn): gongNeng_lst = ['login', 'zhuce'] # 功能列表 while 1: print('接受执行哪个方法的指令。。。') fangshi=conn.recv(1024).decode('gbk') # print('字符串指令:',fangshi) conn.send('ok'.encode('gbk')) # print('发送了确认接收的指令ok') if fangshi.upper()=='Q': print('程序结束') break func=getattr(cls,gongNeng_lst[int(fangshi)-1])#反射 func(conn)#运行对应方法 # 注册方法 @classmethod def zhuce(cls,conn): while 1: print('等待接受数据。。。') from_client_name = conn.recv(1024).decode('utf-8') print('接收到的数据为:',from_client_name) if from_client_name.upper()=='Q': print('返回主界面') break with open('user_info', 'a+', encoding='utf-8')as f: f.seek(0)#把光标移到开头 for line in f: if from_client_name == line.strip().split('|')[0]: print('账号已存在,不允许注册') conn.send(json.dumps(0).encode('utf-8')) break else: print('允许注册') conn.send(json.dumps(1).encode('utf-8')) from_client_info=conn.recv(1024).decode('utf-8') name=from_client_info.split('|')[0]#获取到名字 neiCun=input('请给新用户分配内存空间M(空为默认值50M):') if neiCun=='': neiCun='50' f.write(from_client_info+'|'+neiCun+'\n')#把账号|密码写入文件 file_path=r'.\server_file' + '//' + name#生成账户路径 os.makedirs(file_path)#新建账户文件夹 print('注册成功!!!') conn.send(json.dumps(1).encode('utf-8')) # 登录方法 @classmethod def login(cls,conn): flag = 0 while not flag: print('等待接受数据。。。') from_client_usrInfo = conn.recv(1024).decode('gbk')#获取账号 if from_client_usrInfo.upper()=='Q': print('返回主界面') break with open('user_info', 'r', encoding='utf-8')as f: for line in f: if from_client_usrInfo in line.strip():#数据比对成功 print('数据比对成功') conn.send(json.dumps(1).encode('utf-8')) if conn.recv(1024).decode('gbk')=='ok':#收到为‘ok’ conn.send(line.strip().encode('gbk'))#把文件name|psw|neiCun数据发送过去 name,psw,neiCun=line.strip().split('|')#把此行数据分割放入对象 obj = Account(name, psw, neiCun)#实例化对象 obj.sendPage(conn)#进入第二界面 flag = 1 break else: print('数据比对失败') conn.send(json.dumps(0).encode('utf-8')) #账户及操作相关类 class Account(object): # 初始化方法 def __init__(self,name,psw,neiCun): self.name=name self.psw=psw self.neiCun=neiCun self.path=r'.\server_file'+'//'+name # 功能主页面方法 def sendPage(self,conn): gongNeng_lst = ['update', 'download','newdir','deldir' ] # 功能列表 while 1: print('接受执行哪个方法的指令。。。') num_str=conn.recv(1024).decode('gbk') # print('字符串指令:',fangshi) conn.send('ok'.encode('gbk')) # print('发送了确认接收的指令ok') if num_str.upper()=='Q': print('返回主界面') break if num_str=='5':#当字符串数字为5时 print('选择的功能:查看文件夹') Account.show(conn,self.path)#运行遍历文件夹方法 else: func=getattr(self,gongNeng_lst[int(num_str)-1])#反射 func(conn)#运行对应方法 # 上传文件方法 def update(self,conn): print('选择的功能:上传文件') while 1: updata_path=self.path+'//'#默认上传文件到自己的文件夹 client_fileinfo_len=struct.unpack('i',conn.recv(4))[0]#解包4字节json字典字符串长度 if client_fileinfo_len:#发过来的数据不是0 # print('file_info字典json字符串的长度:',client_fileinfo_len) client_fileinfo_dic=json.loads(conn.recv(client_fileinfo_len).decode('gbk'))#接收file_info字典 if client_fileinfo_dic['updataPath'] == '':#判断字典里的上传保存路径是否为空 updata_path=self.path#拼接上传文件的路径上层文件夹的路径 updata_path_new=self.path+'//'+client_fileinfo_dic['filename']#此路径是上传数据写文件的路径 else: updata_path=os.path.join(updata_path,client_fileinfo_dic['updataPath'])#拼接上传文件的路径上层文件夹的路径 updata_path_new=updata_path+'//'+client_fileinfo_dic['filename']#此路径是上传数据写文件的路径 if os.path.exists(updata_path):#判断上传文件保存路径是否存在 conn.send(struct.pack('i',1))#发送打包数据1 fromclient_wenjian_length = client_fileinfo_dic['filesize'] # 上传文件的大小 print('上传文件的字节长度:', fromclient_wenjian_length) usedNeiCun = Account.CalculateFileSize(self.path) # 计算已使用内存 usedNeiCun = usedNeiCun / (1024 * 1024) # 转换成M SurplusNeiCun = float(self.neiCun) - float(usedNeiCun) # 剩余内存 print('剩余内存:', SurplusNeiCun) fromclient_wenjian_len = fromclient_wenjian_length / (1024 * 1024) # 上传文件大小M if SurplusNeiCun > fromclient_wenjian_len: # 内存可接受这么大的文件 conn.send(struct.pack('i', 1)) # 内存可接受这么大数据向服务端发送打包1 if os.path.exists(updata_path_new):#判断文件路径updata_path_new是否存在相同的文件 conn.send(struct.pack('i',1)) #此路径文件已存在发送打包1数据 print('上传的文件已存在,请进行MD5数据校验看数据是否完整') self.duanDianXuChuan(conn,updata_path_new,fromclient_wenjian_length)#进入断点续传方法,把管道,与路径传过去 else:#不存在此文件路径时 conn.send(struct.pack('i',0)) #此路径文件不存在发送打包0数据 recv_data_len = 0 # 接收数据的长度 recv_data = b'' with open(updata_path_new, 'wb')as f: # 打开服务端接收文件的 while recv_data_len < fromclient_wenjian_length: # 当接收数据长度小于总数据长度时进入循环 data = conn.recv(1024) f.write(data) recv_data_len += len(data) recv_data += data per_cent = recv_data_len * 100 // fromclient_wenjian_length # 通过\r来实现同一行打印,每次打印都回到行首打印 print('\r' + '%d%% %s' % (per_cent, '>' * (per_cent // 5)), end='') else: conn.send(struct.pack('i', 1)) # 向客户端发送接收完毕的数据 print('\n' + '上传接收完毕,接收字节长度为:', recv_data_len) DataVer.RecvDataVer(conn, recv_data) # 调用接收数据校验方法 usedNeiCun = Account.CalculateFileSize(self.path) # 计算已使用内存 usedNeiCun = usedNeiCun / (1024 * 1024) # 转换成M conn.send(struct.pack('f', usedNeiCun)) # 返回已使用内存 else: conn.send(struct.pack('i', 0)) # 内存不足发送0 conn.send(struct.pack('f', SurplusNeiCun)) print('内存不足') else: conn.send(struct.pack('i',0)) print('上传文件保存路径不存在') else: print('退出上传功能') break # 下载文件方法 def download(self,conn): print('选择的功能:下载文件') while 1: file_path_new=conn.recv(1024).decode('gbk')#接受要下载的文件名称 if file_path_new.upper()=='Q': print('退出下载功能') break file_path=self.path+'//'+file_path_new#默认服务器给客户端发送的文件路径 if os.path.exists(file_path):#判断文件路径是否存在 conn.send(struct.pack('i',1))#路径存在发送四字节1 file_info={ 'filename':os.path.basename(file_path_new), 'filepath':file_path, 'filesize':os.path.getsize(file_path) }#文件信息组成的字典 print('文件字节长度:', file_info['filesize']) conn.send(struct.pack('i',len(json.dumps(file_info))))#发送file_info字典序列化成字符串的长度 conn.send(json.dumps(file_info).encode('gbk'))#发送file_info字典序列化后的编码 if struct.unpack('i',conn.recv(4))[0]:#判断客户端是否存在要下载的文件 print('客户端已存在此文件,下面进行数据校验') self.downLoadDuanDianXuChuan(conn,file_path)#调用断点续传方法 else: send_data_len = 0#发送数据长度 send_data=b''#发送的数据 with open(file_path, 'rb')as f: while send_data_len<file_info['filesize']: data=f.read(1024) conn.send(data) send_data_len+=len(data) send_data+=data per_cent = send_data_len*100 // file_info['filesize'] # 通过\r来实现同一行打印,每次打印都回到行首打印 print('\r' + '%d%% %s' % (per_cent, '>' * (per_cent // 5)), end='') else: if conn.recv(1024).decode('utf-8')=='ok': print('\n'+'下载数据发送完毕,发送字节长度为:',send_data_len) DataVer.SendDataVer(conn,send_data)#调用发送数据校验方法 else: conn.send(struct.pack('i',0))#文件路径不存在发送4字节数值0 print('文件路径不存在') # 新建文件夹方法 def newdir(self,conn): print('选择的功能:新建文件夹') while 1: path=conn.recv(1024).decode('gbk') if path.upper()=='Q': print('退出新建文件功能') break new_path=self.path+'//'+path#拼接新建文件夹的路径 new_name = os.path.basename(path) # 获取新建文件夹的名字 new_path_dir=new_path.replace('//'+new_name,'')#获取上层文件夹路径 if os.path.exists(new_path_dir):#判断上层文件件路径是否存在 conn.send(struct.pack('i',1)) os.makedirs(new_path)#新建文件夹 else: conn.send(struct.pack('i',0)) print('路径不存在') # 删除文件夹方法 def deldir(self,conn): print('选择的功能:删除文件夹') while 1: path = conn.recv(1024).decode('gbk') if path.upper()=='Q': print('退出删除文件功能') break new_path = self.path + '//' + path # 拼接删除文件夹的路径 new_name = os.path.basename(path) # 获取删除文件夹的名字 new_path_dir = new_path.replace('//' + new_name, '') # 获取上层文件夹路径 if os.path.exists(new_path_dir): # 判断上层文件件路径是否存在 conn.send(struct.pack('i', 1)) os.removedirs(new_path) # 删除文件夹 else: conn.send(struct.pack('i', 0)) print('路径不存在') #上传的断点续传的方法 def duanDianXuChuan(self,conn,updata_path_new,fromclient_wenjian_length): #updata_path_new 文件上传到服务端的保存路径 #fromclient_wenjian_length 上传文件的总长度 with open(updata_path_new,'rb')as f:#打开服务端文件 data_from_server=f.read()#服务端数据 data_fromServer_len=len(data_from_server)#服务端数据的长度 DataVer.ddxcSendDataVer(conn,data_from_server,data_fromServer_len)#调用数据校验方法 if struct.unpack('i', conn.recv(4))[0]: print('可以进行断点续传') with open(updata_path_new,'ab+')as f: while data_fromServer_len<fromclient_wenjian_length: data=conn.recv(1024) f.write(data)#继续上次数据上传 data_fromServer_len+=len(data) percent=data_fromServer_len*100//fromclient_wenjian_length print('\r %d%% %s '%(percent,'>'*(percent//5)),end='') else: print('\n断点续传完成') f.seek(0) datas=f.read() from_clientdata_md5str=conn.recv(len(getMd5.noEncodeMd5(datas))).decode('gbk') if from_clientdata_md5str==getMd5.noEncodeMd5(datas): conn.send(struct.pack('i',1)) print('md5数据校验成功!!!') else: conn.send(struct.pack('i',0)) print('MD5数据校验失败!!!') else: print('数据不一致不能进行断点续传') #下载的断点续传方法 def downLoadDuanDianXuChuan(self,conn,file_path): loaded_len=DataVer.ddxcRecvDataVer(conn,file_path)#调用数据校验方法 if loaded_len:#进入断点续传的条件 server_data_len=os.path.getsize(file_path)#服务端数据字节长度 with open(file_path,'rb')as f: f.seek(loaded_len)#光标挪到读到的位置 while loaded_len < server_data_len: data=f.read(1024) conn.send(data)#发送断点数据 loaded_len+=len(data) percent=(loaded_len*100)//server_data_len print('\r %d%% %s'%(percent,'>'*(percent//5)),end='') else: print('\n完成了断点续传') f.seek(0) datas=f.read() conn.send(getMd5.noEncodeMd5(datas).encode('gbk')) if struct.unpack('i',conn.recv(4))[0]: print('MD5数据校验成功!!') else: print('MD5数据校验失败!!') # 查看文件夹方法 @staticmethod def show(conn,file_path): nameList = os.listdir(file_path) # 获取账号根目录下的name列表 conn.send(json.dumps(nameList).encode('gbk')) # json转字符串编码发送 while 1: name=conn.recv(1024).decode('gbk')#接收客户端发来的name if name.upper()=='Q': print('返回上层文件夹') break new_path=file_path+'//'+name#拼接新路径 if os.path.isdir(new_path):#判断路径是否为文件夹 conn.send(struct.pack('i',1))#是文件夹发送打包的1 Account.show(conn,new_path)#进入递归 else: conn.send(struct.pack('i',0))#如果不是文件夹发送给客户端打包0 # 计算文件夹大小的方法 @staticmethod def CalculateFileSize(path): usedNeiCun=0 list_name=os.listdir(path)#获取路径下的文件和文件夹名字的列表 for name in list_name: path_abs=os.path.join(path,name)#拼接路径 if os.path.isfile(path_abs):#如果是文件 usedNeiCun+=os.path.getsize(path_abs)#计算文件大小 elif os.path.isdir(path_abs):#如果是文件夹 size=Account.CalculateFileSize(path_abs)#接受内层计算的值 usedNeiCun+=size return usedNeiCun #返回已经使用的内存大小 #多线程通信类 class myServer(socketserver.BaseRequestHandler): #获取信息管道方法 def handle(self): print('连接成功!!!') conn=self.request ProgramProcess.MainPage(conn) print('断开连接,等待下次连接>>>>>>>>>>>>>') conn.close() #程序入口 if __name__ == '__main__': socketserver.TCPServer.allow_reuse_address = True#允许端口重用 server = socketserver.ThreadingTCPServer(('127.0.0.1', 8001), myServer) server.serve_forever()
#ftp_client.py import socket,json,hashlib,struct,os #md5加密类 class getMd5(object): # 普通加密用于账户密码加密方法 @classmethod def makeMd5(cls,msg): md5=hashlib.md5(b'salt!@#$') md5.update(msg.encode('utf-8')) return md5.hexdigest() # 图片加密用于数据校验方法 @classmethod def noEncodeMd5(cls,msg): md5 = hashlib.md5(b'salt!@#$') md5.update(msg) return md5.hexdigest() #数据校验类 class DataVer(object): # 接收数据校验方法,guanDao信息管道,RecvData接收的数据 @classmethod def RecvDataVer(cls,guanDao,recvData): print('开始MD5校验...') md5_data_len = struct.unpack('i', guanDao.recv(4))[0] # 解包 md5_data = guanDao.recv(md5_data_len).decode('gbk') # 解码得到客户端加密数据字符串 if md5_data == getMd5.noEncodeMd5(recvData): # 对比加密字符串 guanDao.send('ok'.encode('gbk')) print('数据校验成功,数据正确!!!!') else: print('数据校验失败,数据错误') # (断点续传)发送数据相同性校验方法 @classmethod def ddxcRecvDataVer(cls, guanDao, file_path):#_file_path 本地上传文件路径 sendData_jsonstr_len = struct.unpack('i', guanDao.recv(4))[0] # 解包 sendData_infoDic_jsonstr = guanDao.recv(sendData_jsonstr_len).decode('gbk') # 解码得到客户端json字符串 sendData_info_dic=json.loads(sendData_infoDic_jsonstr)#发送数据相关信息字典 read_data_len=sendData_info_dic['data_fromServer_len']#读文件的长度 with open(file_path,'rb')as f: client_read_data=f.read(read_data_len) #read_data_len读文件的长度 #client_read_data 读取的数据 if getMd5.noEncodeMd5(client_read_data)==sendData_info_dic['MD5_data']:#数据相同可以断点续传 guanDao.send(struct.pack('i',1))#发送打包数据1 print('数据相同可以断点续传') return read_data_len else: guanDao.send(struct.pack('i', 0)) # 发送打包数据0 print('同一路径下数据不同,无法进行断点续传') return 0 # 发送数据校验方法,guanDao信息管道,sendData发送的数据 @classmethod def SendDataVer(cls, guanDao, sendData): print('开始MD5数据校验...') MD5_data = getMd5.noEncodeMd5(sendData) # 加密数据字符串 guanDao.send(struct.pack('i', len(MD5_data))) # 打包 guanDao.send(MD5_data.encode('gbk')) # 编码发送加密数据字符串 if guanDao.recv(1024).decode('gbk') == 'ok': # 对比返回数据 print('数据校验成功,数据正确!!!!') else: print('数据校验失败,数据错误') # (断点续传)接收数据相同性校验方法 @classmethod def ddxcSendDataVer(cls, guanDao,abs_download_path): with open(abs_download_path,'rb')as f: client_Data=f.read()#读出服务端数据 client_md5Data=getMd5.noEncodeMd5(client_Data)#md5加密得到加密数据字符串 client_md5DataInfo_Dic={ 'client_md5Data':client_md5Data, 'client_Data_len':len(client_Data) } client_md5DataInfoDic_JsonStr=json.dumps(client_md5DataInfo_Dic)#json字符串 guanDao.send(struct.pack('i', len(client_md5DataInfoDic_JsonStr))) # 打包 guanDao.send(client_md5DataInfoDic_JsonStr.encode('gbk')) # 编码发送加密数据字符串 if guanDao.recv(4)[0]: print('数据校验成功,可以进行断点续传') return len(client_Data)#已经下载的数据长度 else: print('数据校验失败,不可以进行断点续传') return 0 #程序流程类 class ProgramProcess(object): # 程序入口的方法 @classmethod def MainPage(cls): client = sendMSG.buid_connct() # 调用生成client对象的方法 lst = ['login', 'zhuce'] while 1: print(''' 欢迎进入FTP文件系统 **************************** 1.登录 2.注册 **************************** ''') num = input('请输入要执行的序号(按Q退出):') if num=='1'or num=='2'or num.upper()=='Q':#判断输入合法 client.send(num.encode('gbk')) if num.upper() == 'Q': print('程序退出>>>>>>>') break if client.recv(1024).decode('gbk') == 'ok': func = getattr(cls, lst[int(num) - 1]) func(client) else: print('输入有误') client.close() # 注册方法 @classmethod def zhuce(cls, client): while 1: name = input('请输入注册的账号(按q退出):') client.send(name.encode('utf-8')) if name.upper() == 'Q': break if json.loads(client.recv(1024).decode('utf-8')): psw = getMd5.makeMd5(input('请输入密码:')) print('等待服务端为您分配磁盘内存空间...') if sendMSG.senD(client, name, psw): print('注册成功!!!') else: print('账号已存在') # 登陆方法 @classmethod def login(cls, client): while 1: name = input('请输入账号(Q退出):') if name.upper() == 'Q': client.send(name.encode('gbk')) break psw = getMd5.makeMd5(input('请输入密码:')) if sendMSG.senD(client, name, psw): client.send('ok'.encode('gbk')) print('登陆成功!!!!') account_info=client.recv(1024).decode('gbk')#接受name|psw|neiCun数据 name,psw,neiCun=account_info.split('|')#切割解构 obj=Acount(name,psw,neiCun)#实例化对象 obj.sendPage(client)#调用实例方法 break else: print('账号或密码错误') #账户相关类 class Acount(object): # 初始化方法 def __init__(self,name,psw,neiCun): self.name=name self.psw=psw self.neiCun=neiCun # 功能界面方法 def sendPage(self,client): while 1: print(''' FTP系统主界面 **************************** 1.上传文件 2.下载文件 3.新建文件夹 4.删除文件夹 5.查看文件夹 **************************** ''') run_lst=['update','download','newdir','deldir',] chioce_num=input('请输入要执行的数字(按Q退出):') if chioce_num=='1'or chioce_num=='2'or chioce_num=='3'or chioce_num=='4'or chioce_num=='5'or chioce_num.upper()=='Q':#判断输入合法性 client.send(chioce_num.encode('gbk'))#发送方法编号\ if client.recv(1024).decode('gbk')=='ok':#接收到服务端返回的🆗数据 if chioce_num.upper() == 'Q': print('返回上一层') break if chioce_num=='5':#如果输入的是'5' print('选择的功能:查看文件夹') Acount.show(client)#运行show静态方法 else: func=getattr(self,run_lst[int(chioce_num)-1])#反射 func(client)#运行对应方法 else: print('没有收到服务端返回数据') else: print('输入有误') # 上传文件方法 def update(self,client): print('选择的功能:上传文件') while 1: client_file_path=input('请输入上传文件的本地路径(Q退出):') if client_file_path.upper()=='Q': client.send(struct.pack('i',0))#发送打包数据0 print('退出文件上传功能') break if os.path.exists(client_file_path):#本地上传路径存在 flag=0 while not flag: updata_path=input('请输入保存到服务端的路径(层级用//隔开,为空是默认路径):') file_info={ 'filename':os.path.basename(client_file_path),#文件信息字典 'filepath':client_file_path, 'filesize':os.path.getsize(client_file_path), 'updataPath':updata_path }#file_info字典 print('上传文件的字节长度:',file_info['filesize']) file_info_jsonstr=json.dumps(file_info)#字典变成json字符串 fileInfo_jsonstrlen_stru=struct.pack('i',len(file_info_jsonstr))#打包字典json字符串长度 client.send(fileInfo_jsonstrlen_stru)#发送字典长度包 client.send(file_info_jsonstr.encode('gbk')) # 发送字典file_info if struct.unpack('i', client.recv(4))[0]: # 上传文件保存到服务端的路径存在 flag = 1 if struct.unpack('i',client.recv(4))[0]:#如果内存空间可以放下此文件大小 if struct.unpack('i',client.recv(4))[0]:#如果上传文件的路径下有与上传文件同名的文件 print('服务端路径下已有与上传文件同名文件,进行MD5比对中...') self.duanDianXuChuan(client,client_file_path)#进入断点续传方法 else:#如果上传文件的路径下没有与上传文件同名的文件 send_data_len=0#发送的数据长度 send_data=b'' with open(file_info['filepath'],'rb')as f: while send_data_len<file_info['filesize']:#当接收的数据长度小于总数据长度时 data=f.read(1024) client.send(data) send_data_len+=len(data) send_data+=data per_cent = send_data_len*100//file_info['filesize'] # 通过\r来实现同一行打印,每次打印都回到行首打印 print('\r' + '%d%% %s' % (per_cent,'>'*(per_cent//5)),end='') else: if struct.unpack('i',client.recv(4))[0]: print('\n'+'上传完毕,上传数据字节长度为',file_info['filesize']) DataVer.SendDataVer(client,send_data)#调用发送数据校验方法 usdNeiCun=struct.unpack('f',client.recv(4))[0] print('总内存: %s M'%self.neiCun) print('已使用内存为: %d M'% usdNeiCun) else: SurplusNeiCun=struct.unpack('f',client.recv(4))[0]#剩余空间 file_size=file_info['filesize']/(1024*1024)#上传文件大小M print('内存剩余空间:%sM '%SurplusNeiCun) print('文件大小: %sM'%file_size) print('内存空间不足!!!!!') else: print('文件保存到服务端的路径不存在!!!') else: print('本地上传文件路径不存在!!!!') # 下载文件方法 def download(self,client): print('选择的功能:下载文件') while 1: file_path=input('请输入要下载文件的路径(层级用//隔开,Q退出):').replace(' ','') client.send(file_path.encode('gbk'))#发送要下载的文件名 if file_path.upper()=='Q': print('退出下载功能') break if struct.unpack('i',client.recv(4))[0]:#如果文件名存在 download_path=r'.\client_file'+'//' # 默认下载路径 print('等待接收服务端发送的文件...') server_fileinfojsonstr_len = struct.unpack('i',client.recv(4))[0]#拆包4字节file_info字典的json字符串长度 # print('file_info字典的json字符串长度:', server_fileinfojsonstr_len) server_fileinfo_jsonstr=client.recv(server_fileinfojsonstr_len).decode('gbk')#接收file_info字典的json字符串 file_info_dic=json.loads(server_fileinfo_jsonstr)#json反序列化变成file_info字典 fromclient_wenjian_length=file_info_dic['filesize']#获取文件长度 print('下载文件的字节长度:',fromclient_wenjian_length) abs_download_path=os.path.join(download_path,file_info_dic['filename'])#下载的全路径 if os.path.exists(abs_download_path):#下载到服务端的路径存在 client.send(struct.pack('i',1))#服务端存在要下载的文件 print('客户端已存在此文件,下面进行数据校验') self.downLoadDuanDianXuChuan(client,abs_download_path,fromclient_wenjian_length)#调用断点续传方法 else: client.send(struct.pack('i',0))#服务端不存在要下载的文件 recv_data_len=0#接收的文件字节长 recv_data=b'' with open(abs_download_path, 'wb')as f: while recv_data_len < fromclient_wenjian_length:#当接受的字节长度总和小于数据总长度时进入循环 data = client.recv(1024) f.write(data)#写入文件 recv_data_len += len(data) recv_data+=data # 进度条百分比前的数字X(X%) per_cent = recv_data_len*100 // fromclient_wenjian_length # 通过\r来实现同一行打印,每次打印都回到行首打印 print('\r' + '%d%% %s' % (per_cent, '>' * (per_cent // 5)), end='') else: client.send('ok'.encode('utf-8'))#发送接收完毕信号 print('\n'+'下载完毕,接收数据字节长度为:',recv_data_len) DataVer.RecvDataVer(client,recv_data)#调用接收数据校验方法 else: print('客户端不存在此文件') # 遍历文件夹方法 @staticmethod def show(client): print('本层文件夹目录为:') nameList = json.loads(client.recv(1024).decode('gbk')) while 1: for name in nameList: print(name) new_name=input('请选择要继续查看的文件夹名(Q退出):') if new_name.upper() =='Q': client.send(new_name.encode('gbk')) # 把q发送给服务端 print('返回上层目录') break if new_name in nameList:#输入的名字在列表中 client.send(new_name.encode('gbk'))#发送new_name if struct.unpack('i',client.recv(4))[0]:#判断是不是文件夹 Acount.show(client)#进入递归 else: print('此名字不是文件夹') else: print('本层目录不存在此文件夹名') # 新建文件夹方法 def newdir(self,client): print('选择的功能:新建文件夹') while 1: path=input('请输入新建夹路径(层级用//隔开),不熟悉请先选择查看文件夹方法(按q退出):') client.send(path.encode('gbk')) if path.upper()=='Q': print('退出新建文件功能') break if struct.unpack('i',client.recv(4))[0]: print('新建文件夹成功!!') else: print('文件路径错误') # 删除文件夹方法 def deldir(self,client): print('选择的功能:删除文件夹') while 1: path=input('请输入删除夹路径(层级用//隔开),不熟悉请先选择查看文件夹方法(按q退出):') client.send(path.encode('gbk')) if path.upper()=='Q': print('退出删除文件功能') break if struct.unpack('i',client.recv(4))[0]: print('删除文件夹成功!!!') else: print('文件路径错误') #上传断点续传方法 def duanDianXuChuan(self,cilent,client_file_path): readed_data_len=DataVer.ddxcRecvDataVer(cilent,client_file_path)#文件从此位置开始读 if readed_data_len:#是否进行断点续传校验方法 print('服务端已经存在数据大小:',readed_data_len) data_len=os.path.getsize(client_file_path)#文件总长度 with open(client_file_path,'rb')as f: f.seek(readed_data_len)#把光标放到断点位置 while readed_data_len <data_len:#已读长度小于总长度 data=f.read(1024) readed_data_len+=len(data) cilent.send(data)#发送断点数据 percent = readed_data_len * 100 // data_len print('\r %d%% %s ' % (percent, '>' * (percent // 5)), end='') else: print('\n 断点续传完成') f.seek(0) datas=f.read() cilent.send(getMd5.noEncodeMd5(datas).encode('gbk')) if struct.unpack('i',cilent.recv(4))[0]: print('md5数据校验成功!!') else: print('MD5数据校验失败') #下载断点续传方法 def downLoadDuanDianXuChuan(self,client,abs_download_path,fromclient_wenjian_length): loaded_len=DataVer.ddxcSendDataVer(client,abs_download_path)#调用数据校验方法 if loaded_len:#进入断点续传的条件 with open(abs_download_path,'ab+')as f: while loaded_len < fromclient_wenjian_length: data=client.recv(1024) f.write(data) loaded_len+=len(data) percent=(loaded_len*100)//fromclient_wenjian_length print('\r %d%% %s'%(percent,'>'*(percent//5)),end='') else: print('\n断点续传完成') f.seek(0) datas=f.read() fromserver_md5_str=client.recv(len(getMd5.noEncodeMd5(datas))).decode('gbk') if fromserver_md5_str==getMd5.noEncodeMd5(datas): client.send(struct.pack('i',1)) print('md5数据校验成功!!') else: client.send(struct.pack('i',0)) print('md5数据校验失败!!') #发送信息相关类 class sendMSG(object): # 建立连接方法,返回client对象 @classmethod def buid_connct(cls): client = socket.socket() client.connect(('127.0.0.1', 8001)) return client # 发送数据user_info方法,返回账号密码是否正确(1/0) @classmethod def senD(cls,client,name,psw): user_info = name + '|' + psw # print('数据传输格式:',user_info) client.send(user_info.encode('utf-8')) from_server_res=json.loads(client.recv(1024).decode('utf-8')) return from_server_res #程序入口 if __name__=='__main__': ProgramProcess.MainPage()