轉發註明出處:http://www.cnblogs.com/0zcl/p/6259128.html,這次博客寫了很久~~ 一、需求 1. 用戶加密認證 (完成)2. 允許同時多用戶登錄 (完成)3. 每個用戶有自己的家目錄 ,且只能訪問自己的家目錄(完成)4. 對用戶進行磁碟配額,每個用戶的可用空間 ...
轉發註明出處:http://www.cnblogs.com/0zcl/p/6259128.html,這次博客寫了很久~~
一、需求
1. 用戶加密認證 (完成)
2. 允許同時多用戶登錄 (完成)
3. 每個用戶有自己的家目錄 ,且只能訪問自己的家目錄(完成)
4. 對用戶進行磁碟配額,每個用戶的可用空間不同(完成)
5. 允許用戶在ftp server上隨意切換目錄cd(完成)
6. 允許用戶查看當前目錄下文件ls(完成)
7. 允許上傳put和下載get文件(完成),保證文件一致性(此需求不做)
8. 文件傳輸過程中顯示進度條(完成)
附加功能:
1.新建目錄mkdir(完成)
2.查看當前工作目錄的路徑pwd(完成)
3.支持文件的斷點續傳(未完成)
二、程式目錄結構
客戶端:
服務端:
三、README
重要!
一、需求 1. 用戶加密認證 (完成) 2. 允許同時多用戶登錄 (完成) 3. 每個用戶有自己的家目錄 ,且只能訪問自己的家目錄(完成) 4. 對用戶進行磁碟配額,每個用戶的可用空間不同(完成) 5. 允許用戶在ftp server上隨意切換目錄cd(完成) 6. 允許用戶查看當前目錄下文件ls(完成) 7. 允許上傳put和下載get文件(完成),保證文件一致性(此需求不做) 8. 文件傳輸過程中顯示進度條(完成) 附加功能: 1.新建目錄mkdir(完成) 2.查看當前工作目錄的路徑pwd(完成) 3.支持文件的斷點續傳(未完成) 二、目錄結構及模塊功能解釋 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 三、狀態碼 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", #磁碟空間不足 } 四、功能解釋 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 判斷宿主目錄磁碟空間是否夠用,可以,上傳文件;否則,報錯.View Code
四、需求分析
做這個小項目之前,如果基礎知識不牢的話,可以看我之前的兩篇博客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])
需求5、6、附加功能1、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:是在我做下載功能的時候遇到的。很奇葩差點懷穎人生。先看下代碼:
客戶端:
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())View Code
服務端:
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())View Code
實現客戶端下載服務端文伯功能。首先客戶端輸入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 1View Code
服務端(下麵代碼嫌多可以只看我加紅的字體):
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)View Code
輸出:
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 0View Code
說明服務端接收到的是“客戶端已準備好下載”,而不是含對應動作(get)的字典!!進一步證明我猜想的是對的!如何解決這個BUG,很簡單,客戶端只要對從服務端收到的狀態碼(文件存在402;服務端文件不存在則發送狀態碼403)進行分開討論就可以解決!!
坑2: 個人覺得坑1很坑爹,我已經寫得很詳細了,還是怕你看不懂
下麵寫一個簡單的吧,放鬆一下:
想實現切換目錄,感覺得先實現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 0View Code
六、源代碼與模塊作用
寫到這裡感覺已經快沒墨水了,如果有誰想做這個小項目的,希望我的博客與代碼思路能幫到你,就像我一臉懵比去參考別人的博客一樣。
ftp_client
|----bin(可執行目錄)
| |----__init__.py
| |----ftp_client.py(客戶端介面)
|----conf(配置文件目錄)
| |----__init__.py
| |----settings.py(配置文件)
|----core(核心代碼)
| |----__init__.py
| |----auth.py(客戶端身份驗證)
| |----cd.py(實現客戶端在服務隨意切換目錄的功能,但只能訪問自己的家目錄)
| |----get.py(客戶端下載功能)