1. 程式人生 > 程式設計 >python 實現簡單的FTP程式

python 實現簡單的FTP程式

FTP即檔案傳輸協議;它基於客戶機-伺服器模型體系結構,應用廣泛。它有兩個通道:一個命令通道和一個數據通道。命令通道用於控制通訊,資料通道用於檔案的實際傳輸。使用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,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,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,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,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,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,):
 """
 執行登陸認證模組,如果登陸成功執行程式主邏輯,否則重新登陸。
 :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("無效的命令")
 ...

總結

以上所述是小編給大家介紹的python 實現簡單的FTP程式,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回覆大家的。在此也非常感謝大家對我們網站的支援!
如果你覺得本文對你有幫助,歡迎轉載,煩請註明出處,謝謝!