高級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 continue141 func=getattr(self,'cmd_compr')#調用此方法142 ret=func(cmd_str)143 if ret:144 continue145 else:146 pass147 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 continue152 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 True161 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_size172 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 return185 if cmd_res_attr["status_code"] ==240:#命令不對186 print(cmd_res_attr['status_msg'])187 return188 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 += data195 else:196 receive_data=receive_data.decode()197 try:198 receive_data=eval(receive_data)#轉為列表 或字典199 except Exception as e:200 pass201 if type(receive_data) is dict:#如果是字典202 for i in receive_data:203 print(i,receive_data[i])204 return 1205 if type(receive_data) is list:#如果是列表206 for i in receive_data:207 print(i)208 return 1209 print(receive_data)210 return 1211 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 return225 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_dir233 return234 elif data["status_code"] ==256:#目錄不存在235 print(data['status_msg'])236 return237 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 return250 elif data["status_code"] ==254:#文件刪除完成251 print(data['status_msg'])252 print('刪除前空間:',data['剩余空間'])253 pass254 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 return260 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 return272 elif data["status_code"] ==256:#目錄創建中273 print(data['目錄'])274 pass275 self.c.send(b'1')#發送到服務器,表示可以276 data=self.get_response()#得到服務器的回復277 if data["status_code"] ==258:#目錄創建中完成278 print(data['status_msg'])279 return280 pass281 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 return293 elif data["status_code"] ==260:#目錄不為空294 print(data['status_msg'])295 print(data['目錄'])296 return297 elif data["status_code"] ==257:#目錄刪除中298 print(data['目錄'])299 pass300 self.c.send(b'1')#發送到服務器,表示可以301 data=self.get_response()#得到服務器的回復302 if data["status_code"] ==259:#目錄刪除完成303 print(data['status_msg'])304 return305 pass306 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':False322 }323 if self.cmd_md5_(cmd_list):#判斷是否進行MD5324 mag_dict['md5'] = True325 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 return331 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 pass341 elif data["status_code"] ==231:#非斷點續傳342 print(data['status_msg'])343 self.rat=0#文件指針位置344 pass345 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()#定義MD5351 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 break361 md5_obj.update(line)#計算MD5362 363 else:364 print(filename,'發送完成!')365 f.close()366 md5_val = md5_obj.hexdigest()367 md5_from_server = self.get_response()#服務端的MD5368 if md5_from_server['status_code'] == 248:369 if md5_from_server['md5'] == md5_val:370 print("%s 文件一致性校驗成功!" % filename)371 return372 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 break384 #print(line)385 else:386 print(filename,'發送完成!')387 f.close()388 return389 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 # return398 #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=True404 else:405 filesize=0406 name_down=False407 mag_dict={408 "action":"get",409 'filename':cmd_list[1],410 'name_down':name_down,411 'size':filesize412 }413 if self.cmd_md5_(cmd_list):#判斷是否進行MD5414 mag_dict['md5'] = True415 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 return424 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=1024438 else:#最后一次439 size=response['file_size'] - received_size440 #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()#獲取MD5454 #print(md5_val)455 md5_from_server = self.get_response()#服務端的MD5456 #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 pass461 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=1024467 else:#最后一次468 size=response['file_size'] - received_size469 #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 pass479 480 else:481 print("下載完成".center(60,'-'))482 file_obj.close()483 pass484 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 main10 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 config10 #修改個信息 磁盤大小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] 密碼:[%s] 磁盤空間:[%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'# IP24 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 True101 else:102 return False103 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 return113 else:114 user_size=self.disk_size()#獲取磁盤信息115 self.send_mge(254,data={'剩余空間':user_size})#準備刪除文件116 file_size=os.path.getsize(file_path)#獲取文件大小117 pass118 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 return126 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 return136 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 return144 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 return154 elif os.listdir(file_path):155 self.send_mge(260,data={'目錄':'無法刪除'})#目錄不是空的156 return157 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 return165 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_size172 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 return185 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 pass194 else:# file_size==s_file_size:#如果大小一樣195 file_path=file_path+'_'+times#命名新的文件 名196 reversed_size=0#接收到數據大小197 self.send_mge(231)#發送 不是斷點文件198 pass199 else:200 reversed_size=0#接收到數據大小201 self.send_mge(231)#發送 不是斷點文件202 pass203 204 f=open(file_path,'ab')205 self.request.recv(1) #客戶端確認 防粘包206 if cmd_dict['md5']:#是否有 md5207 md5_obj = hashlib.md5() # 進行MD5208 while reversed_size< int(file_size):#接收小于文件 大小209 if int(file_size) - reversed_size>1024:#表示接收不止一次210 size=1024211 else:#最后一次212 size=int(file_size) - reversed_size213 #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()#得出MD5222 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=1024228 else:#最后一次229 size=int(file_size) - reversed_size230 #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 return242 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 return251 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=0262 #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 return293 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 return302 elif cmd_attr=='../..':303 self.send_mge(252)#可以切換到上級目錄304 self.user_home_dir=self.home_dir#絕對目錄 = home305 self.user_dir='/'306 clinet_ack=self.request.recv(1024)#為了去粘包307 self.request.send(self.user_dir.encode())#返回相對目錄308 return309 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 return318 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 return327 else:328 self.send_mge(256)#目錄不存在329 return330 331 #查看目錄路徑 CD332 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.warning338 self.user_opert.critical(info_str)#記錄日志339 return340 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.user358 user_info['密碼']=pwds359 user_info['剩余磁盤配額']=Quotation360 return user_info,Quotation361 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 return372 373 #日志信息生成374 def log_str(self,msg,**kwargs):375 info_str='用戶[%s]進行了[%s]操作'%(self.user,msg)376 return info_str377 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 return390 ##單個命令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 return397 else:398 print('沒有相關命令!')399 self.send_mge(241)400 return401 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]))#連接的ip417 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.user423 424 break425 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)#添加日志
| | |- - -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 config10 11 12 from core.ftp_server import MyTCPHandler13 14 import optparse15 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('服務關閉')
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com