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 = '\r [%-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

 

:

 

 

 

posted @ 2017-12-29 15:41  zhuh  阅读(454)  评论(0编辑  收藏  举报