Python實現終端FTP文件傳輸


實現終端FTP文件傳輸

代碼結構:

.
├── client.py
├── readme.txt
└── server.py

運行截圖:

 

readme.txt

tftp文件服務器

項目功能:
    * 客戶端有簡單的頁面命令提示
    * 功能包含:
        1、查看服務器文件庫中的文件列表(普通文件) -> os.listdir
        2、可以下載其中的某個文件到本地
        3、可以上傳客戶端文件到服務器文件庫
    * 服務器需求:
        1、允許多個客戶端同時操作
        2、每個客戶端可能會連續發送命令

技術分析:
    1、TCP套接字更適合文件傳輸
    2、並發方案 -> fork多進程並發
    3、對文件的讀寫操作
    4、獲取文件列表 -> os.listdir() 或 tree
    5、粘包的處理

整體結構設計:
    1、服務器功能封裝在類中(上傳,下載,查看列表)
    2、創建套接字,流程函數調用main()
    3、客戶端負責發起請求,接收回復,展示
    4、服務端負責接受請求,邏輯處理

編程實現:
    1、搭建整體結構,創建網絡連接
    2、創建多進程和類的結構
    3、每個功能模塊的實現

模塊方法:
    os.listdir(path)
    os.path.isfile()
    os.path.isdir()
    

 

server.py

# server.py

import struct
from socket import *
import os
import signal
import sys
import time

# 文件庫
FILE_PATH = '/home/noon/Python/Example/'

# 實現功能模塊
class TftpServer(object):
    def __init__(self, sockfd, addr):
        super().__init__()
        self.sockfd = sockfd
        self.addr = addr
        self.opt = ''

    def display(self):
        re = ''
        for i in os.listdir(FILE_PATH):
            re += i + '\n'
        self.sockfd.send(re.encode())

    def download(self):
        '下載模塊功能實現'
        # 嘗試打開文件
        filename = FILE_PATH + self.opt.split(' ')[1]
        print(filename)
        try:
            fp = open(filename, 'rb')
        except:
            self.sockfd.send(b'Failed to open file')
        else:
            self.sockfd.send(b'Ready to transfer')
            # 循環發送數據
            while True:        
                data = fp.read(1024)        
                if not data:
                    # 如果傳輸完畢,data為空,傳輸0,跳出循環
                    res = struct.pack('i', 0)
                    self.sockfd.send(res)
                    break
                res = struct.pack('i', len(data))
                self.sockfd.send(res)
                self.sockfd.send(data)
            print('Done!')

    def upload(self):
        filename = FILE_PATH + self.opt.split(' ')[1]
        try:
            fp = open(filename, 'wb')
        except:
            self.sockfd.send('Unable to open file'.encode())
        else:
            self.sockfd.send(b'Ready to upload')
            while True:
                res = self.sockfd.recv(4)
                length = struct.unpack('i', res)[0]
                if length == 0:
                    break
                data = self.sockfd.recv(length)
                fp.write(data)
            fp.close()
            print('Done!')


    def quit(self):
        print(self.addr, '斷開連接')
        self.sockfd.close()
        sys.exit()

# 主流程
def main():
    HOST = '0.0.0.0'
    PORT = 5555
    ADDR = (HOST, PORT)

    sockfd = socket()
    sockfd.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    sockfd.bind(ADDR)
    sockfd.listen(5)

    # 通知內核對子進程的結束不關心,由內核回收。
    signal.signal(signal.SIGCHLD, signal.SIG_IGN)

    while True:
        try:
            connfd, addr = sockfd.accept()
        except KeyboardInterrupt:
            sockfd.close()
            sys.exit('服務器退出')
        except Exception as e:
            print(e)
            continue

        print('連接成功:', addr)

        # 創建子進程
        pid = os.fork()

        if pid == 0:
            sockfd.close()
            tftp = TftpServer(connfd, addr)
            while True:
                tftp.opt = connfd.recv(1024).decode()
                if tftp.opt == 'display':
                    tftp.display()
                elif tftp.opt.startswith('download'):
                    tftp.download()
                elif tftp.opt.startswith('upload'):
                    tftp.upload()
                elif tftp.opt == 'quit':
                    tftp.quit()
        else:
            connfd.close()
            continue


if __name__ == '__main__':
    main()

 

client.py

# client.py

from socket import *
import sys
import time
import struct

# 實現各種功能請求
class TftpClient(object):
    def __init__(self, sockfd):
        super().__init__()
        self.sockfd = sockfd
        self.opt = ''

    def panel(self):
        print('+', '*'*30, '+', sep='')
        print('+', 'display'.center(30), '+', sep='')
        print('+', 'download'.center(30), '+', sep='')
        print('+', 'upload'.center(30), '+', sep='')
        print('+', 'quit'.center(30), '+', sep='')
        print('+', '*'*30, '+', sep='')

    def display(self):
        self.sockfd.send(b'display')
        print(self.sockfd.recv(1024).decode())

    def download(self):
        '客戶端下載請求'
        # 先使用display命令向服務器請求文件列表,驗證用戶想要下載的文件是否存在
        filename = input('filename>> ')
        if not filename:
            return
        self.sockfd.send(b'display')
        files = self.sockfd.recv(1024).decode().split('\n')
        if not filename in files:
            print('Cannot locate', filename)
            return
        # 文件存在,發送下載請求到服務端,並接收返回結果
        data = 'download ' + filename
        self.sockfd.send(data.encode())
        data = self.sockfd.recv(1024).decode()
        # 如果服務端無法打開文件
        if data == 'Failed to open file':
            print('Failed to open file')
        # 可以執行下載操作
        else:
            # 調用寫方法
            print(data)
            self.write(filename)
            print('Done!')

    def write(self, filename):
        '從服務器下載文件'
        # 考慮到粘包問題,導入struct模塊,接收服務端要發送的數據的大小,再按照這個大小接收數據,循環執行
        fp = open(filename, 'wb')
        while True:
            # 接收數據大小,調用struct.unpack方法獲得數據大小
            res = self.sockfd.recv(4)
            length = struct.unpack('i', res)[0]
            # 如果數據大小為0,說明傳輸結束,退出循環
            if length == 0:
                break
            # 按照數據的大小接收數據
            data = self.sockfd.recv(length)
            fp.write(data)
        fp.close()

    def upload(self):
        # 文件路徑
        filepath = input('filepath>> ')
        try:
            fp = open(filepath, 'rb')
        except:
            print('Unable to open', filepath)
            return
        else:
            # 文件上傳要保存為什么名字
            # 先使用display命令向服務器請求文件列表,驗證用戶想要上傳的文件名是否存在
            filename = input('filename>> ')
            if not filename:
                return
            self.sockfd.send(b'display')
            files = self.sockfd.recv(1024).decode().split('\n')
            if filename in files:
                print('File already exists!')
                return
            # 可以上傳
            data = 'upload ' + filename
            self.sockfd.send(data.encode())
            data = self.sockfd.recv(1024).decode()
            if data == 'Unable to open file':
                print('服務器打開文件出錯')
                return 
            else:
                self.read(fp)

    def read(self, fp):
        '讀取文件上傳服務器'
        while True:
            data = fp.read(1024)
            if not data:
                res = struct.pack('i', 0)
                self.sockfd.send(res)
                break
            res = struct.pack('i', len(data))
            self.sockfd.send(res)
            self.sockfd.send(data)
        print('Done!')

    def quit(self):
        self.sockfd.send(b'quit')
        self.sockfd.close()
        sys.exit('客戶端關閉')

# 創建套接字,建立連接
def main():
    argc = len(sys.argv)
    if argc != 3:
        sys.exit('Usage: python client.py host port')
    else:
        HOST = sys.argv[1]
        PORT = int(sys.argv[2])
        ADDR = HOST, PORT

        sockfd = socket()
        try:
            sockfd.connect(ADDR)
        except ConnectionRefusedError:
            sys.exit('無法連接到服務端')

        tftp = TftpClient(sockfd)

        tftp.panel()
        while True:
            try:
                tftp.opt = input('>> ').lower()
            except KeyboardInterrupt:
                tftp.quit()
            if tftp.opt == 'display':
                tftp.display()
            elif tftp.opt == 'download':
                tftp.download()
            elif tftp.opt == 'upload':
                tftp.upload()
            elif tftp.opt == 'quit':
                tftp.quit()
            else:
                continue


if __name__ == '__main__':
    main()

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM