python連接遠程服務器,python之FTP程序(支持多用戶在線)

 2023-10-08 阅读 29 评论 0

摘要:轉發注明出處:http://www.cnblogs.com/0zcl/p/6259128.html,這次博客寫了很久~~ python連接遠程服務器。? 一、需求 1. 用戶加密認證 (完成)2. 允許同時多用戶登錄 (完成)3. 每個用戶有自己的家目錄 ,且只能訪問自己的家目錄(完成)4. 對用戶進行磁盤配額,

轉發注明出處:http://www.cnblogs.com/0zcl/p/6259128.html,這次博客寫了很久~~

python連接遠程服務器。?

一、需求

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__.pyftp_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.查看當前目錄完整路徑: pwd7.查看當前路徑下的文件名和目錄名: ls8.下載文件(不可續傳):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 runftp_client.interactive()File "C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_client\core\main.py", line 31, in interactiveinteractive.interactive(self)File "C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_client\core\interactive.py", line 14, in interactivefunc(cmd)File "C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_client\core\main.py", line 38, in cmd_getget.client_get(self, *args)File "C:\Users\Administrator\PycharmProjects\laonanhai\ftp\ftp_client\core\get.py", line 20, in client_getserver_response = self.client.recv(1024).decode()
ConnectionAbortedError: [WinError 10053] 你的主機中的軟件中止了一個已建立的連接。Process finished with exit code 1
View 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_threadself.finish_request(request, client_address)File "C:\Python34\lib\socketserver.py", line 344, in finish_requestself.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 handlecmd_dic = json.loads(self.data.decode())   #字典格式File "C:\Python34\lib\json\__init__.py", line 318, in loadsreturn _default_decoder.decode(s)File "C:\Python34\lib\json\decoder.py", line 343, in decodeobj, end = self.raw_decode(s, idx=_w(s, 0).end())File "C:\Python34\lib\json\decoder.py", line 361, in raw_decoderaise 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 0
View Code

說明服務端接收到的是“客戶端已準備好下載”,而不是含對應動作(get)的字典!!進一步證明我猜想的是對的!如何解決這個BUG,很簡單,客戶端只要對從服務端收到的狀態碼(文件存在402;服務端文件不存在則發送狀態碼403)進行分開討論就可以解決!!

?

坑2: 個人覺得坑1很坑爹,我已經寫得很詳細了,還是怕你看不懂

下面寫一個簡單的吧,放松一下:

想實現切換目錄,感覺得先實現ls,顯示當前目錄下的文件及目錄較好,不然連當前目錄下有什么目錄都不知道,還怎么切換目錄!如何查看當前目錄(家目錄)下的目錄及文件?? 請看下面代碼:

r=os.popen("dir%s"%BASE_DIR)
print(r.read())

輸出:

 驅動器 C 中的卷沒有標簽。卷的序列號是 000C-3580C:\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__.py1 個文件              0 字節8 個目錄 33,612,865,536 可用字節Process finished with exit code 0
View Code

?

?

六、源代碼與模塊作用

寫到這里感覺已經快沒墨水了,如果有誰想做這個小項目的,希望我的博客與代碼思路能幫到你,就像我一臉懵比去參考別人的博客一樣。

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()
View Code

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 }
View Code

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()
View Code

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"])
View Code

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"])
View Code

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()
View Code

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())
View Code

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()
View Code

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"])
View Code

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()   #清空緩存
View Code

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")
View Code

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"])
View Code

?

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()
View Code

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
View Code

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()
View Code

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"))
View Code

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())
View Code

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())
View Code

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())
View Code

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())
View Code

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())
View Code

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())
View Code

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
View Code

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()
View Code

?

七、測試

一些測試用的輸出為了方便查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-3580C:\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>          gg0 個文件              0 字節5 個目錄 34,504,105,984 可用字節>>>:mkdir hh
{'cmd_state': '204'}
>>>:ls驅動器 C 中的卷沒有標簽。卷的序列號是 000C-3580C:\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>          hh0 個文件              0 字節6 個目錄 34,503,962,624 可用字節>>>:cd ..
"204"
>>>:owdlspwdcd ../..get filenameput 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"
>>>:
View Code

?

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
>>>:
View Code

?

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"}'
View Code

?

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/2/131491.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息