• FTP2


    FTP:

    环境:windows, python 3.5
    功能:
    1.用户加密认证,可自行配置家目录磁盘大小
    2.多用户登陆
    3.查看当前目录(家目录权限下)
    4.切换目录(家目录权限下)
    5.上传下载,进度条展示,MD5认证
    6.下载支持断点续传(仅限传输过程中意外终断,后续支持继续传输)

    结构:
    ftp_client   ---|
                    bin ---|
                            start_client.py     ......启动客户端
                    conf---|
                            config.py           ......客户端参数配置
                            system.ini          ......客户端参数配置文件
                    core---|
                            ftp_client.py       ......客户端主程序
    ftp_server  ---|
                    bin ---|
                            start_server.py     ......启动服务端
                    conf---|
                            config.py           ......服务端参数配置
                            system.ini          ......服务端参数配置文件
                    core---|
                            ftp_server.py       ......服务端主程序
                            cmd.py              ......执行系统命令,如(mkdir,dir等)
                    db  ---|
                            data.py             ......存取用户数据
                    home                        ......用户家目录,每个用户生成各自姓名的家目录

    功能实现:
    ftp_server.py启动后进入ftp_server.py,执行类MyServer中的handle()方法等待客户端连接;
    客户端通过 start_client.py 启动进入 ftp_client.py;
    执行 类FtpClient 中的connect()方法建立连接;
    进入 interactive()入口,首先调用authenticate()加密登陆,如未注册则调用register()注册;
    然和根据输入内容,分别映射进入到相应方法中,再各个方法里;
    在各个方法中,首先将action发送给服务端;
    服务端根据action将映射到服务端的各个方法中,进行数据交互;

    如何使用:
    启动start_server.py,启动start_client.py;
    首先输入用户名,如用户名未注册则提示注册,注册时需输入家目录磁盘大小,单位为 b,登陆完成,进入菜单;
    根据提示输入需要进行的操作:
    dir (path):展示当前目录或者展示所给路径目录,如路径超过权限则提示权限不够;
    cd (path):切换目录或返回家目录,根据输入的路径切换到相应目录,格式为cd ./path,必须输入./+路径,./表示当前位置,直接cd则返回家目录
    put filepath:上传文件到家目录下
    get filepath:下载文件到指定目录,只允许下载家目录里存在的文件(下载前请先上传文件到家目录下),支持断点续传

     ftp_client:

    bin:

    #!/usr/bin/env python
    # -*-coding:utf-8-*-
    # Author:zh
    import os
    import sys
    PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    sys.path.append(PATH)
    import core
    
    core.ftp_client.run()
    start_client.py

    conf:

    #!/usr/bin/env python
    # -*-coding:utf-8-*-
    # _author_=zh
    import os
    import configparser
    PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    
    
    class Configuration(object):
        def __init__(self):
            self.config = configparser.ConfigParser()
            self.name = PATH+os.sep+"conf"+os.sep+"system.ini"
    
        def init_config(self):
            # 初始化配置文件,ip :客户端IP,port:客户端端口
            if not os.path.exists(self.name):
                self.config["config"] = {"ip": "localhost", "port": 1234}
                self.config.write(open(self.name, "w", encoding="utf-8", ))
    
        def get_config(self, head="config"):
            '''
            获取配置文件数据
            :param head: 配置文件的section,默认取初始化文件config的数据
            :return:返回head中的所有数据(列表)
            '''
            self.init_config()  # 取文件数据之前生成配置文件
            self.config.read(self.name, encoding="utf-8")
            if self.config.has_section(head):
                section = self.config.sections()
                return self.config.items(section[0])
    config.py

    core:

    #!/usr/bin/env python
    # -*-coding:utf-8-*-
    # Author:zh
    import socket
    import os
    import sys
    import json
    import hashlib
    from conf import config
    # 服务端交互传送结果
    SIGN_DICT = {
        '801': '用户名存在',
        '802': '用户名不存在',
        '804': '路径不存在',
        '806': '文件超过磁盘限额',
        '807': '传输完毕',
        '808': '传输成功',
        '809': '传输失败',
        '811': '文件无权限访问',
        '812': '文件不存在'
    }
    
    
    class FtpClient(object):
        def __init__(self):
            self.client = socket.socket()
    
        def connect(self, ip, port):
            self.client.connect((ip, port))
    
        def interactive(self):
            # 入口
            global catalog
            name = self.authenticate()
            self.catalog = name
            while True:
                cmd = input("%s>>" % self.catalog).strip()
                if len(cmd) == 0:
                    continue
                cmd_str = cmd.split()[0]
                if hasattr(self, cmd_str):
                    func = getattr(self, cmd_str)
                    func(cmd, name)
                else:
                    print("输入格式错误")
                    self.help()
    
        @staticmethod
        def help():
            msg = '''
    dir path(无path默认显示当前目录下文件) 
    cd path(切换目录)
    get filename(下载文件)
    put filename(上传文件)
    quit 退出
            '''
            print(msg)
    
        def register(self, name):
            # 注册
            name = name
            pwd = input("请输入密码(q退出):").strip()
            limit_size = input("请输入用户磁盘空间(单位:b):")
            if pwd.lower() == 'q':
                exit()
            hash_md5 = hashlib.md5()
            hash_md5.update(pwd.encode())
            pwd_md5 = hash_md5.hexdigest()
            user_data = {
                "name": name,
                "pwd": pwd_md5,
                "limit_size": limit_size
            }
            self.client.send(json.dumps(user_data).encode())
    
        def authenticate(self):
            # 登陆
            while True:
                name = input("请输入用户名:").strip().lower()
                msg_dict = {
                    "action": "authenticate",
                    "username": name
                }
                self.client.send(json.dumps(msg_dict).encode())
                server_response = self.client.recv(1024)
                server_response = json.loads(server_response.decode())
                if server_response["sign"] == '802':
                    print(SIGN_DICT['802'])
                    print("请注册!")
                    self.register(name)
                    return name
                elif server_response["sign"] == '801':
                    value = server_response["value"]
                    while True:
                        pwd = input("请输入密码:").strip()
                        hash_md5 = hashlib.md5()
                        hash_md5.update(pwd.encode())
                        pwd_md5 = hash_md5.hexdigest()
                        if value["pwd"] == pwd_md5:
                            print("登陆成功")
                            return name
                        else:
                            print("密码错误,请重新输入")
    
        def dir(self, *args):
            # 查看当前目录
            cmd_split = args[0]
            msg_dict = {
                "action": "dir",
                "command": cmd_split,
                "user_name": self.catalog
            }
            self.client.send(json.dumps(msg_dict).encode())
            server_response = self.client.recv(1024)
            answer = json.loads(server_response.decode())
            if type(answer) is list:
                print(answer[0], answer[1])
            else:
                print(answer)
    
        def cd(self, *args):
            # 切换目录,不带路径则直接切换回家目录下
            cmd_split = args[0].split()
            if len(cmd_split) > 1:
                dir_path = cmd_split[1]
                msg_dict = {
                    "action": "cd",
                    "dir_path": dir_path,
                    "now_path": self.catalog
                }
                self.client.send(json.dumps(msg_dict).encode())
                data = self.client.recv(1024)
                if data.decode() == '803':
                    self.catalog = dir_path.replace('.', self.catalog)
                    print("切换成功")
                else:
                    print(SIGN_DICT[data.decode()])
            else:
                self.catalog = args[1]
    
        def put(self, *args):
            # 上传
            cmd_split = args[0].split()
            if len(cmd_split) > 1:
                filename = cmd_split[1]
                if os.path.isfile(filename):
                    file_size = os.stat(filename).st_size
                    msg_dict = {
                        "action": "put",
                        "filename": filename,
                        "size": file_size,
                        "user_name": args[1]
                    }
                    self.client.send(json.dumps(msg_dict).encode())
                    # 防止黏包,等服务器确认
                    server_response = (self.client.recv(1024)).decode()
                    if server_response == '805':
                        file = open(filename, 'rb')
                        hash_md5 = hashlib.md5()
                        send_data = 0
                        for line in file:
                            self.client.send(line)
                            send_data += len(line)
                            self.progress_bar(send_data, file_size)
                            hash_md5.update(line)
                        else:
                            file.close()
                            pwd_md5 = hash_md5.hexdigest()
                        server_answer = (self.client.recv(1024)).decode()
                        if server_answer == '807':
                            self.client.send(pwd_md5.encode())
                            put_answer = (self.client.recv(1024)).decode()
                            print(SIGN_DICT[put_answer])
                    else:
                        print(SIGN_DICT[server_response])
                else:
                    print(filename, "is not exist")
            else:
                print("请输入上传文件路径")
    
        def get(self, *args):
            # 下载,支持断点续传
            cmd_split = args[0].split()
            if len(cmd_split) > 1:
                file_path = cmd_split[1]
                msg_dict = {
                    "action": "get",
                    "file_path": file_path,
                    "user_name": args[1]
                }
                self.client.send(json.dumps(msg_dict).encode())
                server_response = (self.client.recv(1024)).decode()
                if server_response == '810':
                    self.client.send('810'.encode())  # 避免黏包
                    file_data = self.client.recv(1024)
                    file_length = (json.loads(file_data.decode()))["length"]
                    file_name = (json.loads(file_data.decode()))["file_name"]
                    get_path = input("请输入存放路径:")
                    if os.path.isdir(get_path):
                        if os.path.exists(get_path+os.sep+file_name):
                            choose = input("文件已存在,请选择(1.覆盖,2.重命名,3续传):")
                            if choose == "1":
                                os.remove(get_path+os.sep+file_name)
                                exists_length = 0
                                get_path = get_path+os.sep+file_name
                            elif choose == '2':
                                exists_length = 0
                                get_path = get_path + os.sep + file_name+".new"
                            elif choose == "3":
                                exists_length = os.stat(get_path+os.sep+file_name).st_size
                                get_path = get_path + os.sep + file_name
                        else:
                            exists_length = 0
                            get_path = get_path + os.sep + file_name
                        self.client.send(str(exists_length).encode())
                        file = open(get_path, "wb")
                        file.seek(int(exists_length))
                        hash_md5 = hashlib.md5()
                        get_length = exists_length
                        while get_length < int(file_length):
                            data = self.client.recv(1024)
                            file.write(data)
                            get_length += len(data)
                            hash_md5.update(data)
                            self.progress_bar(get_length, int(file_length))
                        else:
                            pwd_md5 = hash_md5.hexdigest()
                            file.close()
                        self.client.send('传输完成'.encode())
                        get_md5 = (self.client.recv(1024)).decode()
                        if get_md5 == pwd_md5:
                            print("下载成功")
                        else:
                            print("下载失败")
                            os.remove(get_path)
                    else:
                        print("路径输入错误")
                else:
                    print(SIGN_DICT[server_response])
    
            else:
                print("请输入下载文件路径")
    
        @staticmethod
        def quit(*args):
            # 退出
            print("%s已退出" % args[1])
            exit()
    
        @staticmethod
        def progress_bar(send_data, all_data):
            # 进度条展示
            # send_data:已发送的长度,all_data:总长度
            ret = send_data / all_data
            num = int(ret * 100)
            view = '
     [%-100s]%d%%' % ("=" * num, 100,)
            sys.stdout.write(view)
            sys.stdout.flush()
    
    
    def run():
        client = FtpClient()
        conf = config.Configuration()
        conf_data = conf.get_config()
        client.connect(conf_data[0][1], int(conf_data[1][1]))
        client.help()
        client.interactive()
    ftp_client.py

    ftp_server:

    bin

    #!/usr/bin/env python
    # -*-coding:utf-8-*-
    # Author:zh
    import os
    import sys
    PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    sys.path.append(PATH)
    import core
    
    core.ftp_server.run()
    start_server.py

    conf:

    #!/usr/bin/env python
    # -*-coding:utf-8-*-
    # _author_=zh
    import os
    import configparser
    PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    
    
    class Configuration(object):
        def __init__(self):
            self.config = configparser.ConfigParser()
            self.name = PATH+os.sep+"conf"+os.sep+"system.ini"
    
        def init_config(self):
            # 初始化配置文件,IP:服务端IP,port:服务端端口,limit_size:家目录容量,HOME_PATH:用户家目录地址
            if not os.path.exists(self.name):
                self.config["config"] = {"ip": "localhost", "port": 1234}
                self.config.write(open(self.name, "w", encoding="utf-8", ))
    
        def get_config(self, head="config"):
            '''
            获取配置文件数据
            :param head: 配置文件的section,默认取初始化文件config的数据
            :return:返回head中的所有数据(列表)
            '''
            self.init_config()  # 取文件数据之前生成配置文件
            self.config.read(self.name, encoding="utf-8")
            if self.config.has_section(head):
                section = self.config.sections()
                return self.config.items(section[0])
    config.py

    core:

    #!/usr/bin/env python
    # _*_coding:utf-8_*_
    # _author_=zh
    import os
    import locale
    import codecs
    import subprocess
    PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))+os.sep+"home"
    
    
    def order(cmd):
        '''
        执行命令结果输出到屏幕
        :param cmd: 输入的命令
        :return:
        '''
        word = subprocess.Popen(args=cmd, cwd=PATH, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
        # 将结果decode输出,自动获取不同操作系统的默认编码
        return word.stderr.read().decode(codecs.lookup(locale.getpreferredencoding()).name), 
            word.stdout.read().decode(codecs.lookup(locale.getpreferredencoding()).name)
    cmd.py
    #!/usr/bin/env python
    # -*-coding:utf-8-*-
    # Author:zh
    import socketserver
    import json
    import hashlib
    import os
    from conf import config
    import db
    import core
    
    
    PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    
    
    class MyServer(socketserver.BaseRequestHandler):
        def handle(self):
            while True:
                try:
                    data = self.request.recv(1024)
                    if len(data) > 0:
                        cmd_dict = json.loads(data.decode())
                        action = cmd_dict['action']
                        if hasattr(self, action):
                            func = getattr(self, action)
                            func(cmd_dict)
                    else:
                        break
                except (ConnectionResetError, OSError) as e:
                    print(e)
                    break
    
        def dir(self, *args):
            # 查看目录
            cmd_dict = args[0]
            command_data = cmd_dict["command"]
            command = command_data.split()
            user_name = cmd_dict["user_name"]
            home_path = PATH + os.sep + "home" + os.sep + user_name
            if len(command) > 1:
                path_data = command[1]
                if path_data[:len(home_path)] == home_path:
                    answer = core.cmd.order(command_data)
                else:
                    answer = '路径错误'
            else:
                answer = core.cmd.order('%s %s' % (command[0],home_path))
            self.request.send(json.dumps(answer).encode())
    
        def cd(self, *args):
            # 切换目录,默认显示家目录,格式为cd ./path
            cmd_dict = args[0]
            dir_path = cmd_dict["dir_path"]
            now_path = cmd_dict["now_path"]
            if dir_path[0] is '.':
                dir_path = dir_path.replace('.', PATH+os.sep+"home"+os.sep+now_path)
                print("dir_path:", dir_path)
                if os.path.exists(dir_path):
                    sign = '803'
                else:
                    sign = '804'
            else:
                sign = '804'
            self.request.send(sign.encode())
    
        def put(self, *args):
            # 上传
            cmd_dict = args[0]
            filename = cmd_dict["filename"][cmd_dict["filename"].rfind("\")+1:]
            file_size = cmd_dict["size"]
            user_name = cmd_dict["user_name"]
            value = db.data.read(user_name)
            limit_size = int(value["limit_size"])
            home_path = PATH+os.sep+"home"+os.sep+user_name
            if limit_size > file_size:
                if os.path.isfile(home_path+os.sep+filename):
                    file_path = home_path+os.sep+filename+'.new'
                else:
                    file_path = home_path + os.sep + filename
                sign = "805"
            else:
                sign = "806"
            self.request.send(sign.encode())
            if sign == "805":
                file = open(file_path, 'wb')
                recv_size = 0
                hash_md5 = hashlib.md5()
                while recv_size < file_size:
                    data = self.request.recv(1024)
                    hash_md5.update(data)
                    file.write(data)
                    recv_size += len(data)
                else:
                    self.request.send("807".encode())
                    file.close()
                hash_pwd = (self.request.recv(1024)).decode()
                if hash_pwd == hash_md5.hexdigest():
                    sign = '808'
                    value["limit_size"] = limit_size - file_size
                    db.data.write(user_name, value)
                else:
                    sign = '809'
                    os.remove(file_path)
                self.request.send(sign.encode())
    
        def get(self, *args):
            # 下载
            cmd_dict = args[0]
            file_path = cmd_dict["file_path"]
            user_name = cmd_dict["user_name"]
            user_path = PATH+os.sep+"home"+os.sep+user_name
            if file_path[:len(user_path)] == user_path:
                if os.path.isfile(file_path):
                    sign = '810'
                else:
                    sign = '812'
            else:
                sign = '811'
            self.request.send(sign.encode())
            if sign == '810':
                self.request.recv(1024)
                server_msg = {
                    "length": os.stat(file_path).st_size,
                    "file_name": file_path[file_path.rfind("\")+1:]
                }
                self.request.send(json.dumps(server_msg).encode())
                exists_length = (self.request.recv(1024)).decode()
                file = open(file_path, "rb")
                file.seek(int(exists_length))
                hash_md5 = hashlib.md5()
                for line in file:
                    self.request.send(line)
                    hash_md5.update(line)
                else:
                    file.close()
                    pwd_md5 = hash_md5.hexdigest()
                self.request.recv(1024)
                self.request.send(pwd_md5.encode())
    
        def authenticate(self, *args):
            # 登陆认证
            cmd_dict = args[0]
            name = cmd_dict["username"]
            value = db.data.read(name)  # 用户存在返回用户数据,不存在返回None
            if value:
                num_sign = "801"
            else:
                num_sign = "802"
            send_data = {
                "sign": num_sign,
                "value": value
            }
            self.request.send(json.dumps(send_data).encode())
            # 用户名不存在时注册,需接收注册信息并写入数据库
            if not value:
                user_data = self.request.recv(1024)
                if len(user_data) > 0:
                    user_data = json.loads(user_data.decode())
                    db.data.write(user_data["name"], user_data)
                    os.popen("mkdir %s" % (PATH+os.sep+"home"+os.sep+name))
    
    
    def run():
        conf = config.Configuration()
        conf_data = conf.get_config()
        servers = socketserver.ThreadingTCPServer((conf_data[0][1], int(conf_data[1][1])), MyServer)
        servers.serve_forever()
    ftp_server.py

    db:

    #!/usr/bin/env python
    # -*-coding:utf-8-*-
    # _author_=zh
    import shelve
    import os
    PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))+os.sep+"db"+os.sep
    
    
    def write(name, data):
        '''
        存储个人信息
        :param data: 存储的数据
        :param name: shelve的key
        '''
        file = shelve.open("%sdata" % PATH)
        file[name] = data
        file.close()
    
    
    def read(name):
        '''
        读取个人信息
        :param name:存储时传入的name
        :return:返回个人信息
        '''
        file = shelve.open("%sdata" % PATH)
        if name in list(file.keys()):
            data = file[name]
        else:
            data = None
        file.close()
        return data
    data.py

    :

     

  • 相关阅读:
    【windows 10 安装docker for windows】
    【windows本地修改git账号、路径信息】
    【echarts】简单使用流程(java)
    【MySql】explain
    【thymeleaf-标签】th:with
    【MongoDB】Windows:安装与启动
    【thymeleaf-标签】th:if
    Thread类中interrupt()、interrupted()和isInterrupted()方法详解
    java中的锁
    LinkedList集合详解
  • 原文地址:https://www.cnblogs.com/zh-20170913/p/8144796.html
Copyright © 2020-2023  润新知