教你寫一個ftp協議(檔案傳輸協議)
一、FTP協議簡介
FTP 是File Transfer Protocol(檔案傳輸協議)的英文簡稱,而中文簡稱為“文傳協議”。用於Internet上的控制檔案的雙向傳輸。同時,它也是一個應用程式(Application)。基於不同的作業系統有不同的FTP應用程式,而所有這些應用程式都遵守同一種協議以傳輸檔案。在FTP的使用當中,使用者經常遇到兩個概念:”下載”(Download)和”上傳”(Upload)。”下載”檔案就是從遠端主機拷貝檔案至自己的計算機上;”上傳”檔案就是將檔案從自己的計算機中拷貝至遠端主機上。用Internet語言來說,使用者可通過客戶機程式向(從)遠端主機上傳(下載)檔案。
二、FTP Server的使用者分類及許可權歸屬
在考慮FTP伺服器安全性工作的時候,第一步要考慮的就是誰可以訪問FTP伺服器。以下有三種客戶可以訪問FTP Server:
一類是Real帳戶。這類使用者是指在FTP服務上擁有帳號。當這類使用者登入FTP伺服器的時候,其預設的主目錄就是其帳號命名的目錄。但是,其還可以變更到其他目錄中去。如系統的主目錄等等。
第二類帳戶是Guest使用者。在FTP伺服器中,我們往往會給不同的部門或者某個特定的使用者設定一個帳戶。但是,這個賬戶有個特點,就是其只能夠訪問自己的主目錄。伺服器通過這種方式來保障FTP服務上其他檔案的安全性。擁有這類使用者的帳戶,只能夠訪問其主目錄下的目錄,而不得訪問主目錄以外的檔案。
第三類帳戶是Anonymous(匿名)使用者,這也是我們通常所說的匿名訪問。這類使用者是指在FTP伺服器中沒有指定帳戶,但是其仍然可以進行匿名訪問某些公開的資源。
三、寫一個簡單的ftp協議
下面我們寫一個簡單的匿名使用者可以訪問的ftp協議,前兩種賬戶的功能我會在後續部落格中逐一完善。在寫一個ftp協議之前你需要了解以下幾點:
- ftp作為一個伺服器端和客戶端相互傳輸檔案的協議,我們在寫的時候就要分別寫伺服器端程式server_ftp.py和客戶端程式client_ftp.py。
我們要實現多個客戶端可以同時訪問伺服器端,所以要通過多執行緒的方式來使得多個客戶端訪問,在這裡我們伺服器端通過socketserver來寫。
最後通過將一個txt檔案通過客戶端上傳到伺服器端來驗證所寫程式碼的準確性。在下面的程式碼中我會詳細備註每條語句完成的功能。
客戶端程式碼:
import socket #匯入socket模組,用來實現socket通訊
import os #匯入os模組,主要用來呼叫系統命令,獲得路徑
import json #匯入json模組,將字串形式的json資料轉化為字典,也可以將Python中的字典資料轉化為字串形式的json資料
class FtpClient(object ):
def __init__(self):
self.client=socket.socket() #宣告客戶端利用socket通訊
def help(self): #寫一個列印一些指令的幫助資訊函式,
msg='''
ls
pwd
cd../..
get filename
put filename
'''
def connect(self,ip,port): #定義一個連線伺服器函式,呼叫client.connect()方法,連線伺服器端
self.client.connect((ip,port))
def interactive(self): #定義一個與伺服器互動的函式
while True:
cmd=input('>>').strip() #使用者在客戶端輸入指令,strip()去掉使用者輸入指令的空格和換行符
if len(cmd)==0:continue
cmd_str=cmd.split()[0] #拆分指令的第一個字元賦給cmd_str,永遠都是指令help()
#反射
if hasattr(self,'cmd_%s'%cmd_str ): #hasattr() 函式用於判斷物件是否包含對應的屬性
func=getattr(self,'cmd_%s'%cmd_str) # 函式用於返回一個物件屬性值
func(cmd) #取到help中的指令 ls,pwd,get ,put。然後傳入後面的 filename,這樣後面的函式名字就叫做cmd_put cmd_get...
else:
self.help()
def cmd_put(self,*args): #寫一個通過客戶端上傳檔案的函式,*args為了接收更多資料
#上傳一個檔案
cmd_split= args[0].split() #將傳入的第一個引數賦值給cmd_split,變為列表
if len(cmd_split)>1: #這裡大於1 因為最後我們輸入put filename.txt進行驗證,由於存在put所以大於1
filename=cmd_split[1]
if os.path.isfile(filename): #判斷要上傳的檔案是否存在
filesize=os.stat(filename).st_size #獲取檔案大小
#傳送檔案大小,檔名,進行的操作(這裡我們預設put上傳資料)給伺服器,所以寫成json字典形式,需要擴充套件直接在這加
msg_dic={
'action':'put',
'filename':filename,
'size':filesize
}
self.client.send(json.dumps(msg_dic).encode('utf-8'))#發給伺服器端,json.dumps()字典轉換為json格式
server_response=self.client.recv(1024) #等待伺服器響應
f=open(filename ,'rb') #開啟檔案,傳送給伺服器
for i in f:
self.client.send(i)
else:
print('檔案傳輸完畢')
f.close()
else:
print(filename ,'is not exist')
def cmd_get(self): #定義一個從伺服器下載檔案函式,這裡和上面大同小異,先不寫
pass
ftp=FtpClient() #例項化
ftp.connect('localhost',9999) #連線伺服器埠
ftp.interactive() #呼叫和伺服器互動函式
伺服器端程式碼
import socketserver #利用socketserver來寫
import json ,os
#自己寫一個請求處理類,繼承BaseRequestHandler
class MyTCPHandler(socketserver.BaseRequestHandler):
def put(self,*args):
#接收客戶端檔案
cmd_dic=args[0]
filename=cmd_dic['filename'] #獲取檔名
filesize=cmd_dic['size'] #獲取檔案大小
if os.path.isfile(filename ): #如果已經存在上傳檔案,新建一個檔案
f=open(filename+'.new','wb')
else:
#如果不存在 給客戶端響應上傳
f=open(filename ,'wb')
self.request.send(b'200 ok') #響應客戶端
received_size=0
while received_size < filesize : #迴圈接收檔案
data=self.request.recv(1024)
f.write(data)
received_size +=len(data)
else:
print('file[%s] has overload..'%filename ) #檔案傳輸完成
#跟客戶端的互動在handle中
def handle(self):
while True :
try:
self.data=self.request.recv(1024).strip()
#format格式化 列印客戶端ip地址
print('{}wrote:'.format(self.client_address[0]))
print(self.data)
cmd_dic=json.loads(self.data.decode())#json字串轉為字典
action=cmd_dic['action'] #獲取進行的操作 這裡預設是put
#反射
if hasattr(self,action ): #判斷put操作是否存在
func=getattr(self ,action )
func(cmd_dic)
except ConnectionResetError as e:
print(e)
break
if __name__ =='__main__':
HOST,POST='localhost',9999
# 例項化TCPServer
server=socketserver.ThreadingTCPServer((HOST,POST),MyTCPHandler ) #ThreadingTCPServer:多執行緒,多個客戶端可以請求
#這樣此伺服器就可以讓多個客戶端連線
#處理多個請求
server.serve_forever()
執行結果
我們把需要測試的檔案lianxiren.txt放在客戶端目錄下,先執行伺服器,在執行客戶端,並且輸入put lianxiren.txt 可以發現檔案傳輸完成,在伺服器的資料夾中會發現傳輸過去的txt檔案。