python 實(shí)現(xiàn)簡單的FTP程序
FTP即文件傳輸協(xié)議;它基于客戶機(jī)-服務(wù)器模型體系結(jié)構(gòu),應(yīng)用廣泛。它有兩個(gè)通道:一個(gè)命令通道和一個(gè)數(shù)據(jù)通道。命令通道用于控制通信,數(shù)據(jù)通道用于文件的實(shí)際傳輸。使用FTP可以做很多事情,比如移動(dòng)、下載、復(fù)制文件等。
一、開發(fā)環(huán)境
server端:centos 7 python-3.6.2
客戶端:Windows 7 python-3.6.2 pycharm-2018
程序目的:1、學(xué)習(xí)使用socketserver實(shí)現(xiàn)并發(fā)處理多個(gè)客戶端。
2、了解使用struct解決TCP粘包。
二、程序設(shè)計(jì)
(本人菜鳥一枚,對于開發(fā)規(guī)范,接口設(shè)計(jì)完全不懂,完全是隨心所欲,自娛自樂。寫博客主要是記錄自己學(xué)習(xí)的點(diǎn)點(diǎn)滴滴,如有不足之處還請見諒。)
1、server端
1.1 目錄結(jié)構(gòu)如下:

1.2 目錄簡介:
FTP_SERVER:程序主目錄
app:程序主邏輯目錄,目錄下有四個(gè)模塊:
FTPserver.py:FTP Server端啟動(dòng)入口。
login.py:認(rèn)證注冊模塊,用于處理用戶注冊,登錄認(rèn)證。
dataAnalysis.py:命令解析模塊,負(fù)責(zé)解析,執(zhí)行客戶端命令。
FileOpertion.py:負(fù)責(zé)文件讀,寫。數(shù)據(jù)發(fā)送,數(shù)據(jù)接收。
db:存放user_pwd.db文件,用于存放用戶信息(用戶名,密碼,F(xiàn)TP目錄總空間,已使用空間等)
lib:存放公共數(shù)據(jù)。
1.3 模塊中類的繼承關(guān)系

1.4 執(zhí)行流程
1.4.1 程序啟動(dòng)文件FTPserver.py,程序啟動(dòng)后進(jìn)入監(jiān)聽狀態(tài)。核心代碼如下:
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:
# 執(zhí)行用戶選項(xiàng):1、登陸系統(tǒng) 2、注冊賬號。并返回一個(gè)結(jié)果
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): # 執(zhí)行ftpserver主功能
break
elif int(status_id) == -1: # client斷開連接了
break
print(f"客戶端{(lán)self.client_address}斷開了連接")
def run_ftp_server(self,data_analy,commom_obj):
""""
登陸成功后,接收客戶端發(fā)來的命令,并進(jìn)行處理
:param data_analy:負(fù)責(zé)解析,執(zhí)行客戶端命令的對象
:param commom_obj:程序執(zhí)行時(shí)所需的數(shù)據(jù)對象
: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') # 接收客戶端數(shù)據(jù)
if recv_data.upper() == "Q": # 客戶端提出斷開連接了
break
# 解析,處理客戶端的命令
data_analy.syntax_analysis(recv_data, self.request, commom_obj)
return False
if __name__ == '__main__':
print('運(yùn)行FTP服務(wù)')
ip_port = ('192.168.10.10',9000)
# 創(chuàng)建并發(fā)服務(wù)端對象
server = socketserver.ThreadingTCPServer(ip_port, MyFtpServer)
# 開啟服務(wù)
server.serve_forever()
1.4.2 服務(wù)端進(jìn)入監(jiān)聽狀態(tài)后,客戶端發(fā)起連接請求,服務(wù)端接收連接請求后會(huì)等待客戶單發(fā)來狀態(tài)碼,1表示請求登錄FTP服務(wù)器,2表示客戶端要注冊用戶,注冊用戶需要服務(wù)端手動(dòng)反饋狀態(tài)碼1才可注冊。處理用戶登錄,注冊模塊login.py核心代碼如下:
class Login(FileOperation):
"""
登陸注冊類。主要負(fù)責(zé)用戶的登陸認(rèn)證,和用戶注冊。
"""
def run_client_choice(self,socket_obj,commom):
"""
獲取客戶端的請求,1是登陸,2是注冊用戶
:param socket_obj: socket對象
:param commom: ftpserver運(yùn)行時(shí)所需要的數(shù)據(jù)對象
:return:
"""
recv_choice = socket_obj.recv(1).decode("utf-8") # 獲取用戶選項(xiàng):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斷開連接了
# 用戶登陸認(rèn)證
def login_authen(self,socket_obj,commom):
"""
客戶端登陸認(rèn)證
:param socket_obj: socket對象
:param commom: ftpserver運(yùn)行時(shí)需要的數(shù)據(jù)對象
:return:返回1代表登陸成功
"""
# 接收client發(fā)來的用戶名,密碼
recv_userPwd = self.recv_data(socket_obj).decode("utf-8").split("|")
# 效驗(yàn)用戶名密碼
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: 返回是否允許注冊的結(jié)果,1允許客戶端注冊,2拒絕客戶端注冊
"""
while True:
choice_id = input("請輸入回應(yīng)碼:1是允許注冊,2是不允許注冊:")
if choice_id.isdigit() and 3 > int(choice_id) > 0:
socket_obj.send(choice_id.encode("utf-8")) # 發(fā)通知告知客戶端,處理結(jié)果
if choice_id == "1": # 注冊用戶
return self.client_register(socket_obj, commom)
return choice_id
else:
print("您輸入的信息有誤,請重新輸入。")
...
1.4.3 客戶端登錄成功后,服務(wù)端會(huì)等待接收客戶端發(fā)來的命令,命令的解析,執(zhí)行由dataAnalysis.py模塊執(zhí)行,核心代碼如下:
class DataAnalysis(FileOperation):
"""
數(shù)據(jù)分析處理類,主要負(fù)責(zé)解析client發(fā)送過來的指令。
"""
def syntax_analysis(self,recv_data, socket_obj, commom):
"""
負(fù)責(zé)解析客戶端傳來的數(shù)據(jù)。
:param recv_data:接收到的客戶端用戶數(shù)據(jù)
:param socket_obj:socket對象
:param commom:數(shù)據(jù)對象
:return:
"""
clientData = recv_data.split(" ")
if hasattr(self,clientData[0]): # 判斷對象方法是否存在
get_fun = getattr(self,clientData[0])#獲取對象方法
get_fun(clientData,socket_obj,commom) # 運(yùn)行對象方法
else:
pass
...
執(zhí)行客戶端命令后,繼續(xù)等待接收客戶端發(fā)來的命令,如此循環(huán)...。
2、客戶端
2.1 目錄結(jié)構(gòu)如下:

2.2 目錄簡介:
client:程序主目錄。
bin:程序入口,程序啟動(dòng)文件main.py用于建立socket連接,然后調(diào)用FTPclient.py模塊下的run_ftp_client方法運(yùn)行程序。
app:程序主邏輯,目錄下有四個(gè)模塊如下:
FTPclient.py:FTP客戶端,根據(jù)用戶選項(xiàng),執(zhí)行用戶指令。
login.py:認(rèn)證注冊模塊,用于處理用戶注冊,登錄認(rèn)證。
dataAnalysis.py:命令解析模塊,解析用戶輸入的命令,發(fā)給服務(wù)端獲取結(jié)果。
FileOpertion.py:負(fù)責(zé)文件讀,寫。
lib:存放公共數(shù)據(jù),有兩個(gè)文件:
commom.py:主要存放的是公共變量。
help.txt:存放的是幫助文檔,當(dāng)用戶執(zhí)行help命令時(shí)會(huì)調(diào)用該文件。
2.3 模塊中類的繼承關(guān)系

2.4 執(zhí)行流程
2.4.1 程序入口main.py,啟動(dòng)后會(huì)與FTP服務(wù)端建立連接,與服務(wù)端連接成功后會(huì)調(diào)用FTPclient.py模塊下的run_ftp_client方法,執(zhí)行用戶功能。核心代碼如下:
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) # 接收用戶輸入的選項(xiàng),執(zhí)行對應(yīng)的功能
2.4.2 FTPclient.py模塊下的run_ftp_client方法會(huì)打印菜單,并等待用戶輸入選項(xiàng),執(zhí)行相應(yīng)功能,核心代碼如下:
class Client(Login,DataAnalysis):
def run_ftp_client(self,socket_obj):
"""
運(yùn)行用戶輸入的選項(xiàng):1、是登陸 2、是注冊賬號
:return:
"""
while True:
self.login_menu() # 打印系統(tǒng)菜單
choice_id = self.get_user_choice() # 獲取用戶輸入的選項(xiàng)
if choice_id:
if self.run_user_choice(choice_id,socket_obj):
break
else:
print("您輸入的有誤")
def get_user_choice(self):
"""
獲取用戶輸入的選項(xiàng)
:return:
"""
choice_id = input("請輸入選項(xiàng):")
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": # 登陸系統(tǒng)
socket_obj.send(choice_id.encode("utf-8")) # 發(fā)通知告知服務(wù)器準(zhǔn)備登陸
if self.run_login(socket_obj) == True: # 執(zhí)行登陸
return True
elif choice_id == "2": # 注冊用戶
socket_obj.send(choice_id.encode("utf-8")) # 請求服務(wù)器,注冊用戶
self.register_user(socket_obj) # 執(zhí)行注冊
elif choice_id.upper() == "Q": # 退出程序
socket_obj.send(choice_id.encode("utf-8")) # 通知服務(wù)器,準(zhǔn)備退出程序
socket_obj.close()
print("程序正常退出")
return True
def run_login(self,socket_obj,):
"""
運(yùn)行登陸認(rèn)證模塊,如果登陸成功執(zhí)行程序主邏輯,否則重新登陸。
:param socket_obj:
:return:
"""
if self.login_authention(socket_obj):
while True:
send_data = input(">>>").strip(" ") # 獲取發(fā)送數(shù)據(jù)(用戶執(zhí)行的命令)
if send_data.upper() == "Q": # 正常退出程序
socket_obj.send(send_data.encode("utf-8")) # 通知服務(wù)區(qū)斷開連接
socket_obj.close()
print("程序正常退出")
return True
if self.syntax_analysis(send_data, socket_obj): # 解析用戶數(shù)據(jù)并處理數(shù)據(jù)
print("異常退出")
return True
return False
def login_menu(self):
print("-"*41)
print(" 歡迎登陸迷你FTPv1.0")
print("-"*41)
print("1、登陸系統(tǒng)")
print("2、用戶注冊")
print("Q、退出程序")
2.4.3 login.py模塊主要用于處理注冊和登錄的功能,核心代碼如下:
class Login(Commom):
def login_authention(self,socket_obj):
"""
登陸認(rèn)證
:param socket_obj:socket 對象
:return:
"""
user_pwd = self.get_user_pwd() # 獲取用戶名密碼
self.send_data(socket_obj,user_pwd) # 將用戶名和密碼發(fā)給服務(wù)器
recv_status = socket_obj.recv(2).decode("utf-8") # 等待接收狀態(tài)碼
print(self.status_info[recv_status]) # 打印狀態(tài)碼對應(yīng)的結(jié)果
if self.status_info[recv_status] == '登錄成功':
return True
return False
...
def register_user(self,socket_obj):
"""
等待服務(wù)端反饋是否允許注冊用戶。
:param socket_obj:
:return:
"""
print("請等待服務(wù)端回應(yīng).....")
recv_status = socket_obj.recv(1).decode("utf-8")
if recv_status == "1": # 服務(wù)端同意申請賬號
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("服務(wù)端拒絕了您申請賬號的請求,請與管理員取得聯(lián)系。")
return False
...
2.4.4 用戶登錄成功后,會(huì)等待接收用戶輸入命令,由dataAnalysis.py模塊負(fù)責(zé)解析用戶輸入的命令,并將命令發(fā)給FTP服務(wù)器,然后接收服務(wù)器的反饋。核心代碼如下:
class DataAnalysis(FileOperation):
def syntax_analysis(self,cmd,socket_obj):
"""
解析用戶輸入的命令。
:param cmd:用戶執(zhí)行的命令,如:put 上傳的文件
:param socket_obj:socket對象發(fā)送和接收數(shù)據(jù)
:return:
"""
cmd_split = cmd.split(" ") # 將字符串命令分割成列表,用于驗(yàn)證命令是否存在
if hasattr(self,cmd_split[0]):
run_fun = getattr(self,cmd_split[0])
run_fun(cmd_split,socket_obj)
else:
print("無效的命令")
...
總結(jié)
以上所述是小編給大家介紹的python 實(shí)現(xiàn)簡單的FTP程序,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
如果你覺得本文對你有幫助,歡迎轉(zhuǎn)載,煩請注明出處,謝謝!
相關(guān)文章
python 實(shí)現(xiàn)自動(dòng)遠(yuǎn)程登陸scp文件實(shí)例代碼
這篇文章主要介紹了python 實(shí)現(xiàn)自動(dòng)遠(yuǎn)程登陸scp文件實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-03-03
python代碼實(shí)現(xiàn)邏輯回歸logistic原理
這篇文章主要介紹了python代碼實(shí)現(xiàn)邏輯回歸logistic原理,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
Python3中PyQt5簡單實(shí)現(xiàn)文件打開及保存
本文將結(jié)合實(shí)例代碼,介紹Python3中PyQt5簡單實(shí)現(xiàn)文件打開及保存,具有一定的參考價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-06-06
余弦相似性計(jì)算及python代碼實(shí)現(xiàn)過程解析
這篇文章主要介紹了余弦相似性計(jì)算及python代碼實(shí)現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09
python使用fastapi實(shí)現(xiàn)多語言國際化的操作指南
本文介紹了使用Python和FastAPI實(shí)現(xiàn)多語言國際化的操作指南,包括多語言架構(gòu)技術(shù)棧、翻譯管理、前端本地化、語言切換機(jī)制以及常見陷阱和最佳實(shí)踐,需要的朋友可以參考下2025-02-02

