轉發註明出處:http://www.cnblogs.com/0zcl/p/6259128.html,此次博客寫了好久~~
html
1. 用戶加密認證 (完成)
2. 容許同時多用戶登陸 (完成)
3. 每一個用戶有本身的家目錄 ,且只能訪問本身的家目錄(完成)
4. 對用戶進行磁盤配額,每一個用戶的可用空間不一樣(完成)
5. 容許用戶在ftp server上隨意切換目錄cd(完成)
6. 容許用戶查看當前目錄下文件ls(完成)
7. 容許上傳put和下載get文件(完成),保證文件一致性(此需求不作)
8. 文件傳輸過程當中顯示進度條(完成)
附加功能:python
1.新建目錄mkdir(完成)web
2.查看當前工做目錄的路徑pwd(完成)數據庫
3.支持文件的斷點續傳(未完成)json
客戶端:windows
服務端:緩存
重要!安全
1、需求 1. 用戶加密認證 (完成) 2. 容許同時多用戶登陸 (完成) 3. 每一個用戶有本身的家目錄 ,且只能訪問本身的家目錄(完成) 4. 對用戶進行磁盤配額,每一個用戶的可用空間不一樣(完成) 5. 容許用戶在ftp server上隨意切換目錄cd(完成) 6. 容許用戶查看當前目錄下文件ls(完成) 7. 容許上傳put和下載get文件(完成),保證文件一致性(此需求不作) 8. 文件傳輸過程當中顯示進度條(完成) 附加功能: 1.新建目錄mkdir(完成) 2.查看當前工做目錄的路徑pwd(完成) 3.支持文件的斷點續傳(未完成) 2、目錄結構及模塊功能解釋 ftp_client |----bin(可執行目錄) | |----__init__.py | |----ftp_client.py(客戶端接口) |----conf(配置文件目錄) | |----__init__.py | |----settings.py(配置文件) |----core(核心代碼) | |----__init__.py | |----auth.py(客戶端身份驗證) | |----cd.py(實現客戶端在服務隨意切換目錄的功能,但只能訪問本身的家目錄) | |----get.py(客戶端下載功能) | |----interactive.py(用於客戶端與服務端的交互/反射) | |----ls.py(查看當前目錄下的文件(包括目錄)) | |----main.py(主函數,運行被ftp_client.py客戶端接口調用) | |----mkdir.py(實現用戶在當前目錄下可建立目錄的功能) | |----progress_bar.py(進度條:用於顯示上傳與下載的進度) | |----put.py(處理客戶端上傳功能) | |----pwd.py(查看用戶當前的目錄) |----__init__.py ftp_server |----bin | |----__init__.py | |----ftp_server.py(服務端接口) |----core | |----__init__.py | |----auth.py(用戶加密認證,登錄模塊) | |----db_handle.py(讀用戶數據與寫用戶數據--感受這個模塊有點多餘~) | |----deal_cd.py(處理用戶切換目錄的功能) | |----deal_get.py(處理客戶端下載文件的請求) | |----deal_ls.py(完成用戶顯示當前目錄下文件(包括目錄)的請求) | |----deal_mkdir.py(處理用戶在當前目錄(家目錄下)建立目錄的請求) | |----deal_put.py(處理客戶端上傳文件的請求) | |----deal_pwd.py(用來處理客戶端查看當前目錄下的請求) | |----get_dirisize.py(獲取用戶家目錄的大小(字節)) | |----main.py(主函數--運行時被ftp_server.py服務端接口調用) |----data(用戶數據庫) | |----__init__.py | |----Alex.json(Alex用戶的數據庫) | |----zcl.json(zcl用戶的數據庫) |----home(home目錄,用來存放各用戶的家目錄) | |----Alex(Alex的家目錄) | |----zcl(zcl的家目錄) | |----__init__.py |----log(日誌--未拓展) | |----__init__.py |----__init__.py 3、狀態碼 LOGIN_STATE = { "auth_True":"200", #認證成功 "auth_False":"400", #認證失敗 "file_exit":"202", #文件存在 "file_no_exit":"402", #文件不存在 "cmd_right":"201", #命令正確 "cmd_error":"401", #命令錯誤 "dir_exit":"203", #目錄已存在 "dir_no_exit":"403", #目錄不存在 "cmd_success":"204", #命令成功執行 "cmd_fail":"404", #命令執行失敗 "size_enough":"205", #磁盤空間足夠 "size_empty":"405", #磁盤空間不足 } 4、功能解釋 1.conf目錄下settings.py模塊記錄可操做用戶信息,根據用戶信息生成用戶字典和宿主目錄,已經生成的再也不新建。 2.每一個用戶的宿主目錄磁盤空間配額默認爲100M,可在settings.py模塊裏進行修改 3.程序運行在windows8.1系統上,pycharm 3.4,程序需求除斷點續傳與保證文件一致性外所有實現。 4.切換目錄: cd .. 返回上一級目錄 cd dirname 進入dirname eg:cd \aa 用戶登錄後默認進入宿主目錄,只可在宿主目錄下隨意切換. 5.建立目錄:mkdir dirname 在當前目錄下建立目錄,若是目錄存在則報錯,不存在建立. 6.查看當前目錄完整路徑: pwd 7.查看當前路徑下的文件名和目錄名: ls 8.下載文件(不可續傳):get filename ①、服務端當前目錄存在此文件,客戶端不存在此文件,直接下載. ②、服務端當前目錄存在此文件,客戶端存在此文件名,以前下載中斷,文件不可續傳(未實現). ③、服務端當前目錄存在此文件,客戶端存在此文件名,下載,文件名爲filename+".new". 9.上傳文件:put filename 判斷宿主目錄磁盤空間是否夠用,能夠,上傳文件;不然,報錯.
作這個小項目以前,若是基礎知識不牢的話,能夠看我以前的兩篇博客python之socket-ssh實例和[原創]python之socket-ftp。服務器
需求1:用戶加密認證併發
服務端與用戶端進行交互前,確定須要進行認證。在服務端認證仍是在客戶端?固然是服務端啦,客戶端至少需發送用戶名與密碼,服務端接收後在數據庫中查找相應用戶的密碼,若正確,則發送給客戶端相應的狀態碼。這是認證的功能,如何實現加密認證?能夠導入hashlib模塊,用md5對密碼加密,爲了安全起見,服務端數據庫中的密碼應該是加密後的密文。客戶端登錄認證時也應發送密文到服務端,服務端將接收到的密文與數據庫中對應用戶的密文比較。
需求2:容許同時多用戶登陸
其實需求1是在需求2的登錄功能中實現的。那關鍵就在如何解決多用戶與同時(高併發)。其實這個需求挺簡單的。多用戶我這裏不用數據庫(還沒玩透~),我是建一個包來存放數據,每一個用戶對應一個xxx.json(xxx爲用戶名)。json文件裏面存放一個字典,爲何要用字典來存,而不是字符串,列表,回答是更簡單,更易於拓展~~。高併發是什麼?多個用戶(客戶端),發送指令,服務端能及時處理。下面看一個非高併發化的例子。
1 if__name__=="__main__": 2 HOST,PORT="localhost",9999 3 #Create the server,binding to localhost on port 9999 4 server=socketserver.TCPServer((HOST,PORT),MyTCPHandler)#實例化 5 server.serve_forever()
服務端用上述代碼實例化,當開一個客戶端時,運行沒問題,但若是前後再開客戶端2,3,並向服務端發送指令。客戶端2,3是接收不到服務端的數據的(卡住了),但當客戶端1關閉時,客戶端2收到數據,當客戶湍2關閉時,客戶端3收到數據。將上述代碼第四行改成下面的代碼,則能夠處理高併發:
#每來一個請求,服務端就開啓一個新的線程 server=socketserver.ThreadingTCPServer((HOST,PORT),MyTCPHandler)#實例化
需求3: 每一個用戶有本身的家目錄 ,且只能訪問本身的家目錄
此需求可分爲兩個小需求,得先有用戶家目錄,而後用戶有訪問權限,只能訪問家目錄下。
每一個用戶都有家目錄,怎麼實現?剛開始我是很懵比的,後來我參考Linux,在home目錄下存放各個用戶的家目錄。用戶的家目錄能夠用os.path.join(HOME_PATH, xxx)來拼接(xxx爲家目錄),而後就能夠建立用戶的家目錄了。越日後開發發現代碼愈來愈多,因而我最開始就將HOME目錄放在服務端的配置文件中。
BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__))) HOME_PATH=os.path.join(BASE_DIR,"home") print(HOME_PATH)
輸出:C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_server\home
HOME目錄應當是服務端初始化時自動生成的。我用下面的代碼實現。os.popen()很重要,後來的實現中還會用到~
os.popen("mkdir%s"%user_home_path)
目錄示例以下圖:
需求3的第二個小需求。如何只能訪問家目錄?訪問固然是經過cd命令來實現的!這與需求5是有很大聯繫的,能夠順手作需求5!!而想要cd切換目錄,得先有目錄啊!此時只有上圖home目錄下的兩個空用戶目錄Alex,zcl目錄。因而我順手作了附加功能的1--mkdir新建目錄。回到正題,如何只能訪問家目錄,我想了很久,也參考了別人的博客才一點點作出來的。Linux有cd ..能夠回到上一級目錄,我在cd功能也實現了這個。以zcl用戶爲例,zcl目錄是他的家目錄,他沒有權限在C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_server\home\zcl路徑下調用cd ..回到上一級目錄!!
具體實現中,應當是用戶一登錄成功便進入用戶的家目錄。因而我在auth模塊寫了下兩的代碼。self.current_path是用戶當前目錄,用戶在與服務端交互(cd)中是會改變的。
# 登錄後用戶當前目錄, 即用戶的家目錄 self.current_path = os.path.join(settings.HOME_PATH, recv_list[0]) # 用戶宿主目錄 self.user_home_path = os.path.join(settings.HOME_PATH, recv_list[0])
需求五、六、附加功能一、2:容許用戶在ftp server上隨意切換目錄cd、容許用戶查看當前目錄下文件ls、新建目錄mkdir、查看當前工做目錄的路徑pwd
這四個需求都沒有什麼難度,有共同點。以需求6(ls)爲例,先看下代碼實現:
客戶端的ls模塊:
1 import json 2 3 4 def client_ls(self, *args): 5 """查看當前目錄下的文件(包括目錄)""" 6 cmd_split = args[0].split() 7 if len(cmd_split) == 1 and cmd_split[0] == "ls": 8 msg_dic = { 9 "action":"ls", 10 } 11 self.client.send(json.dumps(msg_dic).encode()) 12 server_response = self.client.recv(1024) 13 print(server_response.decode())
服務端的deal_ls模塊:
1 import os,json 2 3 def server_deal_ls(self, *args): 4 """完成用戶顯示當前目錄下文件(包括目錄)的請求""" 5 cmd_dic = args[0] 6 r = os.popen("dir %s" % self.current_path) 7 dir_message = r.read() 8 self.request.send(dir_message.encode())
實現邏輯:
首先你得懂什麼是反射!我會寫這方面的博客,不過得好久之後,建議不懂具體實現的先百度一下。不懂具體實現也沒事,頂多看不懂代碼!你在客戶端輸入ls命令(或者 cd xx/mkdir xx/pwd/get xx/put xx)就經過反射調用客戶端ls模塊的def client_ls(self, *args):方法。而後發送包含相應action的字典(方便拓展)到服務端。服務端接收後,經過字典的action再次反射調用deal_ls模塊的def server_deal_ls(self, *args):方法,處理ls命令,完成後將數據發送到客戶端,客戶端再將其打印到界面。
嗯,反射太強大了!! 下面看下interactive.py交互模塊,看下客戶端反射的實現:
1 def interactive(self): 2 """ 3 本模塊用於客戶端與服務端的交互 4 """ 5 while True: 6 cmd = input(">>>:").strip() 7 if len(cmd) == 0: 8 continue 9 cmd_str = cmd.split()[0] # 指令 10 if hasattr(self, "cmd_%s" % cmd_str): # 反射 11 func = getattr(self, "cmd_%s" % cmd_str) #得到方法對應的內存地址 12 func(cmd) 13 else: 14 self.help()
需求7:容許上傳put和下載get文件
這是個頗有意思的功能,剛開始實現感受蠻6的。上傳與下載文件,還得保持文件的一致性。爲何得保持文件一致性?是由於怕傳的時候萬一丟了什麼數據,被黑客改了數據。舉個例子: 在下載的時候保持文件的一致性,服務端在發送文件給客戶端是一行一行發的,也一行一行用md5加密,經過m.update(line)能夠得出原文件的md5值m1,而客戶端在接收的時候也會一行一行加密,經過m.update(line)得出收到文件的md5值m2,而後服務端發送m1給客戶端進行比較,若m2與m1相同則說明客戶端收到的文件是一致的,反之,說明該文件在傳輸過程當中出現了不可告人的問題!具體的能夠看我以前寫的博客[原創]python之socket-ftp。
我很早就實現上傳下載的功能,當時只想,能把文件傳過去,下載過來就行了。因而出現了下圖的問題:下傳下載的文件與執行文件在同一個目錄下。
仔細想一下,這樣真的能夠嗎?客戶端下載的文件在bin目錄下無所謂,我以爲是能夠的。我這裏將服務端供客戶端下載的文件放在服務端的bin目錄下;但上傳的文件放在服務端的bin目錄下,確定是不行的。一個目錄有如此多的文件,你讓用戶怎麼找??並且用戶根本沒有權限訪問bin目錄。應當是用戶當前在哪一個目錄(確定是家目錄之內)就上傳到哪一個目錄,即上傳到用戶當前所在目錄。還有一個點,用戶上傳空間是有限的,這就與需求4有關聯了。
需求4:對用戶進行磁盤配額,每一個用戶的可用空間不一樣
好比我想限制每一個用戶100M,如何實現?我在配置文件寫了:
#磁盤配額:每一個用戶默認最多100M MAX_SIZE = 102400000
初始化時也將用戶的磁盤配額寫到數據庫中,下面是zcl.json文件:
{"max_size": 102400000, "username": "zcl", "password": "900150983cd24fb0d6963f7d28e17f72", "user_path": "C:\\Users\\Administrator\\PycharmProjects\\laonanhai\\ftp\\ftp_server\\home\\zcl"}
接下來我想,你下載不可能須要限制配額吧!就算在yellow website我也沒見過。上傳空間限制卻是不少,好比百度雲盤~~。
接下來我遇到一個很頭疼的問題:上傳文件時要如何判斷已上傳文件的大小??即用戶家目錄的大小。
經過看別人的博客,我找到下面的代碼:
1 import os 2 3 4 def get_dirsize(dir): 5 """ 6 獲取目錄的大小 7 :param dir: 目錄的路徑 8 :return: 大小(字節) 9 """ 10 size = 0 11 for root, dirs, files in os.walk(dir): 12 size += sum([os.path.getsize(os.path.join(root, name)) for name in files]) 13 return size
由於不懂os.walk(dir),就去看別人的博客Python 3 os.walk使用詳解。你們能夠看看。反正是解決我實際的問題了,哈哈~
需求8:文件傳輸過程當中顯示進度條
進度條我上傳和下載都有作。首先我想的是,進度條是在客戶端仍是服務端實現?固然是客戶端!才能顯示在用戶的界面嘛。下載的進度條較容易作,已經從服務器收到將要下載的文件的大小(字節),也知道此時刻接收文件數據的大小,二者比一下就行了。
1 while receive_size < server_response["file_size"]: 2 data = self.client.recv(1024) 3 receive_size += len(data) 4 #調用progress_bar模塊的方法 5 progress_bar.progress_bar(self, receive_size, server_response["file_size"]) 6 f.write(data)
但上傳的進度條我就卡住了。文件總大小是知道的,但已經上傳的大小呢?要從服務端發送過來?那樣交互就變多了,並且也不大現實……怎麼辦?我又上網查資料。
終於我找到了文件操做的tell()方法:獲取當前指針位置(字節)
1 for line in f: # 上傳文件一行一行 2 self.client.send(line) 3 send_size = f.tell() #獲取當前指針位置(字節) 4 progress_bar.progress_bar(self, send_size, file_size)
作這個小項目我遇到不少問題,一臉懵比的時候都是停下來想一想,再不行看別人的博客參考一下,遇到的BUG就更多了,固然大部分稍稍修改下就行了。我以爲最難的是剛開始作的時候,整個結構都不清楚,到後面大致框架出來了,加一些功能卻是蠻簡單的。
客戶端:
1 import os,json 2 3 4 def client_get(self,*args): 5 """ 6 用來處理客戶端下載功能 7 """ 8 cmd_split=args[0].split() 9 if len(cmd_split)>1: 10 filename=cmd_split[1] 11 msg_dic={ # 爲了可拓展性,用字典形式 12 "action":"get", #發送給服務端的指令 13 "filename":filename, 14 "overridden":True 15 } 16 self.client.send(json.dumps(msg_dic).encode()) 17 #防止粘包,等服務器確認 18 #可優化,確認同時服務端看客戶端是否有權限等404403(狀態碼) 19 server_response=self.client.recv(1024).decode() 20 print(server_response,type(server_response)) 21 self.client.send("客戶端已準備好下載".encode())
服務端:
1 import os,json 2 from conf import settings 3 4 def server_deal_get(self,*args): 5 """處理客戶端下載文件的請求""" 6 cmd_dic=args[0] 7 filename=cmd_dic["filename"] 8 if os.path.isfile(filename): 9 file_size=os.stat(filename).st_size#服務端文件大小 10 msg_dic={ 11 "file_size":file_size,#服務端將發給客戶端的文件的大小 12 "file_exit":settings.LOGIN_STATE["file_exit"] 13 } 14 self.request.send(json.dumps(msg_dic).encode()) 15 #防止粘包,服務端與客戶端再進行一次交互 16 client_response=self.request.recv(1024) 17 print(client_response.decode()) 18 else: 19 self.request.send(json.dumps(settings.LOGIN_STATE["file_no_exit"]).encode())
實現客戶端下載服務端文伯功能。首先客戶端輸入get + 文件名, 經過反射調用client_get(),發送含對應動做(get)的字典到服務端,服務端也經過反射調用server_deal_get(),此時就打開文件,發送給客戶端?不,要先發送文件大小 給客戶端,客戶端才能夠經過while,循環接收比較已接收文件大小與要接收文件大小。這裏我發文件大小的同時也發了一個文件存在的狀態碼402,若服務端文件不存在則發送狀態碼403.
很好,接下來進行測試:
我先登錄成功,而後在客戶端下載oldboy-25.avi文件,下載成功! 而後再下載一個不存在的文件aa, 就出BUG,下面看下具體的BUG提示:
客戶端:
C:\Python34\python3.exe C:/Users/Administrator/PycharmProjects/laonanhai/ftp/ftp_client/bin/ftp_client.py Username:zcl Password:abc ******************Welcome Login******************* >>>:get oldboy-25.avi {"file_exit": "402", "file_size": 180251848} <class 'str'> >>>:get aa "403" <class 'str'> >>>:get oldboy-25.avi Traceback (most recent call last): File "C:/Users/Administrator/PycharmProjects/laonanhai/ftp/ftp_client/bin/ftp_client.py", line 15, in <module> main.run() File "C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_client\core\main.py", line 53, in run ftp_client.interactive() File "C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_client\core\main.py", line 31, in interactive interactive.interactive(self) File "C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_client\core\interactive.py", line 14, in interactive func(cmd) File "C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_client\core\main.py", line 38, in cmd_get get.client_get(self, *args) File "C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_client\core\get.py", line 20, in client_get server_response = self.client.recv(1024).decode() ConnectionAbortedError: [WinError 10053] 你的主機中的軟件停止了一個已創建的鏈接。 Process finished with exit code 1
服務端(下面代碼嫌多能夠只看我加紅的字體):
C:\Python34\python3.exe C:/Users/Administrator/PycharmProjects/laonanhai/ftp/ftp_server/bin/ftp_server.py ['C:\\Users\\Administrator\\PycharmProjects\\laonanhai\\ftp\\ftp_server', 'C:\\Users\\Administrator\\PycharmProjects\\laonanhai\\ftp\\ftp_server\\bin', 'C:\\Python34\\lib\\site-packages\\pip-8.1.2-py3.4.egg', 'C:\\Users\\Administrator\\PycharmProjects\\laonanhai', 'C:\\Windows\\SYSTEM32\\python34.zip', 'C:\\Python34\\DLLs', 'C:\\Python34\\lib', 'C:\\Python34', 'C:\\Python34\\lib\\site-packages'] {'zcl': 'abc', 'Alex': '123'} {'zcl': 'abc', 'Alex': '123'} zcl:900150983cd24fb0d6963f7d28e17f72 <class 'str'> ['zcl', '900150983cd24fb0d6963f7d28e17f72'] C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_server/data/zcl.json file exist {'password': '900150983cd24fb0d6963f7d28e17f72', 'username': 'zcl'} login success send login_state 127.0.0.1 wrote: b'{"filename": "oldboy-25.avi", "action": "get", "overridden": true}' 客戶端已準備好下載 127.0.0.1 wrote: b'{"filename": "aa", "action": "get", "overridden": true}' 127.0.0.1 wrote: b'\xe5\xae\xa2\xe6\x88\xb7\xe7\xab\xaf\xe5\xb7\xb2\xe5\x87\x86\xe5\xa4\x87\xe5\xa5\xbd\xe4\xb8\x8b\xe8\xbd\xbd' ---------------------------------------- Exception happened during processing of request from ('127.0.0.1', 53815) Traceback (most recent call last): File "C:\Python34\lib\socketserver.py", line 617, in process_request_thread self.finish_request(request, client_address) File "C:\Python34\lib\socketserver.py", line 344, in finish_request self.RequestHandlerClass(request, client_address, self) File "C:\Python34\lib\socketserver.py", line 673, in __init__ self.handle() File "C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_server\core\main.py", line 27, in handle cmd_dic = json.loads(self.data.decode()) #字典格式 File "C:\Python34\lib\json\__init__.py", line 318, in loads return _default_decoder.decode(s) File "C:\Python34\lib\json\decoder.py", line 343, in decode obj, end = self.raw_decode(s, idx=_w(s, 0).end()) File "C:\Python34\lib\json\decoder.py", line 361, in raw_decode raise ValueError(errmsg("Expecting value", s, err.value)) from None ValueError: Expecting value: line 1 column 1 (char 0) ----------------------------------------
第二次下載時,服務端接收到的數據是什麼鬼?!!!
127.0.0.1 wrote: b'\xe5\xae\xa2\xe6\x88\xb7\xe7\xab\xaf\xe5\xb7\xb2\xe5\x87\x86\xe5\xa4\x87\xe5\xa5\xbd\xe4\xb8\x8b\xe8\xbd\xbd'
我測試了挺久的,單獨地get oldboy-25.avi(服務端存在的文件)是不會出異常的,可是先get aa(服務端不存在此文件),再get oldboy-25.avi;或者get aa, 再get aa都會出異常。
我看了服務端的代碼及BUG提示後猜測,當輸入get aa時,服務端發送狀態碼,客戶端接收後,還發給服務端self.client.send("客戶端已準備好下載".encode()),而再次輸入get oldboy-25.avi時,服務端接收到的也許是「客戶端已準備好下載」,而不是含對應動做(get)的字典.MY GOD!!
驗證:
1 s=b'\xe5\xae\xa2\xe6\x88\xb7\xe7\xab\xaf\xe5\xb7\xb2\xe5\x87\x86\xe5\xa4\x87\xe5\xa5\xbd\xe4\xb8\x8b\xe8\xbd\xbd' 2 print(type(s)) 3 s1=str(s,encoding="utf-8") 4 print(s1) 5 6 ss="客戶端已準備好下載" 7 b=bytes(ss,encoding="utf-8") 8 print(b)
輸出:
C:\Python34\python3.exe C:/Users/Administrator/PycharmProjects/laonanhai/ftp/ftp_server/core/test.py <class 'bytes'> 客戶端已準備好下載 b'\xe5\xae\xa2\xe6\x88\xb7\xe7\xab\xaf\xe5\xb7\xb2\xe5\x87\x86\xe5\xa4\x87\xe5\xa5\xbd\xe4\xb8\x8b\xe8\xbd\xbd' Process finished with exit code 0
說明服務端接收到的是「客戶端已準備好下載」,而不是含對應動做(get)的字典!!進一步證實我猜測的是對的!如何解決這個BUG,很簡單,客戶端只要對從服務端收到的狀態碼(文件存在402;服務端文件不存在則發送狀態碼403)進行分開討論就能夠解決!!
下面寫一個簡單的吧,放鬆一下:
想實現切換目錄,感受得先實現ls,顯示當前目錄下的文件及目錄較好,否則連當前目錄下有什麼目錄都不知道,還怎麼切換目錄!如何查看當前目錄(家目錄)下的目錄及文件?? 請看下面代碼:
r=os.popen("dir%s"%BASE_DIR) print(r.read())
輸出:
驅動器 C 中的卷沒有標籤。 卷的序列號是 000C-3580 C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_server 的目錄 2017/01/10 週二 上午 01:02 <DIR> . 2017/01/10 週二 上午 01:02 <DIR> .. 2017/01/10 週二 上午 12:43 <DIR> bin 2017/01/10 週二 下午 08:54 <DIR> conf 2017/01/10 週二 下午 10:09 <DIR> core 2017/01/10 週二 上午 01:31 <DIR> data 2017/01/10 週二 上午 11:45 <DIR> home 2016/11/02 週三 下午 09:42 <DIR> log 2016/11/02 週三 下午 09:41 0 __init__.py 1 個文件 0 字節 8 個目錄 33,612,865,536 可用字節 Process finished with exit code 0
寫到這裏感受已經快沒墨水了,若是有誰想作這個小項目的,但願個人博客與代碼思路能幫到你,就像我一臉懵比去參考別人的博客同樣。
ftp_client
|----bin(可執行目錄)
| |----__init__.py
| |----ftp_client.py(客戶端接口)
|----conf(配置文件目錄)
| |----__init__.py
| |----settings.py(配置文件)
|----core(核心代碼)
| |----__init__.py
| |----auth.py(客戶端身份驗證)
| |----cd.py(實現客戶端在服務隨意切換目錄的功能,但只能訪問本身的家目錄)
| |----get.py(客戶端下載功能)
| |----interactive.py(用於客戶端與服務端的交互/反射)
| |----ls.py(查看當前目錄下的文件(包括目錄))
| |----main.py(主函數,運行被ftp_client.py客戶端接口調用)
| |----mkdir.py(實現用戶在當前目錄下可建立目錄的功能)
| |----progress_bar.py(進度條:用於顯示上傳與下載的進度)
| |----put.py(處理客戶端上傳功能)
| |----pwd.py(查看用戶當前的目錄)
|----__init__.py
ftp_client.py
1 """ 2 客戶端接口 3 """ 4 5 import os,sys 6 7 8 dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 9 sys.path.insert(0, dir) 10 11 from core import main 12 13 if __name__ == "__main__": 14 main.run()
settings.py
1 """ 2 客戶端配置 3 """ 4 5 LOGIN_STATE = { 6 "auth_True":"200", #認證成功 7 "auth_False":"400", #認證失敗 8 "file_exit":"202", #文件存在 9 "file_no_exit":"402", #文件不存在 10 "cmd_right":"201", #命令正確 11 "cmd_error":"401", #命令錯誤 12 "dir_exit":"203", #目錄已存在 13 "dir_no_eixt":"403", #目錄不存在 14 "cmd_success": "204", # 命令成功執行 15 "cmd_fail": "404", # 命令執行失敗 16 "size_enough": "205", # 磁盤空間足夠 17 "size_empty": "405", # 磁盤空間不足 18 }
auth.py
1 """ 2 客戶端身份驗證 3 """ 4 from conf import settings 5 import hashlib 6 7 def send_auth(self): 8 """ 9 發送用戶名:密碼到服務端,進行登錄驗證 10 :param self: 11 :return: 返回400(成功)或200(失敗) 12 """ 13 user_name = input("Username:") 14 password = input("Password:") 15 #客戶端發送用戶名與密碼(列表) 16 password_hash = hash(password) 17 user_data = "%s:%s" % (user_name, password_hash) 18 self.client.send(user_data.encode()) 19 #接收服務端返回,認證成功True,認證失敗False 20 auth_recv = self.client.recv(1024).decode() 21 if auth_recv == settings.LOGIN_STATE["auth_True"]: 22 print("Welcome Login".center(50, "*")) 23 return auth_recv 24 elif auth_recv == settings.LOGIN_STATE["auth_False"]: 25 print("usename or password not exist") 26 return auth_recv 27 28 29 30 def hash(data): 31 #對密碼進行md5加密 32 m = hashlib.md5() 33 m.update(data.encode()) 34 #返回加密後的數據 35 return m.hexdigest()
cd.py
1 import json 2 from conf import settings 3 4 5 def client_cd(self, *args): 6 """實現客戶端在服務隨意切換目錄的功能,但只能訪問本身的家目錄""" 7 cmd_split = args[0].split() 8 if len(cmd_split) > 1: 9 cd_dir = cmd_split[1] 10 msg_dic = { # 爲了可拓展性,用字典形式 11 "action": "cd", # 發送給服務端的指令 12 "cd_dir": cd_dir, 13 } 14 self.client.send(json.dumps(msg_dic).encode()) 15 server_response = self.client.recv(1024) 16 print(server_response.decode()) 17 else: 18 print("%s:命令錯誤" % settings.LOGIN_STATE["cmd_error"])
get.py
1 import os,json 2 from conf import settings 3 from core import progress_bar 4 5 def client_get(self, *args): 6 """ 7 用來處理客戶端下載功能 8 """ 9 cmd_split = args[0].split() 10 if len(cmd_split) > 1: 11 filename = cmd_split[1] 12 msg_dic = { # 爲了可拓展性,用字典形式 13 "action": "get", # 發送給服務端的指令 14 "filename": filename, 15 "overridden": True 16 } 17 self.client.send(json.dumps(msg_dic).encode()) 18 # 防止粘包,等服務器確認 19 # 可優化,確認同時服務端看客戶端是否有權限等404 403(標準碼) 20 server_response = json.loads(self.client.recv(1024).decode()) 21 print(server_response,type(server_response)) 22 if server_response["file_exit"] == settings.LOGIN_STATE["file_exit"]: 23 self.client.send("客戶端已準備好下載".encode()) 24 if os.path.isfile(msg_dic["filename"]): #文件已經存在 25 f = open(filename + ".new", "wb") 26 else: 27 f = open(filename, "wb") 28 receive_size = 0 29 while receive_size < server_response["file_size"]: 30 data = self.client.recv(1024) 31 receive_size += len(data) 32 #調用progress_bar模塊的方法 33 progress_bar.progress_bar(self, receive_size, server_response["file_size"]) 34 f.write(data) 35 else: 36 print("download from server success") 37 38 elif server_response["file_exit"] == settings.LOGIN_STATE["file_no_exit"]: 39 print("%s:請求文件不存在" % server_response["file_exit"])
interactive.py
1 def interactive(self): 2 """ 3 本模塊用於客戶端與服務端的交互 4 """ 5 while True: 6 cmd = input(">>>:").strip() 7 if len(cmd) == 0: 8 continue 9 cmd_str = cmd.split()[0] # 指令 10 if hasattr(self, "cmd_%s" % cmd_str): # 反射 11 func = getattr(self, "cmd_%s" % cmd_str) #得到方法對應的內存地址 12 func(cmd) 13 else: 14 self.help()
ls.py
1 import json 2 3 4 def client_ls(self, *args): 5 """查看當前目錄下的文件(包括目錄)""" 6 cmd_split = args[0].split() 7 if len(cmd_split) == 1 and cmd_split[0] == "ls": 8 msg_dic = { 9 "action":"ls", 10 } 11 self.client.send(json.dumps(msg_dic).encode()) 12 server_response = self.client.recv(1024) 13 print(server_response.decode())
main.py
1 import socket 2 import hashlib 3 import os 4 import json 5 from core import interactive 6 from core import put 7 from core import get 8 from core import auth 9 from conf import settings 10 from core import pwd 11 from core import mkdir 12 from core import ls 13 from core import cd 14 15 class FtpClient(object): 16 def __init__(self): 17 self.client = socket.socket() 18 19 def help(self): 20 msg = """ 21 ls 22 pwd 23 cd ../.. 24 get filename 25 put filename 26 """ 27 print(msg) 28 29 def connect(self, ip, port): 30 self.client.connect((ip, port)) 31 32 33 def interactive(self): #交互 34 interactive.interactive(self 35 ) 36 37 def cmd_put(self, *args): #*args是爲了未來參數的拓展 38 put.client_put(self, *args) 39 40 def cmd_get(self, *args): 41 get.client_get(self, *args) 42 43 def send_auth_data(self): 44 #返回用戶賬號名與密碼 45 login_state = settings.LOGIN_STATE["auth_False"] 46 while login_state != settings.LOGIN_STATE["auth_True"]: 47 login_state = auth.send_auth(self) 48 49 def cmd_pwd(self, *args): 50 pwd.client_pwd(self, *args) 51 52 def cmd_mkdir(self, *args): 53 mkdir.client_mkdir(self, *args) 54 55 def cmd_ls(self, *args): 56 ls.client_ls(self, *args) 57 58 def cmd_cd(self, *args): 59 cd.client_cd(self, *args) 60 61 62 def run(): 63 ftp_client = FtpClient() 64 ftp_client.connect("localhost", 8787) 65 #身份驗證 66 ftp_client.send_auth_data() 67 #客戶端與服務端交戶 68 ftp_client.interactive()
mkdir.py
1 import json 2 from conf import settings 3 4 5 def client_mkdir(self, *args): 6 """實現用戶在當前目錄下可建立目錄的功能""" 7 cm_split = args[0].split() 8 if len(cm_split) > 1: 9 new_dir = cm_split[1] 10 msg_dic = { 11 "action":"mkdir", 12 "new_dir":new_dir, #將新建的目錄 13 "overriden":False, #已存在的目錄不可覆蓋 14 } 15 self.client.send(json.dumps(msg_dic).encode()) 16 server_response = self.client.recv(1024) 17 print(json.loads(server_response.decode())) 18 19 else: 20 print("%s:命令錯誤" % settings.LOGIN_STATE["cmd_error"])
progress_bar.py
1 import sys 2 3 4 def progress_bar(self, num, total): 5 """ 6 進度條:用於顯示上傳與下載的進度 7 :return: 無 8 """ 9 rate = num/total 10 rate_num = int(rate * 100) 11 r = "\r%s%d%%" % ("|" * rate_num, rate_num) #/r表示從新回到當前行輸出 12 sys.stdout.write(r) #輸出沒有換行符 13 sys.stdout.flush() #清空緩存
put.py
1 import os,json 2 from conf import settings 3 from core import progress_bar 4 5 6 def client_put(self, *args): 7 """ 8 用於處理客戶端上傳功能 9 """ 10 cmd_split = args[0].split() # 列表 11 if len(cmd_split) > 1: 12 filename = cmd_split[1] 13 if os.path.isfile(filename): # 判斷是否存在文件 14 file_size = os.stat(filename).st_size 15 msg_dic = { # 爲了可拓展性,用字典形式 16 "action": "put", # 發送給服務端的指令 17 "filename": filename, 18 "file_size": file_size, 19 "overridden": True 20 } 21 self.client.send(json.dumps(msg_dic).encode()) 22 # 防止粘包,等服務器確認 23 # 可優化,確認同時服務端看客戶端是否有權限等404 403(標準碼) 24 server_response = json.loads(self.client.recv(1024).decode()) 25 print(server_response) 26 if server_response == settings.LOGIN_STATE["file_exit"] or\ 27 server_response == settings.LOGIN_STATE["file_no_exit"]: 28 f = open(filename, "rb") 29 for line in f: # 上傳文件一行一行 30 self.client.send(line) 31 send_size = f.tell() #獲取當前指針位置(字節) 32 progress_bar.progress_bar(self, send_size, file_size) 33 else: 34 print("file upload success") 35 f.close() 36 #若是磁盤空間不足 37 elif server_response == settings.LOGIN_STATE["size_empty"]: 38 print("server_response:磁盤空間不足") 39 40 else: 41 print(filename, "is not exist")
pwd.py
1 import json 2 from conf import settings 3 4 def client_pwd(self, *args): 5 """用來查看用戶當前的目錄""" 6 cmd_split = args[0].split() 7 if len(cmd_split) == 1 and cmd_split[0] == "pwd": 8 msg_dic = { 9 "action":"pwd", 10 } 11 self.client.send(json.dumps(msg_dic).encode()) 12 server_response = json.loads(self.client.recv(1024).decode()) 13 print(server_response) 14 print(server_response["current_path"]) 15 else: 16 print("%s:命令錯誤" % settings.LOGIN_STATE["cmd_error"])
ftp_server
|----bin
| |----__init__.py
| |----ftp_server.py(服務端接口)
|----core
| |----__init__.py
| |----auth.py(用戶加密認證,登錄模塊)
| |----db_handle.py(讀用戶數據與寫用戶數據--感受這個模塊有點多餘~)
| |----deal_cd.py(處理用戶切換目錄的功能)
| |----deal_get.py(處理客戶端下載文件的請求)
| |----deal_ls.py(完成用戶顯示當前目錄下文件(包括目錄)的請求)
| |----deal_mkdir.py(處理用戶在當前目錄(家目錄下)建立目錄的請求)
| |----deal_put.py(處理客戶端上傳文件的請求)
| |----deal_pwd.py(用來處理客戶端查看當前目錄下的請求)
| |----get_dirisize.py(獲取用戶家目錄的大小(字節))
| |----main.py(主函數--運行時被ftp_server.py服務端接口調用)
|----data(用戶數據庫)
| |----__init__.py
| |----Alex.json(Alex用戶的數據庫)
| |----zcl.json(zcl用戶的數據庫)
|----home(home目錄,用來存放各用戶的家目錄)
| |----Alex(Alex的家目錄)
| |----zcl(zcl的家目錄)
| |----__init__.py
|----log(日誌--未拓展)
| |----__init__.py
|----__init__.py
ftp_server.py
1 import os,sys 2 3 dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 4 sys.path.insert(0, dir) 5 print(sys.path) 6 7 from core import main 8 9 if __name__ == "__main__": 10 main.run()
settings.py
1 """ 2 服務器配置 3 """ 4 import os 5 6 PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 7 HOME_PATH = os.path.join(PATH, "home") 8 9 #賬號名與密碼 10 USER_DATA = { 11 "zcl":"abc", 12 "Alex":"123" 13 } 14 15 LOGIN_STATE = { 16 "auth_True":"200", #認證成功 17 "auth_False":"400", #認證失敗 18 "file_exit":"202", #文件存在 19 "file_no_exit":"402", #文件不存在 20 "cmd_right":"201", #命令正確 21 "cmd_error":"401", #命令錯誤 22 "dir_exit":"203", #目錄已存在 23 "dir_no_exit":"403", #目錄不存在 24 "cmd_success":"204", #命令成功執行 25 "cmd_fail":"404", #命令執行失敗 26 "size_enough":"205", #磁盤空間足夠 27 "size_empty":"405", #磁盤空間不足 28 } 29 30 31 #磁盤配額:每一個用戶默認最多100M 32 MAX_SIZE = 102400000
auth.py
1 """ 2 用戶加密認證,登錄模塊 3 """ 4 import os,sys,hashlib 5 import json 6 from conf import settings 7 from core import db_handle 8 9 10 def auth_login(self): 11 """用戶登錄時調用""" 12 recv_data = self.request.recv(1024).strip() 13 recv_data = recv_data.decode() 14 print(recv_data,type(recv_data)) 15 recv_list = recv_data.split(":") 16 print(recv_list) 17 # 登錄後用戶當前目錄, 即用戶的家目錄 18 self.current_path = os.path.join(settings.HOME_PATH, recv_list[0]) 19 # 用戶宿主目錄 20 self.user_home_path = os.path.join(settings.HOME_PATH, recv_list[0]) 21 22 user_path = "%s/data/%s.json" % (settings.PATH, recv_list[0]) 23 print(user_path) 24 if os.path.isfile(user_path): 25 print("user(file) exist") 26 file_data = db_handle.user_load(user_path) 27 print(file_data) 28 if file_data["password"] == recv_list[1]: 29 print("login success") 30 #發送狀態碼給客戶端 31 self.request.send(settings.LOGIN_STATE["auth_True"].encode()) 32 print("send login_state") 33 #認證成功的狀態碼 34 return settings.LOGIN_STATE["auth_True"] 35 else: 36 #發送狀態碼給客戶端 37 self.request.send(settings.LOGIN_STATE["auth_False"].encode()) 38 print("send login_state") 39 #認證失敗的狀態碼 40 return settings.LOGIN_STATE["auth_False"] 41 else: 42 # 發送狀態碼給客戶端 43 self.request.send(settings.LOGIN_STATE["auth_False"].encode()) 44 print("send login_state") 45 print("False,please registe") 46 return settings.LOGIN_STATE["auth_False"] 47 48 49 def create_user(): 50 #服務端初始化時,先建立兩個用戶Alex,zcl 51 path = settings.PATH 52 for key in settings.USER_DATA: 53 print(settings.USER_DATA) 54 user_path = "%s/data/%s.json" % (path, key) 55 if not os.path.isfile(user_path): 56 password_hash = hash(settings.USER_DATA[key]) 57 user_data = { 58 "username":key, 59 "password":password_hash, 60 "user_path":os.path.join(settings.HOME_PATH, key), #建立同時添加用戶我的目錄 61 "max_size": settings.MAX_SIZE #磁盤配額100M 62 } 63 #json.dump(user_data, open(user_path,"w",encoding="utf-8")) 64 db_handle.user_dump(user_path, user_data) 65 user_mkdir() 66 67 68 def user_mkdir(): 69 """建立用戶我的目錄,在home目錄下""" 70 for key in settings.USER_DATA: 71 user_home_path = os.path.join(settings.HOME_PATH, key) 72 if not os.path.isdir(user_home_path): 73 os.popen("mkdir %s" % user_home_path) 74 75 76 def hash(data): 77 m = hashlib.md5() 78 m.update(data.encode()) 79 #返回加密後的數據 80 return m.hexdigest()
db_handle.py
1 """ 2 讀用戶數據與寫用戶數據 3 """ 4 import json 5 from conf import settings 6 7 8 def user_load(user_path): 9 """ 10 讀用戶數據 11 :param user_path:讀出的路徑 12 :return: 用戶數據字典 13 """ 14 user_data = json.load(open(user_path, "r", encoding="utf-8")) 15 return user_data 16 17 18 def user_dump(user_path, user_data): 19 """ 20 將數據寫到用戶數據庫 21 :param user_path:寫入的路徑 22 :param user_data: 要寫入的數據 23 :return: 24 """ 25 json.dump(user_data, open(user_path, "w", encoding="utf-8"))
deal_cd.py
1 import json,os 2 from conf import settings 3 4 def server_deal_cd(self, *args): 5 """處理用戶切換目錄的功能""" 6 cmd_dic = args[0] 7 cd_dir = cmd_dic["cd_dir"] 8 dir_path = self.current_path + r"%s" % cd_dir 9 if cd_dir == ".." and len(self.current_path) > len(self.user_home_path): 10 #返回上一級目錄 11 self.request.send(json.dumps(settings.LOGIN_STATE["cmd_success"]).encode()) 12 self.current_path = os.path.dirname(self.current_path) 13 elif os.path.isdir(dir_path): #切換目錄 14 if cd_dir != "." and cd_dir != "..": 15 self.request.send(json.dumps(settings.LOGIN_STATE["cmd_success"]).encode()) 16 self.current_path = self.current_path + r"%s" % cd_dir 17 print(self.current_path) 18 else: 19 self.request.send(json.dumps(settings.LOGIN_STATE["cmd_fail"]).encode()) 20 else: #切換的目錄不存在 21 self.request.send(json.dumps(settings.LOGIN_STATE["dir_no_exit"]).encode())
deal_get.py
1 import os,json 2 from conf import settings 3 4 def server_deal_get(self, *args): 5 """處理客戶端下載文件的請求""" 6 cmd_dic = args[0] 7 filename = cmd_dic["filename"] 8 if os.path.isfile(filename): 9 file_size = os.stat(filename).st_size #服務端文件大小 10 msg_dic = { 11 "file_size":file_size, #服務端將發給客戶端的文件的大小 12 "file_exit":settings.LOGIN_STATE["file_exit"] 13 } 14 self.request.send(json.dumps(msg_dic).encode()) 15 #防止粘包,服務端與客戶端再進行一次交互 16 client_response = self.request.recv(1024) 17 print(client_response.decode()) 18 f = open(filename, "rb") 19 for line in f: 20 self.request.send(line) 21 else: 22 print("server:file upload to client success") 23 else: 24 msg_dic = {"file_exit":settings.LOGIN_STATE["file_no_exit"]} 25 self.request.send(json.dumps(msg_dic).encode())
deal_ls.py
1 import os,json 2 3 def server_deal_ls(self, *args): 4 """完成用戶顯示當前目錄下文件(包括目錄)的請求""" 5 cmd_dic = args[0] 6 r = os.popen("dir %s" % self.current_path) 7 dir_message = r.read() 8 self.request.send(dir_message.encode())
deal_mkdir.py
1 import json,os 2 from conf import settings 3 4 5 def server_deal_mkdir(self, *args): 6 """處理用戶在當前目錄(家目錄下)建立目錄的請求""" 7 cmd_dir = args[0] 8 new_dir = cmd_dir["new_dir"] #當前目錄在將建立的目錄 9 new_dir_path = os.path.join(self.current_path, new_dir) 10 print(new_dir_path) 11 if not os.path.isdir(new_dir_path): 12 #不存在目錄,則建立 13 print("new_dir no exit") 14 os.popen("mkdir %s" % new_dir_path) 15 msg_dic = { 16 "cmd_state":settings.LOGIN_STATE["cmd_success"], 17 } 18 self.request.send(json.dumps(msg_dic).encode()) 19 else: 20 msg = "%s:目錄已存在,請先刪除再建立" % settings.LOGIN_STATE["dir_exit"] 21 self.request.send(json.dumps(msg).encode())
deal_put.py
1 import os,json 2 from conf import settings 3 from core import get_dirsize 4 5 6 def server_deal_put(self, *args): 7 """處理客戶端上傳文件的請求""" 8 cmd_dic = args[0] #字典格式 9 filename = cmd_dic["filename"] 10 file_size = cmd_dic["file_size"] 11 file_path = os.path.join(self.current_path, filename) 12 print("AA") 13 dir_size = get_dirsize.get_dirsize(self.user_home_path) 14 print("BB") 15 print("當前用戶磁盤空間大小:%s" % dir_size) 16 #若是用戶家目錄下的大小加上本次將上傳文件的大小仍小於最大的磁盤配額,則能夠繼續上傳 17 if dir_size+file_size < settings.MAX_SIZE: 18 19 if os.path.isfile(file_path): # 若是文件已經存在 20 f = open(file_path + ".new", "wb") 21 # 交互,防止粘包 22 self.request.send(json.dumps(settings.LOGIN_STATE["file_exit"]).encode()) 23 else: # 若是不存在,就上傳 24 f = open(file_path, "wb") 25 self.request.send(json.dumps(settings.LOGIN_STATE["file_no_exit"]).encode()) 26 27 #self.request.send(b"200, OK") # 可優化成字典json,狀態碼 28 # 開始接收數據 29 received_size = 0 30 while received_size < file_size: 31 data = self.request.recv(1024) 32 received_size += len(data) 33 f.write(data) 34 else: # 文件上傳完成 35 print("file [%s] has uploaded..." % filename) 36 37 else: 38 #磁盤配額不足 39 self.request.send(json.dumps(settings.LOGIN_STATE["size_empty"]).encode())
deal_pwd.py
1 import json 2 from conf import settings 3 4 5 def server_deal_pwd(self, *args): 6 """ 用來處理客戶端查看當前目錄下的請求""" 7 cmd_dic = args[0] # 字典格式 8 msg_dic = { 9 "current_path":self.current_path, #發送當前目錄 10 "cmd_state":settings.LOGIN_STATE["cmd_right"] #發送命令狀態 11 } 12 self.request.send(json.dumps(msg_dic).encode())
get_dirsize.py
1 import os 2 3 4 def get_dirsize(dir): 5 """ 6 獲取目錄的大小 7 :param dir: 目錄的路徑 8 :return: 大小(字節) 9 """ 10 size = 0 11 for root, dirs, files in os.walk(dir): 12 size += sum([os.path.getsize(os.path.join(root, name)) for name in files]) 13 return size
main.py
1 import socketserver 2 import json,os 3 from core import deal_put 4 from core import deal_get 5 from core import deal_pwd 6 from core import deal_mkdir 7 from core import deal_cd 8 from core import deal_ls 9 from core import auth 10 from conf import settings 11 12 13 class MyTCPHandler(socketserver.BaseRequestHandler): #繼承父類 14 def put(self, *args): 15 deal_put.server_deal_put(self, *args) 16 17 def get(self, *args): 18 deal_get.server_deal_get(self, *args) 19 20 def pwd(self, *args): 21 deal_pwd.server_deal_pwd(self, *args) 22 23 def mkdir(self, *args): 24 deal_mkdir.server_deal_mkdir(self, *args) 25 26 def ls(self, *args): 27 deal_ls.server_deal_ls(self, *args) 28 29 def cd(self, *args): 30 deal_cd.server_deal_cd(self, *args) 31 32 def handle(self): 33 while True: 34 #用戶認證 35 login_state = auth.auth_login(self) 36 #若是認證成功 37 if login_state == settings.LOGIN_STATE["auth_True"]: 38 while True: 39 try: 40 self.data = self.request.recv(1024).strip() 41 42 print("{} wrote:".format(self.client_address[0])) 43 print(self.data) 44 cmd_dic = json.loads(self.data.decode()) #字典格式 45 action = cmd_dic["action"] #獲取客戶端指令 46 47 if hasattr(self, action): #反射 48 func = getattr(self, action) 49 func(cmd_dic) #參數爲字典格式 50 else: 51 print("服務端反射調用失敗") 52 53 except ConnectionResetError as e: 54 print("客戶端斷開",e) 55 break 56 elif login_state == settings.LOGIN_STATE["auth_False"]: 57 continue 58 59 60 def run(): 61 #程序初始化時建立用戶 62 auth.create_user() 63 #用戶登錄時身份驗證 64 HOST, PORT = "localhost", 8787 65 server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler) #實例化 66 server.serve_forever()
一些測試用的輸出爲了方便查BUG我沒去除~~有點懶~
ftp_client_1:
C:\Python34\python3.exe C:/Users/Administrator/PycharmProjects/laonanhai/ftp/ftp_client/bin/ftp_client.py Username:zcl Password:abc ******************Welcome Login******************* >>>:get video.mp4 {'file_exit': '202', 'file_size': 31491127} <class 'dict'> ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||100%download from server success >>>:cd \aa\ff "204" >>>:pwd {'current_path': 'C:\\Users\\Administrator\\PycharmProjects\\laonanhai\\ftp\\ftp_server\\home\\zcl\\aa\\ff', 'cmd_state': '201'} C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_server\home\zcl\aa\ff >>>:put jita.mp4 402 ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||100%file upload success >>>:get video.mp4 {'file_exit': '202', 'file_size': 31491127} <class 'dict'> ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||100%download from server success >>>:pwd {'current_path': 'C:\\Users\\Administrator\\PycharmProjects\\laonanhai\\ftp\\ftp_server\\home\\zcl\\aa\\ff', 'cmd_state': '201'} C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_server\home\zcl\aa\ff >>>:cd .. "204" >>>:pwd {'current_path': 'C:\\Users\\Administrator\\PycharmProjects\\laonanhai\\ftp\\ftp_server\\home\\zcl\\aa', 'cmd_state': '201'} C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_server\home\zcl\aa >>>:ls 驅動器 C 中的卷沒有標籤。 卷的序列號是 000C-3580 C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_server\home\zcl\aa 的目錄 2017/01/23 週一 下午 12:48 <DIR> . 2017/01/23 週一 下午 12:48 <DIR> .. 2017/01/22 週日 下午 01:24 <DIR> ee 2017/01/23 週一 下午 01:02 <DIR> ff 2017/01/23 週一 下午 12:40 <DIR> gg 0 個文件 0 字節 5 個目錄 34,504,105,984 可用字節 >>>:mkdir hh {'cmd_state': '204'} >>>:ls 驅動器 C 中的卷沒有標籤。 卷的序列號是 000C-3580 C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_server\home\zcl\aa 的目錄 2017/01/23 週一 下午 01:06 <DIR> . 2017/01/23 週一 下午 01:06 <DIR> .. 2017/01/22 週日 下午 01:24 <DIR> ee 2017/01/23 週一 下午 01:02 <DIR> ff 2017/01/23 週一 下午 12:40 <DIR> gg 2017/01/23 週一 下午 01:06 <DIR> hh 0 個文件 0 字節 6 個目錄 34,503,962,624 可用字節 >>>:cd .. "204" >>>:owd ls pwd cd ../.. get filename put filename >>>:pwd {'current_path': 'C:\\Users\\Administrator\\PycharmProjects\\laonanhai\\ftp\\ftp_server\\home\\zcl', 'cmd_state': '201'} C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_server\home\zcl >>>:cd .. "404" >>>:
ftp_client_2:
C:\Python34\python3.exe C:/Users/Administrator/PycharmProjects/laonanhai/ftp/ftp_client/bin/ftp_client.py Username:Alex Password:123 ******************Welcome Login******************* >>>:pwd {'cmd_state': '201', 'current_path': 'C:\\Users\\Administrator\\PycharmProjects\\laonanhai\\ftp\\ftp_server\\home\\Alex'} C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_server\home\Alex >>>:
ftp_server:
C:\Python34\python3.exe C:/Users/Administrator/PycharmProjects/laonanhai/ftp/ftp_server/bin/ftp_server.py ['C:\\Users\\Administrator\\PycharmProjects\\laonanhai\\ftp\\ftp_server', 'C:\\Users\\Administrator\\PycharmProjects\\laonanhai\\ftp\\ftp_server\\bin', 'C:\\Python34\\lib\\site-packages\\pip-8.1.2-py3.4.egg', 'C:\\Users\\Administrator\\PycharmProjects\\laonanhai', 'C:\\Windows\\SYSTEM32\\python34.zip', 'C:\\Python34\\DLLs', 'C:\\Python34\\lib', 'C:\\Python34', 'C:\\Python34\\lib\\site-packages'] {'zcl': 'abc', 'Alex': '123'} {'zcl': 'abc', 'Alex': '123'} zcl:900150983cd24fb0d6963f7d28e17f72 <class 'str'> ['zcl', '900150983cd24fb0d6963f7d28e17f72'] C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_server/data/zcl.json user(file) exist {'max_size': 102400000, 'user_path': 'C:\\Users\\Administrator\\PycharmProjects\\laonanhai\\ftp\\ftp_server\\home\\zcl', 'password': '900150983cd24fb0d6963f7d28e17f72', 'username': 'zcl'} login success send login_state 127.0.0.1 wrote: b'{"filename": "video.mp4", "overridden": true, "action": "get"}' 客戶端已準備好下載 server:file upload to client success 127.0.0.1 wrote: b'{"cd_dir": "\\\\aa\\\\ff", "action": "cd"}' C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_server\home\zcl\aa\ff 127.0.0.1 wrote: b'{"action": "pwd"}' 127.0.0.1 wrote: b'{"filename": "jita.mp4", "file_size": 8386449, "overridden": true, "action": "put"}' AA BB 當前用戶磁盤空間大小:25159347 file [jita.mp4] has uploaded... 127.0.0.1 wrote: b'{"filename": "video.mp4", "overridden": true, "action": "get"}' 客戶端已準備好下載 server:file upload to client success 127.0.0.1 wrote: b'{"action": "pwd"}' 127.0.0.1 wrote: b'{"cd_dir": "..", "action": "cd"}' 127.0.0.1 wrote: b'{"action": "pwd"}' 127.0.0.1 wrote: b'{"action": "ls"}' 127.0.0.1 wrote: b'{"new_dir": "hh", "overriden": false, "action": "mkdir"}' C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_server\home\zcl\aa\hh new_dir no exit 127.0.0.1 wrote: b'{"action": "ls"}' 127.0.0.1 wrote: b'{"cd_dir": "..", "action": "cd"}' 127.0.0.1 wrote: b'{"action": "pwd"}' 127.0.0.1 wrote: b'{"cd_dir": "..", "action": "cd"}' Alex:202cb962ac59075b964b07152d234b70 <class 'str'> ['Alex', '202cb962ac59075b964b07152d234b70'] C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_server/data/Alex.json user(file) exist {'max_size': 102400000, 'user_path': 'C:\\Users\\Administrator\\PycharmProjects\\laonanhai\\ftp\\ftp_server\\home\\Alex', 'password': '202cb962ac59075b964b07152d234b70', 'username': 'Alex'} login success send login_state 127.0.0.1 wrote: b'{"action": "pwd"}'