网络编程之FTP云盘程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 | #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() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 | #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() |
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步