Python implementation terminal FTP file transfer

Terminal implemented FTP file transfer

Code structure:

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

Run shot:

 

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()

 

Guess you like

Origin www.cnblogs.com/noonjuan/p/11314736.html