小程式5:FTP程式

目錄

1.FTP程式所需要的知識點

2.FTP程式具體實現過程

  2.1 FTP程式之註冊功能

  2.2 FTP程式之登錄功能

  2.3 FTP程式之下載功能

3.FTP程式源程式碼

FTP程式所需要的知識點

1.socketserver並發編程

2.連續send,recv黏包現象:struct

3.hashlib模組的md5加密

4.靜態方法staticmethod和類方法classmethod

5.json序列化

6.反射:hasattr,setattr

7.os模組相關方法

FTP程式具體實現過程

FTP程式之註冊功能

1.要明確,FTP程式是要實現服務端的並發的,所以需要引入socketserver模組來實現並發

2.寫服務端下socketserver的基本語法[day31:socketserver的基本語法]

# 服務端
import socketserver

class FTPServer(socketserver.BaseRequestHandler):
    def handle(self):
        pass

myserver = socketserver.ThreadingTCPServer(("127.0.0.1",9000),FTPServer)
myserver.serve_forever()


# 客戶端
import socket
sk = socket.socket()
sk.connect(("127.0.0.1",9000))

sk.close()

3.用戶需要自己輸入帳號和密碼,所以在客戶端需要寫輸入用戶名和密碼的方法(輸入用戶名和密碼後,發送給服務端)

4.在客戶端定義auth方法,先寫兩個input輸入用戶名和密碼

5.輸入完用戶名密碼之後,怎樣將用戶資訊傳給服務端呢?

將用戶名和密碼以及操作做成一個字典,並用json序列化成字元串,並encode後,使用sk.send()發送給服務端

這部分的具體程式碼如下所示:

# 客戶端
def auth(opt):
    usr = input("username:").strip()
    pwd = input("password:").strip()
    dic = {"user":usr,"passwd":pwd,"operate":opt}
    str_dic = json.dumps(dic) # 將字典序列化成字元串
    sk.send(str_dic.encode()) # 將字元串轉化成位元組流並發送出去

auth("register")

6.服務端已經將用戶名密碼和操作發過去了,所以現在服務端需要接收一下,服務端的整體邏輯寫在類中的handle方法

再定義一個專門用來接收的方法myrecv,並使用handle方法去調用myrecv方法

這部分的具體程式碼如下所示:

# 服務端
class FTPServer(socketserver.BaseRequestHandler):
    def handle(self):
        opt_dic = self.myrecv()
        print(opt_dic)

    def myrecv(self):
        info = self.request.recv(1024)
        opt_str = info.decode()
        opt_dic = json.loads(opt_str)
        return opt_dic

通過以上步驟,我們實現了一収一發

7.接收到了客戶端發來的數據,我們就可以在服務端寫一些關於註冊的邏輯

在服務端定義Auth類,專門用來實現註冊登錄,在handler方法也可以去調用類中的成員

那麼Auth類中應該寫什麼呢?

1.首先在當前目錄創建db文件夾,並在db問文件夾中創建userinfo.txt用來存放用戶名和密碼

2.對密碼使用md5加密

8.在Auth類中定義md5方法,用來對密碼進行一個加密操作

# 服務端
class Auth():
    def md5(usr,pwd):
        md5_obj = hashlib.md5(usr.encode())
        md5_obj.update(pwd.encode())
        return md5_obj.hexdigest()

我們先加密一份數據存放到userinfo.txt中

9.現在已經對每個用戶名的密碼加密了,但是還有一個問題需要考慮,在註冊的時候,不能註冊已經存在的用戶名,所以需要對用戶名進行判斷

10.定義register方法,並使用classmethod裝飾器,當其他類調用register方法時,會自動傳遞類參數.

11.拼接出一個userinfo所在文件的完整路徑

1.首先獲取當前文件(server.py)所在的位置

兩種方法:

  方法一:os.getcwd()

  方法二:os.path.dirname(__file__)

print(os.getcwd()) # F:\OldBoyPython\week6\day36
print(__file__) # F:/OldBoyPython/week6/day36/ceshi.py
print(os.path.dirname(__file__)) # F:/OldBoyPython/week6/day36

2.使用os.path.join進行路徑拼接

base_path = os.getcwd()
userinfo = os.path.join(base_path,"db","userinfo.txt")
print(userinfo) # F:\OldBoyPython\week6\day36\db\userinfo.txt

這樣,我們就獲取到了userinfo.txt的絕對路徑了

12.當有了userinfo.txt的絕對路徑後,我們就可以開始文件操作

在第9步,我們說到要檢測用戶名是否存在,現在我們就可以實現了

當用戶名存在時,返回一個狀態False和一個用戶名已存在資訊提示

@classmethod
def register(cls, opt_dic):
    with open(userinfo, mode='r', encoding='utf-8') as fp:
        for line in fp:
            username = line.split(":")[0]
            if username == opt_dic["user"]:
                return {"result": False, "info": "用戶名存在了"}

13.用戶名存在的邏輯已經寫完,接下來就是用戶名可以使用的邏輯

要注意:密碼需要加密後再寫入

with open(userinfo, mode='a+', encoding='utf-8') as fp:
    # 帳號就是字典的帳號,密碼使用md5加密處理後再寫入文件
    strvar = "%s:%s\n" % (opt_dic["user"], cls.md5(opt_dic["user"], opt_dic["passwd"]))
    fp.write(strvar)

如果登錄成功了,返回一個狀態True和一個註冊成功資訊提示

到此,註冊部分的邏輯就已經寫完了,具體程式碼如下所示:

@classmethod
def register(cls, opt_dic):
    # 1.檢測註冊的用戶是否存在
    with open(userinfo, mode='r', encoding='utf-8') as fp:
        for line in fp:
            username = line.split(":")[0]
            if username == opt_dic["user"]:
                return {"result": False, "info": "用戶名存在了"}
    # 2.當前用戶可以註冊
    with open(userinfo, mode='a+', encoding='utf-8') as fp:
        # 帳號就是字典的帳號,密碼使用md5加密處理後再寫入文件
        strvar = "%s:%s\n" % (opt_dic["user"], cls.md5(opt_dic["user"], opt_dic["passwd"]))
        fp.write(strvar)

    # 3.返回一個註冊成功的狀態
    return {"result": True, "info": "註冊成功"}

14.註冊的register方法已經寫完,但是現在我們需要將register方法和下面的FTPServer類建立聯繫,這個時候就需要使用反射來實現了

換句話來說:就是想在FTPServer的handle方法中使用Auth中的register方法

15.構建出反射,程式碼如下所示

到目前為止,基本的程式碼已經實現,現進行測試,程式碼如下所示

# 服務端
import socketserver
import json
import hashlib
import os


# 找當前資料庫文件所在的絕對路徑
base_path = os.getcwd()
# F:\OldBoyPython\week6\day36\db\userinfo.txt
userinfo = os.path.join(base_path,"db","userinfo.txt")

class Auth():
    @ staticmethod
    def md5(usr,pwd):
        md5_obj = hashlib.md5(usr.encode())
        md5_obj.update(pwd.encode())
        return md5_obj.hexdigest()

    @ classmethod
    def register(cls,opt_dic):
        # 1.檢測註冊的用戶是否存在
        with open(userinfo,mode='r',encoding='utf-8') as fp:
            for line in fp:
                username = line.split(":")[0]
                if username == opt_dic["user"]:
                    return {"result":False,"info":"用戶名存在了"}
        # 2.當前用戶可以註冊
        with open(userinfo, mode='a+', encoding='utf-8') as fp:
            # 帳號就是字典的帳號,密碼使用md5加密處理後再寫入文件
            strvar = "%s:%s\n" % (opt_dic["user"],cls.md5(opt_dic["user"],opt_dic["passwd"]))
            fp.write(strvar)

        # 3.返回一個註冊成功的狀態
        return {"result":True,"info":"註冊成功"}

class FTPServer(socketserver.BaseRequestHandler):
    def handle(self):
        opt_dic = self.myrecv()
        print(opt_dic)
        if hasattr(Auth,"register"):
            res = getattr(Auth,"register")(opt_dic)
            print(res)


    def myrecv(self):
        info = self.request.recv(1024)
        opt_str = info.decode()
        opt_dic = json.loads(opt_str)
        return opt_dic

myserver = socketserver.ThreadingTCPServer(("127.0.0.1",9000),FTPServer)
myserver.serve_forever()
# 客戶端
import socket
import json

sk = socket.socket()
sk.connect(("127.0.0.1",9000))

# 處理収發數據的邏輯
def auth(opt):
    usr = input("username:").strip()
    pwd = input("password:").strip()
    dic = {"user":usr,"passwd":pwd,"operate":opt}
    str_dic = json.dumps(dic) # 將字典序列化成字元串
    sk.send(str_dic.encode()) # 將字元串轉化成位元組流並發送出去

auth("register")

sk.close()

運行結果如下圖所示

客戶端輸入用戶名和密碼

服務端接收到客戶端發來的數據

並且userinfo.txt也已經寫入了你剛才在客戶端輸入的用戶名和密碼

16.在服務端我們可以看到註冊成功/註冊失敗的資訊了,現在我們想把這個資訊發回給客戶端,在客戶端也能顯示出來

和服務端的myrecv方法一樣,我們需要自定義一個接収方法mysend

既然在服務端發數據,當然要在客戶端接收數據

 

好的,到此第一部分註冊功能就全部完成了。讓我們看一下運行結果

所有的資訊都應該是顯示在客戶端上的

 

FTP程式之登錄功能

1.現在添加了登錄功能,所以反射的時候就要動態起來。

2.Auth類中只有註冊和登錄兩個方法,如果用戶在客戶端傳入其他方法,必須要給予錯誤的提示

 

下面,我們來測試一下結果

3.現在就可以開始寫登錄函數的邏輯了。。。

登錄嘛,肯定是要驗證用戶名和密碼的,所以肯定需要從userinfo.txt中取出用戶名和密碼進行比對

所以先進行文件操作,將用戶名和密碼取出來,在進行驗證

@ classmethod
def login(cls,opt_dic):
    with open(userinfo,mode='r',encoding='utf-8') as fp:
        for line in fp:
            username,password = line.strip().split(":")
            if username == opt_dic["user"] and password == cls.md5(opt_dic["user"],opt_dic["passwd"]):
                return {"result":True,"info":"登陸成功"}

        return {"result":False,"info":"登錄失敗"}

其他的部分都不用改,定義了login函數,FTPServer就會自己識別是什麼操作,並且通過反射獲取到對應方法的返回值,將返回值發送給客戶端,然後客戶端接收後,列印出來

運行結果如下圖所示

4.到此,登錄部分的邏輯也已經完成了!!

但是在客戶端調用時,還是非常死板的

這種調用方式非常的lowb,所以需要改進一下。。

我們需要搞一個介面。

5.先在客戶端定義login函數和register函數,在函數里進行調用。

6.除了登錄和註冊函數,還需要搞一個退出的功能

在客戶端定義myexit函數,用來實現退出的功能

現在我們在客戶端已經定義了退出函數,但是在服務端我們也要讓服務端知道退出的狀態。

我們在客戶端發送了一個opt_dic給服務端,然後服務端接收這個opt_dic

 

到此,退出功能就已經實現完了。

7.現在我們需要把登錄,註冊和退出形成一套介面

def main():
    # 生成菜單介面
    for i,tup in enumerate(operate_lst,start=1):
        print(i,tup[0])
    
    # 輸入相應序號,實現對應操作
    num = int(input("請選擇您要進行的操作>>>"))
    res = operate_lst[num-1][1]()
    return res # 將對應操作的返回值返回出來

while True:
    res = main() # 調用main獲取到對應的返回值
    print(res)

在客戶端我們可以通過while True實現循環調用main,進而可以進行循環登錄註冊和退出。

那麼在服務端我們也應該是循環進行調用註冊登錄和退出

所以需要在服務端也加上一個while True

8.到此為止,登錄,註冊和退出的功能就都已經實現了。

程式碼如下所示

# 服務端
import socketserver
import json
import hashlib
import os

# 找當前資料庫文件所在的絕對路徑
base_path = os.path.dirname(__file__)
# /mnt/hgfs/python31_gx/day36/db/userinfo.txt
userinfo = os.path.join(base_path,"db","userinfo.txt")

class Auth():
    @staticmethod
    def md5(usr,pwd):
        md5_obj = hashlib.md5(usr.encode())
        md5_obj.update(pwd.encode())
        return md5_obj.hexdigest()

    @classmethod
    def register(cls,opt_dic):
        # 1.檢測註冊的用戶是否存在
        with open(userinfo,mode="r",encoding="utf-8") as fp:
            for line in fp:
                username = line.split(":")[0]
                if username == opt_dic["user"]:
                    return {"result":False,"info":"用戶名存在了"}
                    
        # 2.當前用戶可以註冊
        with open(userinfo,mode="a+",encoding="utf-8") as fp:
            strvar = "%s:%s\n" % (   opt_dic["user"] , cls.md5(   opt_dic["user"],opt_dic["passwd"]   )    )
            fp.write(strvar)
                    
        """
            當用戶上傳的時候,給他創建一個專屬文件夾,存放數據
        """
        
        # 3.返回狀態
        return {"result":True,"info":"註冊成功"}
        
    @classmethod
    def login(cls,opt_dic):
        with open(userinfo , mode="r" , encoding="utf-8") as fp:
            for line in fp:
                username,password = line.strip().split(":")
                if username == opt_dic["user"] and password == cls.md5( opt_dic["user"] , opt_dic["passwd"] ) :
                    return {"result":True,"info":"登錄成功"}
             
            return {"result":False,"info":"登錄失敗"}
        
    @classmethod
    def myexit(cls,opt_dic):
        return {"result":"myexit"}
        

class FTPServer(socketserver.BaseRequestHandler):
    def handle(self):
        while True:
            opt_dic = self.myrecv()
            print(opt_dic) # {'user': 'wangwen', 'passwd': '111', 'operate': 'register'}
            if hasattr(Auth,opt_dic["operate"]):
                # print(  getattr(Auth,"register")   )
                res = getattr(Auth,opt_dic["operate"])(opt_dic) # login(opt_dic)
                
                # 如果接受的操作是myexit,代表退出
                if res["result"] == "myexit":
                    return 
                
                # 把註冊的狀態發送給客戶端
                self.mysend(res)
            else:
                dic = {"result":False,"info":"沒有該操作"}
                self.mysend(dic)

    # 接收方法
    def myrecv(self):
        info = self.request.recv(1024)
        opt_str = info.decode()
        opt_dic = json.loads(opt_str)
        return opt_dic        
        
    # 發送方法
    def mysend(self,send_info):
        send_info = json.dumps(send_info).encode()
        self.request.send(send_info)

# 設置一個埠可以綁定多個程式
# socketserver.TCPServer.allow_reuse_address = True
myserver = socketserver.ThreadingTCPServer( ("127.0.0.1",9000) , FTPServer)
myserver.serve_forever()
# ### 客戶端
import socket
import json
""""""
sk = socket.socket()
sk.connect( ("127.0.0.1",9000) )

# 處理收發數據的邏輯
def auth(opt):
    usr = input("username: ").strip()
    pwd = input("password: ").strip()
    dic = {"user":usr,"passwd":pwd,"operate":opt}
    str_dic = json.dumps(dic)
    # 發送數據
    sk.send(str_dic.encode("utf-8"))
    
    # 接受服務端響應的數據
    file_info = sk.recv(1024).decode()
    file_dic = json.loads(file_info)
    return file_dic
    


# 註冊
def register():
    res = auth("register")
    return res

# 登錄
def login():
    res = auth("login")
    return res
    
# 退出
def myexit():    
    opt_dic = {"operate":"myexit"}
    sk.send(json.dumps(opt_dic).encode())
    exit("歡迎下次再來")
    

# 第一套操作介面
#                      0                  1                2
operate_lst1 = [ ("登錄",login) ,("註冊",register) , ("退出",myexit) ]



"""
1.登錄
2.註冊
3.退出

1 ('登錄', <function login at 0x7ff7cf171a60>)
2 ('註冊', <function register at 0x7ff7cf17e620>)
3 ('退出', <function myexit at 0x7ff7cf171ae8>)
"""



def main():
    for i,tup in enumerate(operate_lst1,start=1):
        print(i , tup[0])
    num = int(input("請選擇執行的操作>>> ").strip()) # 1 2 3
    # 調用函數
    # print(operate_lst1[num-1]) ('退出', <function myexit at 0x7f801e34aa60>)
    # print(operate_lst1[num-1][1]) <function myexit at 0x7f801e34aa60>
    # operate_lst1[num-1][1]() myexit()
    res = operate_lst1[num-1][1]()
    return res
    
while True:
    # 開啟第一套操作介面
    res = main()
    print(res)

sk.close()

執行結果如下圖所示

FTP註冊之下載功能

1.當你登錄成功後,要跳轉到另一套介面,讓用戶選擇下載上傳還是退出

所以我們需要像登錄註冊退出那套介面邏輯一樣,再搞一個operate_lst2

只有登錄成功的時候,才能出現第二套介面。

 

2.客戶端現在已經發送過去了,那麼對應的服務端也應該有所接收

 

 

3.download我們後面再說,先把介面2的退出搞定

同理,客戶端的myexit有exit()直接終止程式,在服務端也要及時終止程式

直接搞上一個return,連循環加函數全都退出

 

到此,介面2的退出也已經搞定了,接下來就搞最複雜的download

4.下載,先搞一下這個客戶端

在客戶端定義一個download方法,定義一個字典,字典里寫入操作和下載的文件名

 

5.客戶端定義了下載方法將字典發送過去,服務端也應該定義download下載方法來接收這個字典並進行邏輯操作

# 服務端
def download(self, opt_dic):
    filename = opt_dic["filename"]  # 獲取用戶在客戶端輸入的文件名
    file_abs = os.path.join(base_path, "video", filename)  # 獲取到要下載影片的絕對路徑
    if os.path.exists(file_abs):  # 如果文件存在
        dic = {"result": True, "info": "文件存在,可以下載"}
        self.mysend()
    else:  # 如果文件不存在
        pass

6.如果文件存在可以下載,那麼就可以執行下載的流程了

在下載時,服務端需要將影片發送給客戶端,因為影片很大,且需要分段發送,所以可能會存在黏包現象。

所以需要引入struct模組,並改造mysend方法,以解決黏包現象

# 服務端
def mysend(self, send_info, sign=False):
    send_info = json.dumps(send_info).encode()
    if sign:
        # 1.發送數據的長度
        res = struct.pack("i", len(send_info))
        self.request.send(res)
    # 2.發送真實的數據
    self.request.send(send_info)
# 客戶端
def myrecv(info_len=1024,sign=False):
    if sign:
        # 1.接受數據的長度
        info_len = sk.recv(4)
        info_len = struct.unpack("i",info_len)[0]

    # 2.接受真實的數據
    file_info = sk.recv(info_len).decode()
    file_dic = json.loads(file_info)
    return file_dic

7.客戶端向服務端發送下載操作和要下載的文件名,服務端接收到文件名稱,返回一個可以下載的狀態給客戶端

8.剛才服務端已經將文件存在,可以下載的提示資訊發給客戶端了,接下來服務端要發送客戶端要下載的影片的文件名字和文件大小

9.現在該發的都發了,最後一步就是發送真實的內容

10.現在幾乎是已經大功告成了,還差最後一點小瑕疵

登錄功能的第7步,我們說到,要想進行循環操作(循環選擇下載上傳和退出),需要在客戶端和服務端加while True

11.到此!!所有功能實現完畢

運行結果如下圖所示

 

這個時候,我們去download文件夾,可以查看到下載的影片

 

FTP程式源程式碼

客戶端

# 客戶端
import socket
import json
import struct
import os

sk = socket.socket()
sk.connect(("127.0.0.1",9000))

def myrecv(info_len=1024,sign=False):
    if sign:
        info_len = sk.recv(4)
        info_len = struct.unpack("i",info_len)[0]

    file_info = sk.recv(info_len).decode()
    file_dic = json.loads(file_info)
    return file_dic


# 處理収發數據的邏輯
def auth(opt):
    usr = input("username:").strip()
    pwd = input("password:").strip()
    dic = {"user":usr,"passwd":pwd,"operate":opt}
    str_dic = json.dumps(dic) # 將字典序列化成字元串
    sk.send(str_dic.encode()) # 將字元串轉化成位元組流並發送出去
    return myrecv()


def login():
    res = auth("login")
    return res

def register():
    res = auth("register")
    return res

def myexit():
    opt_dic = {"operate":"myexit"}
    sk.send(json.dumps(opt_dic).encode())
    exit("歡迎下次再來")

def download():
    operate_dict = {
        "operate":"download",
        "filename":"ceshi123.mp4"
    }
    # 把要下載的文件名稱傳遞給服務端
    operate_str = json.dumps(operate_dict)
    sk.send(operate_str.encode("utf-8"))

    # 接受服務端發過來的數據(是否可以操作)
    res = myrecv(sign=True)
    print(res)

    # 1.如果收到了服務端的可以下載的提示,就創建一個文件夾用來存放下載的影片
    if res["result"]:
        try:
            os.mkdir("mydownload")
        except:
            pass
    else:
        print("沒有該文件")
    # 2.接受文件名字和文件大小
    dic = myrecv(sign=True)
    print(dic)
    # 3.接収真實的文件
    with open("./mydownload/" + dic["filename"],mode='wb') as fp:
        while dic["filesize"]:
            content = sk.recv(102400)
            fp.write(content)
            dic["filesize"] -= len(content)
    print("客戶端下載完畢")





operate_lst1 = [("註冊",register),
               ("登錄",login),
               ("退出",myexit)]
operate_lst2 = [("下載",download),
                ("退出",myexit)]

def main(operate_lst):
    for i,tup in enumerate(operate_lst,start=1):
        print(i,tup[0])

    num = int(input("請選擇您要進行的操作>>>"))
    res = operate_lst[num-1][1]()
    return res

while True:
    res = main(operate_lst1)
    if res["result"]:
        while True:
            res = main(operate_lst2)


sk.close()

服務端

# 服務端
import socketserver
import json
import hashlib
import os
import struct


# 找當前資料庫文件所在的絕對路徑
base_path = os.getcwd()
# F:\OldBoyPython\week6\day36\db\userinfo.txt
userinfo = os.path.join(base_path,"db","userinfo.txt")

class Auth():
    @ staticmethod
    def md5(usr,pwd):
        md5_obj = hashlib.md5(usr.encode())
        md5_obj.update(pwd.encode())
        return md5_obj.hexdigest()

    @ classmethod
    def register(cls,opt_dic):
        # 1.檢測註冊的用戶是否存在
        with open(userinfo,mode='r',encoding='utf-8') as fp:
            for line in fp:
                username = line.split(":")[0]
                if username == opt_dic["user"]:
                    return {"result":False,"info":"用戶名存在了"}
        # 2.當前用戶可以註冊
        with open(userinfo, mode='a+', encoding='utf-8') as fp:
            # 帳號就是字典的帳號,密碼使用md5加密處理後再寫入文件
            strvar = "%s:%s\n" % (opt_dic["user"],cls.md5(opt_dic["user"],opt_dic["passwd"]))
            fp.write(strvar)

        # 3.返回一個註冊成功的狀態
        return {"result":True,"info":"註冊成功"}

    @ classmethod
    def login(cls,opt_dic):
        with open(userinfo,mode='r',encoding='utf-8') as fp:
            for line in fp:
                username,password = line.strip().split(":")
                if username == opt_dic["user"] and password == cls.md5(opt_dic["user"],opt_dic["passwd"]):
                    return {"result":True,"info":"登陸成功"}

            return {"result":False,"info":"登錄失敗"}

    @ classmethod
    def myexit(cls,opt_dic):
        return {"result":"myexit"}

class FTPServer(socketserver.BaseRequestHandler):
    def handle(self):
        while True:
            opt_dic = self.myrecv()
            print(opt_dic) # {'user': 'libolun', 'passwd': '111', 'operate': 'register'}
            if hasattr(Auth,opt_dic["operate"]):
                res = getattr(Auth,opt_dic["operate"])(opt_dic)
                if res["result"] == "myexit":
                    return
                self.mysend(res)

                if res["result"]: # 接受介面2數據
                    while True:
                        opt_dic = self.myrecv()
                        print(opt_dic)

                        if opt_dic["operate"] == "myexit":
                            return

                        if hasattr(self,opt_dic["operate"]):
                            getattr(self,opt_dic["operate"])(opt_dic)



            else:
                dic = {"result":False,"info":"沒有該操作"}
                self.mysend(dic)


    def myrecv(self):
        info = self.request.recv(1024)
        opt_str = info.decode()
        opt_dic = json.loads(opt_str)
        return opt_dic

    def mysend(self,send_info,sign=False):
        send_info = json.dumps(send_info).encode()
        if sign:
            res = struct.pack("i",len(send_info))
            self.request.send(res)
        self.request.send(send_info)

    def download(self,opt_dic):
        filename = opt_dic["filename"] # 獲取用戶在客戶端輸入的文件名
        file_abs = os.path.join(base_path,"video",filename) # 獲取到要下載影片的絕對路徑
        if os.path.exists(file_abs): # 如果文件存在
            # 1.告訴客戶端,文件存在,可以下載
            dic = {"result":True,"info":"文件存在,可以下載"}
            self.mysend(dic,sign=True)

            # 2.發送文件的名字和文件的大小
            filesize = os.path.getsize(file_abs)
            dic = {"filename":filename,"filesize":filesize}
            self.mysend(dic,sign=True)

            # 3.真正開始發送數據
            with open(file_abs,mode='rb') as fp:
                while filesize:
                    content = fp.read(102400)
                    self.request.send(content)
                    filesize -= len(content)
            print("伺服器下載完畢")
        else:
            dic = {"result":False,"info":"文件不存在"}
            self.mysend(dic,sign=True)


myserver = socketserver.ThreadingTCPServer(("127.0.0.1",9000),FTPServer)
myserver.serve_forever()