python之FTP程式(支持多用戶線上)

来源:http://www.cnblogs.com/0zcl/archive/2017/01/23/6259128.html
-Advertisement-
Play Games

轉發註明出處: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 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_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 0
View 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 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(客戶端下載功能)

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、表單提交,並將其提交到本頁 (1) form 屬性method為post方法,修改路由,使其接收post、get的請求 (2)LoginController.php 修改login方法,根據不同的請求返回不同的內容 如果請求方法為get ,返回登陸頁面;如果請求為post,就行驗證 刷新頁面,如 ...
  • 1、載入驗證碼類,並驗證 (1)下載定義好的code驗證碼類,放置到resources目錄下 (2) 添加路由 (3)添加方法 LoginController.php 註意: 不要忘記引入Code類,不加‘\’,會引入App\Http\Controllers\Admin\Code 驗證: 2、$_S ...
  • (1)項目下.env 是配置資料庫的文件 分別寫入資料庫伺服器地址、所連接的資料庫名、表首碼、用戶名、密碼 其中表首碼DB_PREFIX 欄位預設沒有,需要在config/database.php添加 (2)驗證是否成功 創建一個控制器 php artisan make:controller Ind ...
  • 1.時間模塊 1 import time 2 import datetime 3 4 #CPU真正運行的時間 5 #print(time.clock()) 6 7 #返回與utc時間的時間差,以秒計算 8 #print(time.altzone) 9 10 #返回時間格式"Mon Jan 23 14 ...
  • 項目發起於2015年9月,最初的需求微信公眾號預約上門洗車可線上支付和當面現金支付。之前他們都是用電話預約上門洗車。 微信預約:使用技術後臺spring+struts2+jdbc,前臺主要是jquerymobile+bootstrap。圖標使用的是阿裡的iconfont。 15年10月正式上線,後來 ...
  • 數組保存的是一組有順序的、具有相同類型的數據。 1、創建: 數組的聲明格式: int arrary[]; int [] array1, array2; //同時聲明多個數組。 上面的語句只是對數組進行了聲明,還沒有對其分配記憶體,不可存放、訪問。Java中數組可以看做是一種特殊的對象,可用new對數組 ...
  • 高級語言程式設計報告 列印版報告截止上交日期:2014年11 月 15 日 電子版報告發至[email protected], 郵件標題寫明報告次數序號姓名 序號 34 姓名 許愷 照片 成績 E-MAIL及電話 18810556775 實習題目 第一次作業: 函數 任務六 一、 代碼及註釋 //編 ...
  • Spring boot是Spring推出的一個輕量化web框架,主要解決了Spring對於小型項目飽受詬病的配置和開發速度問題。 Spring Boot 包含的特性如下: 創建可以獨立運行的 Spring 應用。 直接嵌入 Tomcat 或 Jetty 伺服器,不需要部署 WAR 文件。 提供推薦的 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...