python 實現簡單的FTP

来源:https://www.cnblogs.com/caesar-id/archive/2019/12/27/12105321.html
-Advertisement-
Play Games

socketserver、struct模塊練習,簡易的FTP ...


一、開發環境

server端:centos 7  python-3.6.2

客戶端:Windows 7 python-3.6.2 pycharm-2018

程式目的:1、學習使用socketserver實現併發處理多個客戶端。

             2、瞭解使用struct解決TCP粘包。

二、程式設計

(本人菜鳥一枚,對於開發規範,介面設計完全不懂,完全是隨心所欲,自娛自樂。寫博客主要是記錄自己學習的點點滴滴,如有不足之處還請見諒。)

1、server端

1.1 目錄結構如下:

 1.2 目錄簡介:

FTP_SERVER:程式主目錄

app:程式主邏輯目錄,目錄下有四個模塊:

          FTPserver.py:FTP  Server端啟動入口。

          login.py:認證註冊模塊,用於處理用戶註冊,登錄認證。

          dataAnalysis.py:命令解析模塊,負責解析,執行客戶端命令。

          FileOpertion.py:負責文件讀,寫。數據發送,數據接收。

db:存放user_pwd.db文件,用於存放用戶信息(用戶名,密碼,FTP目錄總空間,已使用空間等)

lib:存放公共數據。

1.3 模塊中類的繼承關係

1.4 執行流程

1.4.1 程式啟動文件FTPserver.py,程式啟動後進入監聽狀態。核心代碼如下:

class MyFtpServer(socketserver.BaseRequestHandler):
    
    def handle(self):  # 重寫handle方法,處理socket請求
        print(f"連接來自{self.client_address}的客戶端")
        commom_obj = Commom()
        data_analy = DataAnalysis()
        login_obj = Login()
        while 1:
            # 執行用戶選項:1、登陸系統 2、註冊賬號。並返回一個結果
            status_id = login_obj.run_client_choice(self.request, commom_obj)
            if status_id == "01":  # 登陸成功
                if not self.run_ftp_server(data_analy,commom_obj):  # 執行ftpserver主功能
                    break
            elif int(status_id) == -1: # client斷開連接了
                break
        print(f"客戶端{self.client_address}斷開了連接")

    def run_ftp_server(self,data_analy,commom_obj):
        """"
        登陸成功後,接收客戶端發來的命令,併進行處理
        :param data_analy:負責解析,執行客戶端命令的對象
        :param commom_obj:程式執行時所需的數據對象
        :return 返回false代表客戶端斷開連接了
        """
        while True:
            try:
                cmd_len_pack = self.request.recv(4)  
                cmd_len = struct.unpack('i',cmd_len_pack)[0] # 獲取命令長度,防止粘包  
            except Exception:
                break
            recv_data = self.request.recv(cmd_len).decode('utf-8')  # 接收客戶端數據
            if recv_data.upper() == "Q":  # 客戶端提出斷開連接了
                break
            # 解析,處理客戶端的命令
            data_analy.syntax_analysis(recv_data, self.request, commom_obj)
        return False

if __name__ == '__main__':
    print('運行FTP服務')
    ip_port = ('192.168.10.10',9000)
    # 創建併發服務端對象
    server = socketserver.ThreadingTCPServer(ip_port, MyFtpServer)
    # 開啟服務
    server.serve_forever()

 1.4.2 服務端進入監聽狀態後,客戶端發起連接請求,服務端接收連接請求後會等待客戶單發來狀態碼,1表示請求登錄FTP伺服器,2表示客戶端要註冊用戶,註冊用戶需要服務端手動反饋狀態碼1才可註冊。處理用戶登錄,註冊模塊login.py核心代碼如下:

class Login(FileOperation):
    """
    登陸註冊類。主要負責用戶的登陸認證,和用戶註冊。
    """
    def run_client_choice(self,socket_obj,commom):
        """
        獲取客戶端的請求,1是登陸,2是註冊用戶
        :param socket_obj: socket對象
        :param commom: ftpserver運行時所需要的數據對象
        :return:
        """
        recv_choice = socket_obj.recv(1).decode("utf-8")  # 獲取用戶選項:1是登陸,2是註冊用戶
        if recv_choice == "1":  # client請求登陸
            return self.login_authen(socket_obj,commom)
        elif recv_choice == "2":  # client請求註冊賬號
            return self.register_user(socket_obj,commom)
        else:
            return -1  # client斷開連接了

    # 用戶登陸認證
    def login_authen(self,socket_obj,commom):
        """
        客戶端登陸認證
        :param socket_obj:  socket對象
        :param commom:  ftpserver運行時需要的數據對象
        :return:返回1代表登陸成功
        """
        # 接收client發來的用戶名,密碼
        recv_userPwd = self.recv_data(socket_obj).decode("utf-8").split("|")  
        # 效驗用戶名密碼
        check_ret = self.check_user_pwd(recv_userPwd, socket_obj,commom)
        if check_ret:  # 用戶名密碼正確
            self.check_user_home_dir(commom,recv_userPwd[0]) # 檢測用戶家目錄
            return commom.status_info["login_success"]
        else:
            return commom.status_info["login_fail"]

   ...

    # 註冊用戶
    def register_user(self,socket_obj,commom):
        """
        :param socket_obj:
        :param commom:
        :return: 返回是否允許註冊的結果,1允許客戶端註冊,2拒絕客戶端註冊
        """
        while True:
            choice_id = input("請輸入回應碼:1是允許註冊,2是不允許註冊:")
            if choice_id.isdigit() and 3 > int(choice_id) > 0:
                socket_obj.send(choice_id.encode("utf-8"))  # 發通知告知客戶端,處理結果
                if choice_id == "1":  # 註冊用戶
                    return self.client_register(socket_obj, commom)
                return choice_id
            else:
                print("您輸入的信息有誤,請重新輸入。")

   ...

 1.4.3 客戶端登錄成功後,服務端會等待接收客戶端發來的命令,命令的解析,執行由dataAnalysis.py模塊執行,核心代碼如下:

class DataAnalysis(FileOperation):
    """
    數據分析處理類,主要負責解析client發送過來的指令。
    """
    def syntax_analysis(self,recv_data, socket_obj, commom):
        """
        負責解析客戶端傳來的數據。
        :param recv_data:接收到的客戶端用戶數據
        :param socket_obj:socket對象
        :param commom:數據對象
        :return:
        """
        clientData = recv_data.split(" ")
        if hasattr(self,clientData[0]):  # 判斷對象方法是否存在
            get_fun = getattr(self,clientData[0])#獲取對象方法
            get_fun(clientData,socket_obj,commom)  # 運行對象方法
        else:
            pass
    ...

執行客戶端命令後,繼續等待接收客戶端發來的命令,如此迴圈...。

2、客戶端

2.1 目錄結構如下:

2.2 目錄簡介:

client:程式主目錄。

bin:程式入口,程式啟動文件main.py用於建立socket連接,然後調用FTPclient.py模塊下的run_ftp_client方法運行程式。

app:程式主邏輯,目錄下有四個模塊如下:

          FTPclient.py:FTP客戶端,根據用戶選項,執行用戶指令。

          login.py:認證註冊模塊,用於處理用戶註冊,登錄認證。

          dataAnalysis.py:命令解析模塊,解析用戶輸入的命令,發給服務端獲取結果。

          FileOpertion.py:負責文件讀,寫。

lib:存放公共數據,有兩個文件:

       commom.py:主要存放的是公共變數。

       help.txt:存放的是幫助文檔,當用戶執行help命令時會調用該文件。

2.3 模塊中類的繼承關係

2.4 執行流程

2.4.1 程式入口main.py,啟動後會與FTP服務端建立連接,與服務端連接成功後會調用FTPclient.py模塊下的run_ftp_client方法,執行用戶功能。核心代碼如下:

socket_obj = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
socket_obj.connect(("192.168.10.10",9000))

client_obj = Client()
client_obj.run_ftp_client(socket_obj)  # 接收用戶輸入的選項,執行對應的功能

2.4.2 FTPclient.py模塊下的run_ftp_client方法會列印菜單,並等待用戶輸入選項,執行相應功能,核心代碼如下:

class Client(Login,DataAnalysis):

    def run_ftp_client(self,socket_obj):
        """
        運行用戶輸入的選項:1、是登陸  2、是註冊賬號
        :return:
        """
        while True:
            self.login_menu()  # 列印系統菜單
            choice_id = self.get_user_choice()  # 獲取用戶輸入的選項
            if choice_id:
                if self.run_user_choice(choice_id,socket_obj):
                    break
            else:
                print("您輸入的有誤")
    def get_user_choice(self):
        """
        獲取用戶輸入的選項
        :return:
        """
        choice_id = input("請輸入選項:")
        if choice_id.isdigit() and 4 > int(choice_id) > 0 or choice_id.upper() == "Q":
            return choice_id
        return False
    def run_user_choice(self,choice_id,socket_obj):
        if choice_id == "1":  # 登陸系統
            socket_obj.send(choice_id.encode("utf-8"))  # 發通知告知伺服器準備登陸
            if self.run_login(socket_obj) == True:  # 執行登陸
                return True
        elif choice_id == "2":  # 註冊用戶
            socket_obj.send(choice_id.encode("utf-8"))  # 請求伺服器,註冊用戶
            self.register_user(socket_obj)  # 執行註冊
        elif choice_id.upper() == "Q":  # 退出程式
            socket_obj.send(choice_id.encode("utf-8"))  # 通知伺服器,準備退出程式
            socket_obj.close()
            print("程式正常退出")
            return True

    def run_login(self,socket_obj,):
        """
        運行登陸認證模塊,如果登陸成功執行程式主邏輯,否則重新登陸。
        :param socket_obj:
        :return:
        """
        if self.login_authention(socket_obj):
            while True:
                send_data = input(">>>").strip(" ")  # 獲取發送數據(用戶執行的命令)
                if send_data.upper() == "Q":  # 正常退出程式
                    socket_obj.send(send_data.encode("utf-8"))  # 通知服務區斷開連接
                    socket_obj.close()
                    print("程式正常退出")
                    return True
                if self.syntax_analysis(send_data, socket_obj):  # 解析用戶數據並處理數據
                    print("異常退出")
                    return True
        return False

    def login_menu(self):
        print("-"*41)
        print("          歡迎登陸迷你FTPv1.0")
        print("-"*41)
        print("1、登陸系統")
        print("2、用戶註冊")
        print("Q、退出程式")

2.4.3 login.py模塊主要用於處理註冊和登錄的功能,核心代碼如下:

class Login(Commom):
    def login_authention(self,socket_obj):
        """
        登陸認證
        :param socket_obj:socket 對象
        :return:
        """
        user_pwd = self.get_user_pwd()  # 獲取用戶名密碼
        self.send_data(socket_obj,user_pwd)  # 將用戶名和密碼發給伺服器
        recv_status = socket_obj.recv(2).decode("utf-8")  # 等待接收狀態碼
        print(self.status_info[recv_status])  # 列印狀態碼對應的結果
        if self.status_info[recv_status] == '登錄成功':
            return True
        return False
    ...

    def register_user(self,socket_obj):
        """
        等待服務端反饋是否允許註冊用戶。
        :param socket_obj:
        :return:
        """
        print("請等待服務端回應.....")
        recv_status = socket_obj.recv(1).decode("utf-8")
        if recv_status == "1":  # 服務端同意申請賬號
            user_pwd = self.get_regist_user_pwd()  # 獲取註冊用戶名和密碼
            if user_pwd:
                self.send_data(socket_obj,user_pwd)
                result = socket_obj.recv(2).decode("utf-8")
                print(self.status_info[result])
            else:
                print("用戶名密碼有誤")
        else:  # 客戶端拒絕申請賬號的請求
            print("服務端拒絕了您申請賬號的請求,請與管理員取得聯繫。")
        return False
    ...

2.4.4 用戶登錄成功後,會等待接收用戶輸入命令,由dataAnalysis.py模塊負責解析用戶輸入的命令,並將命令發給FTP伺服器,然後接收伺服器的反饋。核心代碼如下:

class DataAnalysis(FileOperation):

    def syntax_analysis(self,cmd,socket_obj):
        """
        解析用戶輸入的命令。
        :param cmd:用戶執行的命令,如:put 上傳的文件
        :param socket_obj:socket對象發送和接收數據
        :return:
        """
        cmd_split = cmd.split(" ")  # 將字元串命令分割成列表,用於驗證命令是否存在
        if hasattr(self,cmd_split[0]):
            run_fun = getattr(self,cmd_split[0])
            run_fun(cmd_split,socket_obj)
        else:
            print("無效的命令")
    ...

三、功能演示


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

-Advertisement-
Play Games
更多相關文章
  • JavaScript高級函數 1.map/reduce map() map()是array的一個方法 作用: 對array中每一個元素調用自定義函數 map的回調函數有三個參數: callback(currentValue, index, array) 通常只要第一個參數 PS: map()傳入的參 ...
  • 在前端開發過程中,非IE瀏覽器下,當容器的高度自動,並且容器內容中有浮動元素(float為left或right),此時如果容器的高度不能自適應內容的高度,從而使得內容溢出破壞整體佈局,這種現象叫做浮動溢出,為了方式這個現象的發生,就需要對CSS樣式進行處理,而這個過程就叫做CSS清除浮動。現在常用的 ...
  • <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv ...
  • ...
  • 今天在改一個看似很簡單以前也經常遇到的一個bug發現了很多問題。實際結果肯定沒有想象的那麼簡單。所以我想總結一下:保證下次不會踩坑。 業務場景是點擊一個按鈕,會產生一個彈框。重覆快速的點擊多次,會產生多個彈框。那麼這個問題該怎麼解決呢? 1. 函數防抖節流 這兩個東西我不知道看過了多少文檔,可到現在 ...
  • 高德地圖技術團隊希望打造一套快速精準的UI解決方案,通過自動化的方式生產UI代碼,解放研發生產力的同時滿足客戶需求。 ...
  • [TOC] 簡單工廠模式缺陷 "大白話簡單工廠模式(Simple Factory Pattern)" 中通過買車的經歷解釋了簡單工廠模式。但熟悉設計模式的朋友會發現一些問題。 工廠類集中了所有實例(產品)的創建邏輯,一旦這個工廠不能正常工作,整個系統都會受到影響。用日產車工廠的例子來形容就是日產汽車 ...
  • 微服務改造是一個長期過程,這個過程會遇到各式各樣的問題,方法論可以幫助我們更好地解決這些問題,並且降低風險。緊接昨天的上篇《如何把單體式應用拆解成微服務?【上】》,今天我們一起來看看具體的拆解場景。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...