(转)python高级FTP
原文地址:http://www.itnose.net/detail/6754889.html
高级FTP服务器
1. 用户加密认证
2. 多用户同时登陆
3. 每个用户有自己的家目录且只能访问自己的家目录
4. 对用户进行磁盘配额、不同用户配额可不同
5. 用户可以登陆server后,可切换目录
6. 查看当前目录下文件
7. 上传下载文件,保证文件一致性
8. 传输过程中现实进度条
9.支持断点续传
10.用户操作日志
服务端 启动参数 start
客户端 启动参数 -s localhost -P 9500
程序结构:
seniorFTP/#综合目录
|- - -ftp_client/#客户端程序目录
| |- - -__init__.py
| |- - -bin/#启动目录
| | |- - -__init__.py
| | |- - -client_ftp.py#客户端视图启动
| |
| |- - -cfg/#配置目录
| | |- - -__init__.py
| | |- - -config.py#配置文件
| |
| |- - -down/#下载文件目录
| |
| |- - -putfile/#上传文件目录
| |
| |
| |- - -REDMAE
|- - -ftp_server/#服务端程序目录
| |- - -__init__.py
| |- - -bin/#启动目录
| | |- - -__init__.py
| | |- - -start.py#服务端视图启动
| | |- - -user_reg.py#用户注册启动
| |
| |- - -cfg/#配置目录
| | |- - -__init__.py
| | |- - -config.py#配置文件
| | |- - -userpwd.cfg#用户信息文件
| |
| |- - -core/#文件目录
| | |- - -__init__.py
| | |- - -ftp_server.py#服务端主要逻辑 类
| | |- - -logs.py#日志主要逻辑 类
| | |- - -main.py#服务端启动主程序
| |
| |- - -home/#用户文件目录
| | |- - -用户/#个人目录
| |
| |- - -log/#日志文件目录
| |
| |- - -REDMAE
| |
|
|- - -REDMAE
先上流程图:
详细代码如下:
|- - -ftp_client/#客户端程序目录
| |- - -__init__.py
| |- - -bin/#启动目录
| | |- - -__init__.py
| | |- - -client_ftp.py#客户端视图启动
1 #!usr/bin/env python 2 #-*-coding:utf-8-*- 3 # Author calmyan 4 import socket,os,json,getpass,hashlib 5 import os ,sys,optparse 6 7 STATUS_CODE={ 8 240:'格式出错,格式:{"action":"get","filename":"filename","size":100}', 9 241:'指令错误', 10 242:'用户密码出错', 11 243:'用户或密码出错', 12 244:'用户密码通过校验', 13 } 14 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量 15 sys.path.append(BASE_DIR)#增加环境变量 16 from cfg import config 17 class FTPClient(object): 18 def __init__(self): 19 paresr=optparse.OptionParser() 20 paresr.add_option('-s','--server',dest='server',help='服务器地址') 21 paresr.add_option('-P','--port',type="int",dest='port',help='服务器端口') 22 paresr.add_option('-u','--username',dest='username',help='用户名') 23 paresr.add_option('-p','--password',dest='password',help='密码') 24 (self.options,self.args)=paresr.parse_args()#返回一个字典与列表的元组 25 self.verify_args(self.options,self.args)#判断参数 26 self.ser_connect()#连接服务端 27 self.cmd_list=config.CMD_LIST 28 self.rat=0#文件断点 29 30 #实例化一个连接端 31 def ser_connect(self): 32 self.c=socket.socket()#实例化一个连接端 33 self.c.connect((self.options.server,self.options.port))#进行连接 34 35 #判断用户与密码是否成对出现 36 def verify_args(self,options,args): 37 if (options.username is None and options.password is None) or (options.username is not None and options.password is not None):#判断用户与密码是否成对出现 38 pass##判断用户与密码单个出现 39 else: 40 exit('出错:请输入用户与密码!')#退出 41 if options.server and options.port:#端口判断 42 if options.port>0 and options.port<65535: 43 return True 44 else: 45 print('端口号:[%s]错误,端口范围:0-65535'%options.port) 46 47 #登陆方法 48 def landing(self):#登陆方法 49 '''用户验证''' 50 if self.options.username is not None:#判断用户名已经输入 51 #print(self.options.username,self.options.password) 52 return self.get_user_pwd(self.options.username,self.options.password)#返回结果 53 else: 54 print('用户登陆'.center(60,'=')) 55 ret_count=0#验证次数 56 while ret_count<5: 57 username=input('用户名:').strip() 58 password=getpass.getpass('密码:').strip() 59 if self.get_user_pwd(username,password): 60 return self.get_user_pwd(username,password)#调用远程验证用户 返回结果 61 else: 62 ret_count+=1#次数加一 63 print('认证出错次数[%s]'%ret_count) 64 else: 65 print('密码出错次数过多!') 66 exit() 67 68 #'''用户名与密码检验''' 69 def get_user_pwd(self,username,password): 70 '''用户名与密码检验''' 71 #发送 头文件 72 data={ 73 'action':'auth', 74 'username':username, 75 'password':password 76 } 77 self.c.send(json.dumps(data).encode())#发送到服务器 78 response = self.get_response()#得到服务端的回复 79 if response.get('status_code') == 244: 80 print(STATUS_CODE[244]) 81 self.user = username#存下用户名 82 self.user_dir=response.get('dir')#目录 83 return True 84 else: 85 print(response.get("status_msg") ) 86 87 #服务器回复 88 def get_response(self):#服务器回复 89 '''服务器回复信息''' 90 data=self.c.recv(1024)#接收回复 91 data = json.loads(data.decode()) 92 return data 93 94 #指令帮助 95 def help(self):#指令帮助 96 attr=''' 97 help 指令帮助 98 ---------------------------------- 99 info 个人信息 100 ---------------------------------- 101 ls 查看当前目录(linux/windows) 102 ---------------------------------- 103 pwd 查看当前路径(linux/windows) 104 ---------------------------------- 105 cd 目录 切换目录(linux/windows) 106 ---------------------------------- 107 get filename 下载文件 108 ---------------------------------- 109 put filename 上传文件 110 ---------------------------------- 111 --md5 使用md5 在get/put 后 112 ---------------------------------- 113 mkdir name 创建目录(linux/windows) 114 ---------------------------------- 115 rmdir name 删除目录(linux/windows) 116 ---------------------------------- 117 rm filename 删除文件 (linux/windows) 118 ---------------------------------- 119 exit 退出 120 ---------------------------------- 121 '''.format() 122 print(attr) 123 124 ##交互 125 def inter(self):#交互 126 if self.landing():#通过用户密码认证 127 print('指令界面'.center(60,'=')) 128 self.help() 129 while True: 130 cmd = input('[%s]-->指令>>>:'%self.user_dir).strip() 131 if len(cmd)==0:continue#输入空跳过 132 if cmd=='exit':exit()#退出指令 133 cmd_str=cmd.split()#用空格分割 取命令到列表 134 #print(cmd_str) 135 #print(len(cmd_str)) 136 if len(cmd_str)==1 and cmd_str[0] in self.cmd_list:#如果是单个命令 并且在命令列表中 137 #if len(cmd_str)==1:#如果是单个命令 并且在命令列表中 138 if cmd_str[0]==config.HELP: 139 self.help() 140 continue 141 func=getattr(self,'cmd_compr')#调用此方法 142 ret=func(cmd_str) 143 if ret: 144 continue 145 else: 146 pass 147 elif len(cmd_str)>1: 148 if hasattr(self,'cmd_%s'%cmd_str[0]):#判断类中是否有此方法 149 func=getattr(self,'cmd_%s'%cmd_str[0])#调用此方法 150 func(cmd_str)#执行 151 continue 152 else: 153 print('指令出错!') 154 self.help()# 155 156 #'''是否要md5''' 157 def cmd_md5_(self,cmd_list): 158 '''是否要md5''' 159 if '--md5' in cmd_list: 160 return True 161 162 #进度条 163 def show_pr(self,total):#进度条 164 received_size = 0 #发送的大小 165 current_percent = 0 # 166 while received_size < total: 167 if int((received_size / total) * 100 ) > current_percent : 168 print("#",end="",flush=True)#进度显示 169 current_percent = int((received_size / total) * 100 ) 170 new_size = yield #断点跳转 传入的大小 171 received_size += new_size 172 173 #单个命令 174 def cmd_compr(self,cmd_str,**kwargs): 175 mag_dict={ 176 "action":"compr", 177 'actionname':cmd_str[0] 178 } 179 self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送数据 180 cmd_res_attr=self.get_response()#得到服务器的回复 181 if type(cmd_res_attr) is not int:#如果不int 类型 182 if cmd_res_attr["status_code"] ==241:#命令不对 183 print(cmd_res_attr['status_msg']) 184 return 185 if cmd_res_attr["status_code"] ==240:#命令不对 186 print(cmd_res_attr['status_msg']) 187 return 188 size_l=0#收数据当前大小 189 self.c.send('准备好接收了,可以发了'.encode('utf-8')) 190 receive_data= ''.encode() 191 while size_l< cmd_res_attr: 192 data=self.c.recv(1024)#开始接收数据 193 size_l+=len(data)#加上 194 receive_data += data 195 else: 196 receive_data=receive_data.decode() 197 try: 198 receive_data=eval(receive_data)#转为列表 或字典 199 except Exception as e: 200 pass 201 if type(receive_data) is dict:#如果是字典 202 for i in receive_data: 203 print(i,receive_data[i]) 204 return 1 205 if type(receive_data) is list:#如果是列表 206 for i in receive_data: 207 print(i) 208 return 1 209 print(receive_data) 210 return 1 211 212 #切换目录 213 def cmd_cd(self,cmd_list,**kwargs): 214 '''切换目录''' 215 mag_dict={ 216 "action":"cd", 217 'actionname':cmd_list[1] 218 } 219 self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送数据 220 msg_l=self.c.recv(1024)#接收数据 消息 221 data=json.loads(msg_l.decode()) 222 if data["status_code"] ==251:#目录不可切换 223 print(data['status_msg']) 224 return 225 elif data["status_code"] ==252:#目录可以换 226 print(data['status_msg']) 227 self.c.send(b'1')#发送到服务器,表示可以了 228 data=self.c.recv(1024) 229 print(data.decode()) 230 user_dir=data.decode() 231 print(user_dir) 232 self.user_dir=user_dir 233 return 234 elif data["status_code"] ==256:#目录不存在 235 print(data['status_msg']) 236 return 237 238 #删除文件 239 def cmd_rm(self,cmd_list,**kwargs): 240 mag_dict={ 241 "action":"rm", 242 'filename':cmd_list[1] 243 } 244 self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送文件信息 245 data=self.get_response()#得到服务器的回复 246 if data["status_code"] ==245:#文件不存在 247 print(data['status_msg']) 248 #print('删除前空间:',data['剩余空间']) 249 return 250 elif data["status_code"] ==254:#文件删除完成 251 print(data['status_msg']) 252 print('删除前空间:',data['剩余空间']) 253 pass 254 self.c.send(b'1')#发送到服务器,表示可以 255 data=self.get_response()#得到服务器的回复 256 if data["status_code"] ==255:#文件删除完成 257 print(data['status_msg']) 258 print('删除后空间:',data['剩余空间']) 259 return 260 261 #创建目录 262 def cmd_mkdir(self,cmd_list,**kwargs): 263 mag_dict={ 264 "action":"mkdir", 265 'filename':cmd_list[1] 266 } 267 self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送文件信息 268 data=self.get_response()#得到服务器的回复 269 if data["status_code"] ==257:#目录已经存在 270 print(data['status_msg']) 271 return 272 elif data["status_code"] ==256:#目录创建中 273 print(data['目录']) 274 pass 275 self.c.send(b'1')#发送到服务器,表示可以 276 data=self.get_response()#得到服务器的回复 277 if data["status_code"] ==258:#目录创建中完成 278 print(data['status_msg']) 279 return 280 pass 281 282 #删除目录 283 def cmd_rmdir(self,cmd_list,**kwargs): 284 mag_dict={ 285 "action":"rmdir", 286 'filename':cmd_list[1] 287 } 288 self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送文件信息 289 data=self.get_response()#得到服务器的回复 290 if data["status_code"] ==256:#目录不存在 291 print(data['status_msg']) 292 return 293 elif data["status_code"] ==260:#目录不为空 294 print(data['status_msg']) 295 print(data['目录']) 296 return 297 elif data["status_code"] ==257:#目录删除中 298 print(data['目录']) 299 pass 300 self.c.send(b'1')#发送到服务器,表示可以 301 data=self.get_response()#得到服务器的回复 302 if data["status_code"] ==259:#目录删除完成 303 print(data['status_msg']) 304 return 305 pass 306 307 #上传方法 308 def cmd_put(self,cmd_list,**kwargs):#上传方法 309 if len(cmd_list) > 1: 310 filename=cmd_list[1]#取文件名 311 filename_dir=config.PUT_DIR+filename#拼接文件名路径 312 313 if os.path.isfile(filename_dir):#是否是一个文件 314 filesize=os.stat(filename_dir).st_size#获取文件大小 315 #执行行为 名字,大小,是否 316 mag_dict={ 317 "action":"put", 318 'filename':filename, 319 'size':filesize, 320 'overridden':True, 321 'md5':False 322 } 323 if self.cmd_md5_(cmd_list):#判断是否进行MD5 324 mag_dict['md5'] = True 325 self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送文件信息 326 data=self.get_response()#得到服务器的回复 327 if data["status_code"] ==250:#磁盘空间不足 328 print(data['status_msg']) 329 print(mag_dict['size']) 330 return 331 if data["status_code"] ==249:#磁盘空间足够 332 print(data['status_msg']) 333 print('剩余空间',data['剩余空间']) 334 self.c.send(b'1')#发送到服务器,表示可以上传文件了 335 data=self.get_response()#得到服务器的回复 336 if data["status_code"] ==230:#断点续传 337 print(data['status_msg']) 338 print(data['文件大小']) 339 self.rat=data['文件大小']#文件指针位置 340 pass 341 elif data["status_code"] ==231:#非断点续传 342 print(data['status_msg']) 343 self.rat=0#文件指针位置 344 pass 345 f=open(filename_dir,'rb')#打开文件 346 f.seek(self.rat)#移动到位置 347 print(mag_dict['md5']) 348 self.c.send(b'1')#发送到服务器,表示可以上传文件了 349 if mag_dict['md5']==True: 350 md5_obj = hashlib.md5()#定义MD5 351 progress = self.show_pr(mag_dict['size']) #进度条 传入文件大小 352 progress.__next__() 353 while self.rat<filesize: 354 line=f.read(1024) 355 self.c.send(line) 356 try: 357 progress.send(len(line))#传入当前数据大小 358 except StopIteration as e: 359 print("100%") 360 break 361 md5_obj.update(line)#计算MD5 362 363 else: 364 print(filename,'发送完成!') 365 f.close() 366 md5_val = md5_obj.hexdigest() 367 md5_from_server = self.get_response()#服务端的MD5 368 if md5_from_server['status_code'] == 248: 369 if md5_from_server['md5'] == md5_val: 370 print("%s 文件一致性校验成功!" % filename) 371 return 372 else: 373 progress = self.show_pr(mag_dict['size']) #进度条 传入文件大小 374 progress.__next__() 375 #for line in f: 376 while self.rat<filesize: 377 line=f.read(1024) 378 self.c.send(line) 379 try: 380 progress.send(len(line))#传入当前数据大小 381 except StopIteration as e: 382 print("100%") 383 break 384 #print(line) 385 else: 386 print(filename,'发送完成!') 387 f.close() 388 return 389 else: 390 print(filename,'文件不存在!') 391 392 #下载方法 393 def cmd_get(self,cmd_list,**kwargs):#下载方法 394 #cmd_split= args[0].split()#指令解析 395 # if len(cmd_list) == 1: 396 # print("没有输入文件名.") 397 # return 398 #down_filename = cmd_list[1].split('/')[-1]#文件名 399 down_filename=cmd_list[1]#取文件名 400 file_path='%s/%s'%(config.GET_DIR,down_filename)#拼接文件路径 用户down目录 401 if os.path.isfile(file_path):#文件是否存 402 filesize=os.stat(file_path).st_size#获取文件大小 403 name_down=True 404 else: 405 filesize=0 406 name_down=False 407 mag_dict={ 408 "action":"get", 409 'filename':cmd_list[1], 410 'name_down':name_down, 411 'size':filesize 412 } 413 if self.cmd_md5_(cmd_list):#判断是否进行MD5 414 mag_dict['md5'] = True 415 self.c.send(json.dumps(mag_dict).encode())#发送 416 self.c.send(b'1')#发送到服务器,防粘包 417 418 response = self.get_response()#服务器返回文件 的信息 419 if response["status_code"] ==247:#如文件存在 420 if name_down==True and response['file_size']==filesize: 421 print('文件已经下载完成') 422 self.c.send(b'2') 423 return 424 self.c.send(b'1')#发送到服务器,表示可以接收文件了 425 #if name_down: 426 received_size = filesize#当前接收的数据大小 427 #else: 428 #received_size = 0#当前接收的数据大小 429 430 file_obj = open(file_path,"ab")#打开文件 431 if self.cmd_md5_(cmd_list): 432 md5_obj = hashlib.md5() 433 progress = self.show_pr(response['file_size']) #进度条 传入文件大小 434 progress.__next__() 435 while received_size< response['file_size']: 436 if response['file_size'] - received_size>1024:#表示接收不止一次 437 size=1024 438 else:#最后一次 439 size=response['file_size'] - received_size 440 #print('最后一个大小',size) 441 data= self.c.recv(size)#接收数据 442 443 try: 444 progress.send(len(data))#传入当前数据大小 445 except StopIteration as e: 446 print("100%") 447 received_size+=len(data)#接收数据大小累加 448 file_obj.write(data)#写入文件 449 md5_obj.update(data)#进行MD5验证 450 else: 451 print("下载完成".center(60,'-')) 452 file_obj.close() 453 md5_val = md5_obj.hexdigest()#获取MD5 454 #print(md5_val) 455 md5_from_server = self.get_response()#服务端的MD5 456 #print(md5_from_server['md5']) 457 if md5_from_server['status_code'] == 248: 458 if md5_from_server['md5'] == md5_val: 459 print("%s 文件一致性校验成功!" % down_filename) 460 pass 461 else: 462 progress = self.show_pr(response['file_size']) #进度条 传入文件大小 463 progress.__next__() 464 while received_size< response['file_size']: 465 if response['file_size'] - received_size>1024:#表示接收不止一次 466 size=1024 467 else:#最后一次 468 size=response['file_size'] - received_size 469 #print('最后一个大小',size) 470 data= self.c.recv(size)#接收数据 471 472 try: 473 progress.send(len(data))#传入当前数据大小 474 except StopIteration as e: 475 print("100%") 476 received_size+=len(data)#接收数据大小累加 477 file_obj.write(data)#写入文件 478 pass 479 480 else: 481 print("下载完成".center(60,'-')) 482 file_obj.close() 483 pass 484 self.c.send(b'1')#发送到服务器,表示可以接收文件了 485 486 if __name__=='__main__': 487 488 c=FTPClient() 489 c.inter()
| |- - -cfg/#配置目录
| | |- - -__init__.py
| | |- - -config.py#配置文件
1 #!usr/bin/env python 2 #-*-coding:utf-8-*- 3 # Author calmyan 4 5 import os ,sys 6 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量 7 sys.path.append(BASE_DIR)#增加环境变量 8 #print(BASE_DIR) 9 10 PUT_DIR=BASE_DIR+'\putfile\\'#定义用户上传目录文件路径变量 11 GET_DIR=BASE_DIR+'\down\\'#定义用户下载目录文件路径变量 12 HELP='help' 13 CMD_LIST=['ls','pwd','info','help']
|- - -ftp_server/#服务端程序目录
| |- - -__init__.py
| |- - -bin/#启动目录
| | |- - -__init__.py
| | |- - -start.py#服务端视图启动
1 #!usr/bin/env python 2 #-*-coding:utf-8-*- 3 # Author calmyan 4 import socket,os,json 5 import os ,sys 6 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量 7 sys.path.append(BASE_DIR)#增加环境变量 8 9 from core import main 10 11 if __name__ == '__main__': 12 13 main.ArvgHandler()
| | |- - -user_reg.py#用户注册启动
1 #!usr/bin/env python 2 #-*-coding:utf-8-*- 3 # Author calmyan 4 5 import configparser 6 import os ,sys 7 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量 8 sys.path.append(BASE_DIR)#增加环境变量 9 from cfg import config 10 #修改个信息 磁盘大小 11 def set_info(name,pwd,size): 12 config_info=configparser.ConfigParser()#读数据 13 config_info.read(config.AUTH_FILE)#读文件 用户名密码 14 #print(config_info.options(name)) 15 config_info[name]={} 16 config_info.set(name,config.PWD,pwd)#密码 17 config_info.set(name,config.QUOTATION,size)#磁盘信息 18 config_info.write(open(config.AUTH_FILE,'w'))#写入文件 19 file_path='%s/%s'%(config.USER_HOME,name)#拼接目录路径 20 os.mkdir(file_path)#创建目录 21 print('创建完成'.center(60,'=')) 22 print('用户名:[%s]\n密码:[%s]\n磁盘空间:[%s]'%(name,pwd,size)) 23 24 if __name__ == '__main__': 25 name=input('name:') 26 pwd=input('pwd:') 27 size=input('size:') 28 set_info(name,pwd,size)
| | |- - -userpwd.cfg#用户信息文件
1 #!usr/bin/env python 2 #-*-coding:utf-8-*- 3 # Author calmyan 4 5 import os ,sys 6 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量 7 sys.path.append(BASE_DIR)#增加环境变量 8 #HOME_PATH = os.path.join(BASE_DIR, "home") 9 10 11 12 #USER_DIR='%s\\data\\'%BASE_DIR#定义用户数据目录文件路径变量 13 #USER_DIR='%s/data'%BASE_DIR#定义用户数据目录文件路径变量 14 #USER_HOME='%s\\home\\'%BASE_DIR#定义用户家目录文件路径变量 15 USER_HOME='%s/home'%BASE_DIR#定义用户家目录文件路径变量 16 #LOG_DIR='%s\\log\\'%BASE_DIR#日志目录 17 USER_LOG='%s/log/user_log.log'%BASE_DIR#日志登陆文件 18 USER_OPERT='%s/log/user_opert.log'%BASE_DIR#日志操作文件 19 20 LOG_LEVEL='DEBUG'#日志级别 21 22 AUTH_FILE='%s/cfg/userpwd.cfg'%BASE_DIR#用户名密码文件 23 HOST='0.0.0.0'# IP 24 PORT=9500#端口 25 QUOTATION='Quotation'#磁盘空间 26 PWD='PWD'#密码
| |- - -core/#服务端主要文件目录
| | |- - -__init__.py
| | |- - -ftp_server.py#服务端主要逻辑 类
1 #!usr/bin/env python 2 #-*-coding:utf-8-*- 3 # Author calmyan 4 import socketserver,os,json,pickle,configparser,time 5 time_format='%Y%m%d%H%M%S'#定义时间格式 6 times=time.strftime(time_format)#定义时间 7 8 STATUS_CODE={ 9 230:'文件断点继传', 10 231:'新文件', 11 240:'格式出错,格式:{"action":"get","filename":"filename","size":100}', 12 241:'指令错误', 13 242:'用户名或密码为空', 14 243:'用户或密码出错', 15 244:'用户密码通过校验', 16 245:'文件不存在或不是文件', 17 246:'服务器上该文件不存在', 18 247:'准备发送文件,请接收', 19 248:'md5', 20 249:'准备接收文件,请上传', 21 250:'磁盘空间不够', 22 251:'当前已经为主目录', 23 252:'目录正在切换', 24 253:'正在查看路径', 25 254:'准备删除文件', 26 255:'删除文件完成', 27 256:'目录不存在', 28 257:'目录已经存在', 29 258:'目录创建完成', 30 259:'目录删除完成', 31 260:'目录不是空的', 32 } 33 import os ,sys,hashlib 34 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量 35 sys.path.append(BASE_DIR)#增加环境变量 36 from cfg import config 37 from core.logs import log_log 38 from core.logs import user_opert 39 40 41 class MyTCPHandler (socketserver.BaseRequestHandler):# 42 43 def setup(self): 44 print('监听中。。。') 45 #'''用户名与密码是否为空''' 46 def cmd_auth(self,*args,**kwargs):#用户校验 47 '''用户名与密码是否为空''' 48 data=args[0]#获取 传来的数据 49 if data.get('username') is None or data.get('password') is None:#如果用户名或密码为空 50 self.send_mge(242)#发送错误码 51 name=data.get('username')#用户名 52 pwd=data.get('password')#密码 53 print(name,pwd) 54 user=self.authusername(name,pwd)#用户名与密码的校验 获名用户名 55 if user is None:#用户名不存在 56 self.send_mge(243) 57 else: 58 self.user=name#保存用户名 59 self.home_dir='%s/%s'%(config.USER_HOME,self.user)#拼接 用户home目录路径 用户根目录 60 self.user_home_dir=self.home_dir#当前所在目录 61 # self.user_dir=self.user_home_dir.split('/')[-1]#当前所在目录 相对 62 self.dir_join()#进行目录拼接 63 self.send_mge(244,data={'dir':self.user_dir})#相对 目录 64 65 #目录拼接 66 def dir_join(self,*args,**kwargs): 67 self.user_dir=self.user_home_dir.split(self.home_dir)[-1]+'/'#当前所在目录 相对 68 print(self.user_dir) 69 70 #'''用户名与密码的校验'' 71 def authusername(self,name,pwd): 72 '''用户名与密码的校验''' 73 config_info=configparser.ConfigParser()#读数据 74 config_info.read(config.AUTH_FILE)#读文件 用户名密码 75 if name in config_info.sections():#用户名存 76 password=config_info[name]['PWD'] 77 if password==pwd:#密码正确 78 print('通过校验!') 79 config_info[name]['USERname']=name#名字的新字段 80 info_str='用户[%s],成功登陆'%name 81 self.log_log.warning(info_str)#记录日志 82 #log_log(info_str) 83 return config_info[name] 84 else: 85 info_str='用户[%s],登陆错误'%name 86 #log_log(info_str) 87 self.log_log.warning(info_str)#记录日志 88 return 0 89 90 #判断文件 是否存在 91 def file_name(self,file_path): 92 if os.path.isfile(file_path):#文件是否存 93 return True 94 else: 95 return False 96 97 #判断目录是否存在 98 def file_dir(self,file_path): 99 if os.path.isdir(file_path):#目录是否存 100 return True 101 else: 102 return False 103 104 #删除文件 105 def cmd_rm(self,*args,**kwargs): 106 cmd_dict=args[0]#获取字典 107 action=cmd_dict["action"] 108 filename =cmd_dict['filename']#文件名 109 file_path='%s/%s'%(self.user_home_dir,filename)#拼接文件路径 110 if not self.file_name(file_path): 111 self.send_mge(245)#文件不存在 112 return 113 else: 114 user_size=self.disk_size()#获取磁盘信息 115 self.send_mge(254,data={'剩余空间':user_size})#准备删除文件 116 file_size=os.path.getsize(file_path)#获取文件大小 117 pass 118 self.request.recv(1) #客户端确认 防粘包 119 os.remove(file_path) 120 new_size=float((float(user_size)+float(file_size))/1024000)#空间大小增加 121 self.set_info(str(new_size))#传入新大小 122 self.send_mge(255,data={'剩余空间':new_size})#删除文件完成 123 info_str=self.log_str('删除文件')#生成日志信息 124 self.user_opert.critical(info_str)#记录日志 125 return 126 127 #创建目录 128 def cmd_mkdir(self,*args,**kwargs): 129 cmd_dict=args[0]#获取字典 130 action=cmd_dict["action"] 131 filename =cmd_dict['filename']#目录名 132 file_path='%s/%s'%(self.user_home_dir,filename)#拼接目录路径 133 if self.file_dir(file_path): 134 self.send_mge(257)#目录已经 存在 135 return 136 else: 137 self.send_mge(256,data={'目录':'创建中...'})#目录创建中 138 self.request.recv(1) #客户端确认 防粘包 139 os.mkdir(file_path)#创建目录 140 self.send_mge(258)#目录完成 141 info_str=self.log_str('创建目录')#生成日志信息 142 self.user_opert.critical(info_str)#记录日志 143 return 144 145 #删除目录 146 def cmd_rmdir(self,*args,**kwargs): 147 cmd_dict=args[0]#获取字典 148 action=cmd_dict["action"] 149 filename =cmd_dict['filename']#目录名 150 file_path='%s/%s'%(self.user_home_dir,filename)#拼接目录路径 151 if not self.file_dir(file_path): 152 self.send_mge(256)#目录不存在 153 return 154 elif os.listdir(file_path): 155 self.send_mge(260,data={'目录':'无法删除'})#目录不是空的 156 return 157 else: 158 self.send_mge(257,data={'目录':'删除中...'})#目录创建中 159 self.request.recv(1) #客户端确认 防粘包 160 os.rmdir(file_path)#删除目录 161 self.send_mge(259)#目录删除完成 162 info_str=self.log_str('删除目录')#生成日志信息 163 self.user_opert.critical(info_str)#记录日志 164 return 165 166 #磁盘空间大小 167 def disk_size(self): 168 attr_list=self.user_info()#调用个人信息 169 put_size=attr_list[1]#取得磁盘信息 170 user_size=float(put_size)*1024000#字节 171 return user_size 172 173 #'''客户端上传文件 ''' 174 def cmd_put(self,*args,**kwargs): 175 '''客户端上传文件 ''' 176 cmd_dict=args[0]#获取字典 177 filename =cmd_dict['filename']#文件名 178 file_size= cmd_dict['size']#文件大小 179 #user_home_dir='%s/%s'%(config.USER_HOME,self.user)#拼接 用户home目录路径 180 file_path='%s/%s'%(self.user_home_dir,filename)#拼接文件路径 181 user_size=self.disk_size()#取得磁盘信息 182 if float(file_size)>float(user_size):#空间不足 183 self.send_mge(250,data={'剩余空间':user_size}) 184 return 185 self.send_mge(249,data={'剩余空间':user_size})#发送一个确认 186 self.request.recv(1) #客户端确认 防粘包 187 if self.file_name(file_path):#判断文件名是否存在, 188 s_file_size=os.path.getsize(file_path)##获取服务器上的文件大小 189 if file_size>s_file_size:#如果服务器上的文件小于要上传的文件进 190 tmp_file_size=os.stat(file_path).st_size#计算临时文件大小 191 reversed_size=tmp_file_size#接收到数据大小 192 self.send_mge(230,data={'文件大小':reversed_size})#发送临时文件大小 193 pass 194 else:# file_size==s_file_size:#如果大小一样 195 file_path=file_path+'_'+times#命名新的文件 名 196 reversed_size=0#接收到数据大小 197 self.send_mge(231)#发送 不是断点文件 198 pass 199 else: 200 reversed_size=0#接收到数据大小 201 self.send_mge(231)#发送 不是断点文件 202 pass 203 204 f=open(file_path,'ab') 205 self.request.recv(1) #客户端确认 防粘包 206 if cmd_dict['md5']:#是否有 md5 207 md5_obj = hashlib.md5() # 进行MD5 208 while reversed_size< int(file_size):#接收小于文件 大小 209 if int(file_size) - reversed_size>1024:#表示接收不止一次 210 size=1024 211 else:#最后一次 212 size=int(file_size) - reversed_size 213 #print('最后一个大小',size) 214 data= self.request.recv(size)#接收数据 215 md5_obj.update(data) 216 reversed_size+=len(data)#接收数据大小累加 217 f.write(data)#写入文件 218 else: 219 f.close() 220 print('[%s]文件上传完毕'.center(60,'-')%filename) 221 md5_val = md5_obj.hexdigest()#得出MD5 222 print(md5_val) 223 self.send_mge(248,{'md5':md5_val})#发送md5给客户端 224 else: 225 while reversed_size< int(file_size):#接收小于文件 大小 226 if int(file_size) - reversed_size>1024:#表示接收不止一次 227 size=1024 228 else:#最后一次 229 size=int(file_size) - reversed_size 230 #print('最后一个大小',size) 231 data= self.request.recv(size)#接收数据 232 reversed_size+=len(data)#接收数据大小累加 233 f.write(data)#写入文件 234 else: 235 print('[%s]文件上传完毕'%filename.center(60,'-')) 236 f.close() 237 new_size=float((float(user_size)-float(file_size))/1024000)#扣除空间大小 238 self.set_info(str(new_size))#传入新大小 239 info_str=self.log_str('文件上传')#生成日志信息 240 self.user_opert.critical(info_str)#记录日志 241 return 242 243 #用户下载文件 244 def cmd_get(self,*args,**kwargs):#用户下载文件 245 ''' 用户下载文件''' 246 data=args[0] 247 print(data) 248 if data.get('filename') is None:#判断文件名不为空 249 self.send_mge(245) 250 return 251 252 self.request.recv(1) #客户端确认 防粘包 253 file_path='%s/%s'%(self.user_home_dir,data.get('filename'))#拼接文件路径 用户文件路径 254 if os.path.isfile(file_path):#判断文件是否存在 255 file_obj=open(file_path,'rb')#打开文件句柄\ 256 file_size=os.path.getsize(file_path)#获取文件大小 257 if data['name_down']: 258 send_size=data['size']#已经发送数据大小 259 #self.send_mge(230,data={'文件大小':file_size})#断点续传 260 else: 261 send_size=0 262 #self.send_mge(231)#非断点续传 263 #self.request.recv(1) #客户端确认 防粘包 264 file_obj.seek(send_size)#移动到 265 self.send_mge(247,data={'file_size':file_size})#发送相关信息 266 attr=self.request.recv(1024) #客户端确认 防粘包 267 if attr.decode()=='2':return #如果返回是 268 if data.get('md5'): 269 md5_obj = hashlib.md5() 270 while send_size<file_size: 271 line=file_obj.read(1024) 272 #for line in file_obj: 273 self.request.send(line) 274 md5_obj.update(line) 275 else: 276 file_obj.close() 277 md5_val = md5_obj.hexdigest() 278 self.send_mge(248,{'md5':md5_val}) 279 print("发送完毕.") 280 else: 281 while send_size<file_size: 282 line=file_obj.read(1024) 283 #for line in file_obj: 284 self.request.send(line) 285 else: 286 file_obj.close() 287 print("发送完毕.") 288 self.request.recv(1) #客户端确认 防粘包 289 info_str=self.log_str('下载文件')#生成日志信息 290 #user_opert(info_str)#记录日志 291 self.user_opert.critical(info_str)#记录日志 292 return 293 294 #切换目录 295 def cmd_cd(self,cmd_dict,*args,**kwargs): 296 '''切换目录''' 297 cmd_attr=cmd_dict['actionname']#获取命令 298 if cmd_attr=='..' or cmd_attr=='../..': 299 if (self.home_dir)==self.user_home_dir: 300 self.send_mge(251) 301 return 302 elif cmd_attr=='../..': 303 self.send_mge(252)#可以切换到上级目录 304 self.user_home_dir=self.home_dir#绝对目录 = home 305 self.user_dir='/' 306 clinet_ack=self.request.recv(1024)#为了去粘包 307 self.request.send(self.user_dir.encode())#返回相对目录 308 return 309 else: 310 self.send_mge(252)#可以切换到上级目录 311 print(self.user_home_dir)#绝对目录 312 print(os.path.dirname(self.user_home_dir))#父级目录 313 self.user_home_dir=os.path.dirname(self.user_home_dir)#父级目录 314 self.dir_join()#目录拼接切换 315 clinet_ack=self.request.recv(1024)#为了去粘包 316 self.request.send(self.user_dir.encode())#返回相对目录 317 return 318 319 elif os.path.isdir(self.user_home_dir+'/'+cmd_attr):#如果目录存在 320 self.send_mge(252) 321 self.user_home_dir=self.user_home_dir+'/'+cmd_attr#目录拼接 322 self.dir_join()#相对目录拼接切换 323 clinet_ack=self.request.recv(1024)#为了去粘包 324 print(clinet_ack.decode()) 325 self.request.send(self.user_dir.encode()) 326 return 327 else: 328 self.send_mge(256)#目录不存在 329 return 330 331 #查看目录路径 CD 332 def cmd_pwd(self,cmd_dict): 333 self.request.send(str(len(self.user_dir.encode('utf-8'))).encode('utf-8'))#发送大小 334 clinet_ack=self.request.recv(1024)#为了去粘包 335 self.request.send(self.user_dir.encode())#发送相对路径 336 info_str=self.log_str('查看目录路径')#生成日志信息 337 #logger.warning 338 self.user_opert.critical(info_str)#记录日志 339 return 340 341 #修改个信息 磁盘大小 342 def set_info(self,new_size): 343 config_info=configparser.ConfigParser()#读数据 344 config_info.read(config.AUTH_FILE)#读文件 用户名密码 345 print(config_info.options(self.user)) 346 config_info.set(self.user,config.QUOTATION,new_size) 347 config_info.write(open(config.AUTH_FILE,'w')) 348 349 #读取个人信息 350 def user_info(self): 351 config_info=configparser.ConfigParser()#读数据 352 config_info.read(config.AUTH_FILE)#读文件 用户名密码 353 print(config_info.options(self.user)) 354 pwds=config_info[self.user][config.PWD]#密码 355 Quotation=config_info[self.user][config.QUOTATION]#磁盘配额 剩余 356 user_info={} 357 user_info['用户名']=self.user 358 user_info['密码']=pwds 359 user_info['剩余磁盘配额']=Quotation 360 return user_info,Quotation 361 362 #查看用户信息 363 def cmd_info(self,*args,**kwargs): 364 attr=self.user_info() 365 info_dict=attr[0] 366 self.request.send(str(len(json.dumps(info_dict))).encode('utf-8'))# 367 clinet_ack=self.request.recv(1024)#为了去粘包 368 self.request.send(json.dumps(info_dict).encode('utf-8'))#发送指令 369 info_str=self.log_str('查看用户信息')#生成日志信息 370 self.user_opert.critical(info_str)#记录日志 371 return 372 373 #日志信息生成 374 def log_str(self,msg,**kwargs): 375 info_str='用户[%s]进行了[%s]操作'%(self.user,msg) 376 return info_str 377 378 379 #目录查看 380 def cmd_ls(self,*args,**kwargs): 381 data=os.listdir(self.user_home_dir)#查看目录文件 382 print(data) 383 datas=json.dumps(data)#转成json格式 384 self.request.send(str(len(datas.encode('utf-8'))).encode('utf-8'))#发送大小 385 clinet_ack=self.request.recv(1024)#为了去粘包 386 self.request.send(datas.encode('utf-8'))#发送指令 387 info_str=self.log_str('目录查看')#生成日志信息 388 self.user_opert.critical(info_str)#记录日志 389 return 390 ##单个命令 391 def cmd_compr(self,cmd_dict,**kwargs): 392 attr=cmd_dict['actionname']#赋于变量 393 if hasattr(self,'cmd_%s'%attr):#是否存在 394 func=getattr(self,'cmd_%s'%attr)#调用 395 func(cmd_dict) 396 return 397 else: 398 print('没有相关命令!') 399 self.send_mge(241) 400 return 401 402 #'''发送信息码给客户端''' 403 def send_mge(self,status_code,data=None): 404 '''发送信息码给客户端''' 405 mge={'status_code':status_code,'status_msg':STATUS_CODE[status_code]}#消息 406 if data:#不为空 407 mge.update(data)#提示码进行更新 408 print(mge) 409 self.request.send(json.dumps(mge).encode())#发送给客户端 410 411 #重写handle方法 412 def handle(self):#重写handle方法 413 while True: 414 #try: 415 self.data=self.request.recv(1024).strip()#接收数据 416 print('ip:{}'.format(self.client_address[0]))#连接的ip 417 print(self.data) 418 self.log_log=log_log()#登陆日志 419 self.user_opert=user_opert()#操作日志 420 if not self.data: 421 print("[%s]客户端断开了!."%self.user) 422 info_str='用户[%s],退出'%self.user 423 424 break 425 cmd_dict=json.loads(self.data.decode())#接收 数据 426 if cmd_dict.get('action') is not None:#判断数据格式正确 427 action=cmd_dict['action']#文件 头 428 if hasattr(self,'cmd_%s'%action):#是否存在 429 func=getattr(self,'cmd_%s'%action)#调用 430 func(cmd_dict) 431 else: 432 print('没有相关命令!') 433 self.send_mge(241) 434 else: 435 print('数据出错!') 436 self.send_mge(240) 437 #except Exception as e: 438 # print('客户端断开了!',e) 439 # break
| | |- - -logs.py#日志主要逻辑 类
1 #!usr/bin/env python 2 #-*-coding:utf-8-*- 3 # Author calmyan 4 import os,logging,time 5 from cfg import config 6 LOG_LEVEL=config.LOG_LEVEL 7 8 9 def log_log():#登陆日志,传入内容 10 logger=logging.getLogger('用户成功登陆日志')#设置日志模块 11 logger.setLevel(logging.DEBUG) 12 fh=logging.FileHandler(config.USER_LOG,encoding='utf-8')#写入文件 13 fh.setLevel(config.LOG_LEVEL)#写入信息的级别 14 fh_format=logging.Formatter('%(asctime)s %(message)s',datefmt='%m/%d/%Y %I:%M:%S %p')#日志格式 15 fh.setFormatter(fh_format)#关联格式 16 logger.addHandler(fh)#添加日志输出模式 17 #logger.warning(info_str) 18 return logger 19 20 def user_opert():#用户操作日志,传入内容 21 logger=logging.getLogger('用户操作日志')#设置日志模块 22 logger.setLevel(logging.CRITICAL) 23 fh=logging.FileHandler(config.USER_OPERT,encoding='utf-8')#写入文件 24 fh.setLevel(config.LOG_LEVEL)#写入信息的级别 25 fh_format=logging.Formatter('%(asctime)s %(message)s',datefmt='%m/%d/%Y %I:%M:%S %p')#日志格式 26 fh.setFormatter(fh_format)#关联格式 27 logger.addHandler(fh)#添加日志输出模式 28 #logger.critical(info_str) 29 return logger
| | |- - -main.py#服务端启动主程序
1 #!usr/bin/env python 2 #-*-coding:utf-8-*- 3 # Author calmyan 4 5 import socketserver,os,json,pickle 6 import os ,sys 7 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量 8 sys.path.append(BASE_DIR)#增加环境变量 9 from cfg import config 10 11 12 from core.ftp_server import MyTCPHandler 13 14 import optparse 15 class ArvgHandler(object): 16 def __init__(self):# 可 传入系统参数 17 self.paresr=optparse.OptionParser()#启用模块 18 #self.paresr.add_option('-s','--host',dest='host',help='服务绑定地址') 19 #self.paresr.add_option('-s','--port',dest='host',help='服务端口') 20 (options,args)=self.paresr.parse_args()#返回一个字典与列表的元组 21 22 self.verufy_args(options,args)#进行校验 23 def verufy_args(self,options,args): 24 '''校验与调用''' 25 if hasattr(self,args[0]):#反射判断参数 26 func=getattr(self,args[0])#生成一个实例 27 func()#开始调用 28 else: 29 self.paresr.print_help()#打印帮助文档 30 def start(self): 31 print('服务启动中....') 32 s=socketserver.ThreadingTCPServer((config.HOST,config.PORT),MyTCPHandler)#实例化一个服务端对象 33 s.serve_forever()#运行服务器 34 print('服务关闭')
技术链接