嘘~ 正在从服务器偷取页面 . . .

网络通信编程学习(7)/ FTP项目(1) —— 项目需求梳理以及基本框架搭建


项目需求梳理

    1. 用户登录认证(目前只实现一次连接一个客户端,即单线程)
        1.1 用户密码 md5 加密
    2. 查看目录
        2.1 ls 命令 表示查看用户在服务端的目录,默认是家目录
        2.2 dir 命令 表示用户查看在客户端的目录
    3. 切换目录
        3.1 用户使用 cd 命令切换服务端目录
            3.1.1 实际上在服务端并没有真正切换目录
            3.1.2 而且服务端代码编写时要注意,对用户来说,家目录就是最高一级,即对用户而言,home/username
                  是可以达到的路径,但 server/home 目录对用户来说是不存在路径
            3.1.3 在切换路径后,客户端要记录用户此时切换到的路径,为接下来的 断点续存 功能做准备,不然会出现客
                  户端重新连接继续下载时找不到要下载文件在服务端路径的情况
    4. 下载/上传/断点续存 进度条 创建
    5. 下载功能
        5.1 下载完成前,文件命名为 “filename.download”
            5.1.1 在创建 “filename.download” 文件前判断该文件是否存在,如果存在,就在 download 后面再
                  加时间戳后缀
            5.1.2 若 FTP 在文件下载完成前崩溃导致文件未下载完成,创建 shelvel 对象,记录 文件名,未下载完
                  成文件在服务端的路径,已下载的大小,为 断点续存 功能编写做准备
            5.1.3 文件下载完成后,将后缀部分去除,如果去除后缀后文件已存在,就增加时间戳后缀,即“filename.时
                  间戳”
    6. 上传功能(基本和下载功能一致)
        6.1 下载完成前,文件命名为 “filename.upload”
            6.1.1 在创建 “filename.upload” 文件前判断该文件是否存在,如果存在,就在 upload 后面再
                  加时间戳后缀
            6.1.2 若 FTP 在文件上传完成前崩溃导致文件未上传完成,创建 shelvel 对象,记录 文件名,未上传完
                  成文件在服务端的路径,已下载的大小,为 断点续存 功能编写做准备
            6.1.3 文件下载完成后,将后缀部分去除,如果去除后缀后文件已存在,就增加时间戳后缀,即“filename.时
                  间戳”
     7. 下载/上传 断点续存功能
        7.1 在用户登陆成功后,打印未下载完成、未上传完成的文件名,供用户选择是否要继续上传或下载
        7.2 在用户选择继续下载或上传后,仍出现进度条并且从上次断的百分比开始增加
        7.3 文件命名仍和下载/上传功能中文件命名一致

项目基本框架搭建

项目文件说明

-- FTP (项目名称)
    -- client 客户端
    -- server 服务端
        -- bin (存放可执行的二进制文件)
        -- conf (存放配置文件)
            -- accounts.ini (包括用户名称和密码,在此文件中的密码使用 md5 加密,非明文显示)
            -- setting.py (包括服务端接口、用户家目录等内容)
        -- home(存放用户家目录,比如 home\aoteman 是 用户 aoteman 的家目录)
        -- lib (存放的是函数调用的信息,在windows操作系统中起到链接程序和函数的作用。其意义在于代码重用,程
                序员将常用的功能写成函数,保存为lib文件,在以后编程要实现这些功能的时候,就不需要再重新编写代
                码,而是直接调用写好的lib文件,这很大程度上减轻程序员的负担。)
        -- log (项目日志)

bin 文件夹下 FTPServer.py 文件

# coding=gbk

import os,sys

# 因为要调用 server 文件夹下的其他 python 文件,所以程序。路径应为 \..\..\server
# 先返回 FTPServer.py 文件的绝对路径,在往上两级得到 server 文件夹的绝对路径
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# print(BASE_DIR)

sys.path.append(BASE_DIR)

if __name__ == '__main__':
    # 从 lib 文件夹下 导入 management.py 文件
    from lib import management

    # sys.argv 可以简单的理解为 将 终端命令 作为参数传入, sys.argv 的结果是列表
    # 比如说在终端输入 python server\bin\FTPServer.py start 运行,那么此时的 sys.argv 就是 ['server\\bin\\FTPServer.py', 'start']
    FTP = management.FTPManagement(sys.argv)
    FTP.execute()

lib 文件夹下 management.py

# coding=gbk

from lib import main

class FTPManagement(object):
    def __init__(self, sys_argv):
        self.sys_argv = sys_argv # 终端命令运行时传入的参数
        # print(self.sys_argv)

    def help_msg(self):
        """
        帮助信息,为用户打印提示信息
        """
        help_msg = """
        start   start up server
        """

        # 退出并打印
        exit(help_msg)

    def parameter_verification(self):
        """
        验证参数合法性
            1. 项目启动时的命令设置为 python 文件路径 命令(如 start)
                1.1 所以如果参数长度小于 2,应该向用户提示服务端可以接受的,命令
                1.2 只需要判断长度是否小于2,如果大于2,程序只把 sys.argv 列表中的第二位参数视为命令
        """
        if len(self.sys_argv) < 2:
            self.help_msg()

    def execute(self):
        """解析指令"""
        self.parameter_verification() # 如果未通过参数验证,退出并打印 帮助信息,不会再运行下面的代码
        parameter = self.sys_argv[1] # 取出代表命令的参数
        # print(parameter)

        # 判断 FTPManagement类 中是否有与命令参数相同的函数
        # 即如果命令是 start, 那么需要判断 FTPManagement类 中是否有 self.start 函数
            # 如果有此函数,调用函数
            # 如果没有此函数,打印 提示信息
        if hasattr(self, parameter): # FTPManagement类 中有 self.start 函数
            func = getattr(self, parameter) # func 赋值为 self.start 函数
            func() # 运行 self.start 函数
        else:
            print(" command does not exist !")
            print(" start   start up server !")

    def start(self):
        # print("------------")
        Server = main.Server(self)
        Server.run_forever()

lib 文件夹下 main.py

# coding=gbk
import socket

from conf import setting

class Server(object):
    MSG_SIZE = 8192  # 一次性接收的信息长度

    # 状态码
    STATUS_CODE = {

    }

    def __init__(self, management):
        self.management = management

        self.user_home = setting.USER_HOME_DIR # 记录用户家目录
        self.current_dir = self.user_home # 记录用户当前目录,用户切换目录时使用
        self.shelve_obj = None # 记录下载/上传未完成的文件信息

        # 建立服务端
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 买手机
        self.sock.bind((setting.HOST, setting.PORT)) # 装上手机卡
        self.sock.listen(5) # 开机,最大监听数为 5

    def run_forever(self):
        """正式启动服务端"""
        print(("FTP Server start %s %s " % (setting.HOST, setting.PORT)).center(50, "-"))

        while True:
            self.conn, self.addr = self.sock.accept()  # 等待电话
            print(self.addr)

            print("Connection from %s" % (self.addr, )) # self.addr 是元组,所以选择 (self.addr, )的形式

            try: # 防止报错而服务端退出程序
                self.handle() # 处理客户端和服务端和交互问题
            except Exception as e:
                print(e)

    def handle(self):
        pass

conf 文件夹下 setting.py 文件

# coding=gbk
import os.path

# 服务器端口
HOST = "0.0.0.0"
PORT = 9999

# 用户家目录
# 先切换到 server 目录,在切换到 home 目录
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
USER_HOME_DIR = os.path.join(BASE_DIR, "home")

client 文件夹下 FTPClient.py 文件

# coding=gbk
import optparse
import shelve
import socket

class Client(object):
    MSG_SIZE = 8192 # 一次性接收的信息长度

    def __init__(self):
        self.show_to_client = None # 输入命令时显示给用户的信息,如 【\username】>>:
        self.username = None # 记录用户名称
        self.current_dir = None # 记录用户下载文件时,文件在客户端的路径
        self.shelve_obj = shelve.open("FTP_Download") # 记录未下载完成的文件信息
        self.shelve_obj = shelve.open("FTP_Upload") # 记录未上传完成的文件信息

        # 终端命令参数
        # 输入 python client/FTPClient.py -h 得到提示信息
        # 比如输入 python client/FTPClient.py -H 123 -P 12456 返回的结果是 self.option = {'serverHost': '123', 'port': 12456}, self.args = []
        # 比如输入 python client/FTPClient.py 123   456   789, 返回的结果是 self.option = {'serverHost': None, 'port': None}, self.args = ['123', '456', '789']
        parser = optparse.OptionParser()
        parser.add_option("-H", "--serverHost", dest="serverHost", help="ftp server ip_addr")
        parser.add_option("-P", "--port", type="int", dest="port", help="ftp server port")
        self.options, self.args = parser.parse_args()  # 取出参数
        # print(self.options, self.args)

        self.parameter_test()
        self.make_connection()

    def parameter_test(self):
        """参数检验,保证用户输入了 -H 和 -P 内容,不然客户端无法连接服务端端口"""
        if not self.options.serverHost or not self.options.port: # 其中一项为空
            exit("Error: must supply serverHost and port !")

    def make_connection(self):
        """客户端连接"""
        self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 买手机
        self.client.connect((self.options.serverHost, self.options.port)) # 打电话
        print("Connection successful !")

if __name__ == '__main__':
    client = Client()

当前运行结果(终端运行)

服务端

客户端


文章作者: New Ass
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 New Ass !
  目录