1. 程式人生 > >教你寫一個ftp協議(檔案傳輸協議)

教你寫一個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協議之前你需要了解以下幾點:

  1. ftp作為一個伺服器端和客戶端相互傳輸檔案的協議,我們在寫的時候就要分別寫伺服器端程式server_ftp.py和客戶端程式client_ftp.py。
  2. 我們要實現多個客戶端可以同時訪問伺服器端,所以要通過多執行緒的方式來使得多個客戶端訪問,在這裡我們伺服器端通過socketserver來寫。

  3. 最後通過將一個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檔案。
這裡寫圖片描述