作业需求

要求:

  1. 用户加密认证 1
  2. 允许同时多用户登录 1
  3. 每个用户有自己的家目录 ,且只能访问自己的家目录 1
  4. 对用户进行磁盘配额,每个用户的可用空间不同
  5. 允许用户在ftp server上随意切换目录 1
  6. 允许用户查看当前目录下文件 1
  7. 允许上传和下载文件,保证文件一致性 1
  8. 文件传输过程中显示进度条 1
  9. 附加功能:支持文件的断点续传---------------------未完成

作业分析

作业结构:

client:

java sftp 断点续传 python ftp 断点续传_json

server:

java sftp 断点续传 python ftp 断点续传_json_02

小说明:

 

在这个作业中呢,用到了很多知识点,比如用户加密认证加密传送文件都用到了hashlib模块;保存用户信息,传递一些字典用到了json模块;多用户同时在线用到了socketserver模块;还有一些命令的操作,用到了os.popen()函数;还有一些有关路径的函数,什么的。。。诶,好像也没用到好多哈。。。对了还有反射,这个非常重要。其他就没什么的了,具体的分析看下面:

client端代码

java sftp 断点续传 python ftp 断点续传_json_03

java sftp 断点续传 python ftp 断点续传_客户端_04

import socket,hashlib,json,os ,sys
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
print(base_dir)
sys.path.append(base_dir) # 添加conf文件的路径\\

from conf import config
from core import put, auth, ls, cd, mkdir


httpCode = """
"200" : "服务器成功返回网页"
"404" : "请求的网页不存在"
"403" : "出现错误"
"503" : "服务不可用"
 """


class FtpClient(object):
    def __init__(self):
        self.client = socket.socket()   # 创建一个socket客户端
        self.userMessDirt = {}
    def connect(self,host,port):
        self.client.connect((host, port))    # 客户端和服务器端进行连接
    def help(self):
        msg = '''
                ls
                pwd
                cd ../..
                get filename
                put filename
                '''
        print(msg)
    def interactive(self):
        self.userMessDirt = json.loads(self.client.recv(config.buffer).decode())
        self.localDir = os.path.basename(self.userMessDirt["homeDir"])  # 初始地址
        while True:
            cmd_str = input("[%s@user %s] #" %(self.userMessDirt["userName"], self.localDir))
            if len(cmd_str) ==0:continue
            cmd = cmd_str.split()[0]
            if hasattr(self,"cmd_%s" % cmd):
                func = getattr(self, "cmd_%s" % cmd)
                func(cmd_str)
            else:
                self.help()
    def cmd_ls(self, *args):
        '''客户端实现打印出该目录下的所有文件'''
        ls.ls(self, *args)

    def cmd_pwd(self, *args):
        '''客户端实现打印出当前目录功能'''
        pass
    def cmd_cd(self, *args):
        '''客户端实现切换目录功能'''
        cd.cd(self, *args)
    def cmd_get(self, *args):
        '''客户端下载文件'''
        pass
    def cmd_put(self, *args):
        '''客户端上传文件'''
        put.cmd_put(self,*args)
    def cmd_mkdir(self, *args):
        '''创建一个空文件夹'''
        mkdir.mkdir(self, *args)
    def auth_login(self):
        auth.auth_login(self)

    def auth_register(self):
        auth.auth_register(self)


def run():
    info = """
    1.登录
    2.注册
    >>:"""
    auth_choice = input(info).strip()
    ftp = FtpClient()
    ftp.connect("localhost",9090)
    if auth_choice == "1":
        ftp.auth_login()
    else:
        ftp.auth_register()
    ftp.interactive()

main

java sftp 断点续传 python ftp 断点续传_json_03

java sftp 断点续传 python ftp 断点续传_客户端_04

import hashlib, os, sys
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
from conf import config

def auth_login(self):
    self.client.send("login".encode("utf-8"))  # 通知对面我要登录了
    loginErrorCount = 3
    while True:
        userName_input = self.client.recv(config.buffer)
        userName = input(userName_input.decode()).strip()
        self.client.send(userName.encode("utf-8"))  # 注意这里的编码问题
        httpCode = self.client.recv(config.buffer).decode()
        if httpCode == "403":
            print("用户名错误,请重新输入")
        elif httpCode == "200":
            break
    while loginErrorCount:
        has = hashlib.md5()
        passWord_input = self.client.recv(config.buffer)
        passWord = input(passWord_input.decode()).strip()
        has.update(passWord.encode("utf-8"))  # 使用hashlib编码必须要使用byte类型
        self.client.send(has.hexdigest().encode("utf-8"))
        # print("has:",has.hexdigest())             # 测试是否生成了md5
        # self.client.send(self.passWord.encode("utf-8"))
        http_code = self.client.recv(config.buffer).decode()
        if http_code == "200":
            break
        elif http_code == "403":
            loginErrorCount -= 1
            print("密码错误,剩余\033[31;1m%s\033[0m机会" % loginErrorCount)
    if loginErrorCount == 0:
        sys.exit(0)

def auth_register(self):
    self.client.send("register".encode("utf-8"))  # 通知对面我要注册了
    has = hashlib.md5()
    has_1 = hashlib.md5()
    userName_input = self.client.recv(config.buffer)
    userName = input(userName_input.decode()).strip()
    self.client.send(userName.encode("utf-8"))
    while True:
        passWord_input = self.client.recv(config.buffer)
        passWord = input(passWord_input.decode()).strip()
        has.update(passWord.encode("utf-8"))  # 对第一次输入的密码加密
        self.client.send(has.hexdigest().encode("utf-8"))

        passWord_input_again = self.client.recv(config.buffer)
        passWord_again = input(passWord_input_again.decode()).strip()
        has_1.update(passWord_again.encode("utf-8"))  # 对第一次输入的密码加密
        self.client.send(has_1.hexdigest().encode("utf-8"))

        http_code = self.client.recv(config.buffer).decode()
        if http_code == "200":
            break
        elif http_code == "403":
            print("密码错误。。。")

client.auth

java sftp 断点续传 python ftp 断点续传_json_03

java sftp 断点续传 python ftp 断点续传_客户端_04

import json, os
base_dir = os.path.dirname(os.path.abspath(__file__))
from conf import config

def cd(self, *args):
    '''cd 中.. 是返回上层目录,/ '''
    cmd_str = args[0].split()
    if len(cmd_str) > 1:
        cmd_path = cmd_str[1]
        cmd_dirt = {
            "action" : "cd",
            "path" : cmd_path
        }
        self.client.send(json.dumps(cmd_dirt).encode("utf-8"))
        self.localDir = os.path.basename(self.client.recv(config.buffer).decode())

client.cd

ps:在这里说一下,貌似真的是各种命令的实行大多一样,只要做出来了一个,其他的就能做了

java sftp 断点续传 python ftp 断点续传_json_03

java sftp 断点续传 python ftp 断点续传_客户端_04

import sys
def progress_bar(num_cur, total):
    ratio = float(num_cur) / float(total)
    percentage = int(ratio * 100)
    r = '\r\n[%s%s]%d%%' % (">"*percentage, " "*(100-percentage), percentage )
    sys.stdout.write(r)
    sys.stdout.flush()

client.progress_bar

java sftp 断点续传 python ftp 断点续传_json_03

java sftp 断点续传 python ftp 断点续传_客户端_04

import os, json, hashlib, sys
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
from core import progress_bar
from conf import config


def cmd_put(self, *args):
    cmd_str = args[0].split()  # 提取出args中的数据,并将其按空格的形式转换成列表
    if len(cmd_str) > 1:
        fileName = cmd_str[1]
        if os.path.isfile(fileName):  # 判断这是不是个文件
            fileSize = os.stat(fileName).st_size  # 获得文件的大小
            cmd_dirt = {
                "action": "put",
                "fileName": fileName,
                "fileSize": fileSize,
                "overridden": True
            }
            self.client.send(json.dumps(cmd_dirt).encode("utf-8"))  # 用json将整个字典传过去
            self.client.recv(config.buffer)  # 等待服务器端的确认, 不用做任何处理
            with open(fileName, "rb") as fr:
                has = hashlib.md5()
                sum_size = 0
                for line in fr:
                    line_size = len(line)
                    sum_size += line_size
                    has.update(line)
                    self.client.send(has.hexdigest().encode("utf-8"))  # line 本身就是字节类型
                    return_md5 = self.client.recv(config.buffer)
                    if has.hexdigest() == return_md5.decode():
                        self.client.send(b'200')
                        self.client.recv(config.buffer)
                        self.client.send(line)
                        progress_bar.progress_bar(sum_size, cmd_dirt["fileSize"])
                    else:
                        self.client.send(b'403')
                        print("传输错误。。。")
                        break
                else:
                    print("\nfile upload success...")
        else:
            print(fileName, "不存在。。。")

client.put

java sftp 断点续传 python ftp 断点续传_json_03

java sftp 断点续传 python ftp 断点续传_客户端_04

# ls
import json, os
base_dir = os.path.dirname(os.path.abspath(__file__))
from conf import config
def ls(self, *args):
    cmd_str = args[0].split()
    if len(cmd_str) == 1 and cmd_str[0] == "ls":
        cmd_dirt = {
            "action" : "ls",
        }
        self.client.send(json.dumps(cmd_dirt).encode("utf-8"))
        return_mess = self.client.recv(config.buffer)
        print(return_mess.decode())
# mkdir
import json, os
base_dir = os.path.dirname(os.path.abspath(__file__))
from conf import config

def mkdir(self, *args):
    cmd_str = args[0].split()
    if len(cmd_str) > 1:
        fileName = cmd_str[1]
        cmd_dirt = {
            "action" : "mkdir",
            "fileName" : fileName,
        }
        self.client.send(json.dumps(cmd_dirt).encode("utf-8"))
        self.client.recv(config.buffer)

ls & mkdir(pwd、get都没做)

java sftp 断点续传 python ftp 断点续传_json_03

java sftp 断点续传 python ftp 断点续传_客户端_04

import os,sys,logging
# 最顶端的路径
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0])))

# 存放日志的目录
LOG_DIR = BASE_DIR + '/log/log.txt'

logging.basicConfig(level=logging.DEBUG,    # level=loggin.INFO意思是,把日志纪录级别设置为INFO,也就是说,只有比日志是INFO或比INFO级别更高的日志才会被纪录到文件里
                    format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
                    datefmt='%a, %d %b %Y %H:%M:%S',
                    filename=LOG_DIR,
                    filemode='a+')

# 存放用户信息的目录
USER_DIR = BASE_DIR + '/date/'

# 每次发送文件的大小
buffer = 2048
# 给用户分配的内存大小
memory = 100 * 1024 * 1024

client.config

server代码

java sftp 断点续传 python ftp 断点续传_json_03

java sftp 断点续传 python ftp 断点续传_客户端_04

import socketserver,os,sys,json
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
print(base_dir)
sys.path.append(base_dir ) # 添加conf文件的路径

from conf import config
from core import put, auth, ls, cd, mkdir

class MyTCPHandler(socketserver.BaseRequestHandler):
    def handle(self):       # 所有的请求都是在handle 中执行的
        try:
            self.userMessDirt = {}
            state = self.request.recv(config.buffer).decode()
            if state == "login":
                self.auth_login()
            elif state == "register":
                self.auth_register()
            self.request.send(json.dumps(self.userMessDirt).encode("utf-8"))
            self.current_path = os.path.join(self.userMessDirt["homeDir"])
            os.chdir(self.current_path)                                 # 将当前的目录改到self.current_path下面去,这里是指每个用户的root目录
            while True:
                cmd_dirt = json.loads(self.request.recv(config.buffer).decode())
                action = cmd_dirt["action"]
                if hasattr(self, action):
                    func = getattr(self, action)
                    func(cmd_dirt)
        except ConnectionResetError as e:
            print(self.userMessDirt["userName"], "断开。。。")
    def ls(self, *args):
        '''查看该目录下的所有文件'''
        ls.ls(self, *args)
    def pwd(self, *args):
        '''打印当前路径'''
        pass
    def cd(self, *args):
        '''切换目录'''
        cd.cd(self,*args)
    def get(self, *args):
        '''服务器端 发送文件给 客户端'''
        pass
    def mkdir(self, *args):
        '''创建一个空的文件夹'''
        mkdir.mkdir(self, *args)
    def put(self, *args):
        '''服务器端 接收 客户端给的文件'''
        put.put(self, *args)
    def auth_login(self):
        auth.auth_login(self)
    def auth_register(self):
        auth.auth_register(self)





def run():
    Host , Port = "localhost" , 9090        # ip地址
    server = socketserver.ThreadingTCPServer((Host,Port) , MyTCPHandler)    # 每来一个请求,服务端就开启一个新的线程
    server.serve_forever()

main

java sftp 断点续传 python ftp 断点续传_json_03

java sftp 断点续传 python ftp 断点续传_客户端_04

import os,sys,logging
# 最顶端的路径
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0])))

# 存放日志的目录
LOG_DIR = BASE_DIR + '/log/log.txt'

logging.basicConfig(level=logging.DEBUG,    # level=loggin.INFO意思是,把日志纪录级别设置为INFO,也就是说,只有比日志是INFO或比INFO级别更高的日志才会被纪录到文件里
                    format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
                    datefmt='%a, %d %b %Y %H:%M:%S',
                    filename=LOG_DIR,
                    filemode='a+')

# 存放用户信息的目录
USER_DIR = BASE_DIR + '/date/'
# 每次发送文件的大小
buffer = 2048
# 给用户分配的内存大小
memory = 100 * 1024 * 1024
# 家目录的路径
HOME_DIR = BASE_DIR + '\home\\'

server.config

java sftp 断点续传 python ftp 断点续传_json_03

java sftp 断点续传 python ftp 断点续传_客户端_04

import os, sys, json
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
from conf import config


def dirt(userName, passWord, memory, homeDir):
    return {
        "userName":userName,
        "passWord":passWord,
        "memory" : memory,
        "homeDir" : homeDir
    }
def auth_login(self):
    while True:
        self.request.send('请输入用户名:'.encode("utf-8"))
        userName = self.request.recv(config.buffer)
        try:
            with open(config.USER_DIR + userName.decode(), "r", encoding="utf-8") as fr:
                self.userMessDirt = json.load(fr)  # 记住json只能dump&load一次,所以放在循环里的时候注意
            self.request.send(b'200')
            break
        except FileNotFoundError as e:
            self.request.send(b'403')
    loginErrorCount = 3
    while loginErrorCount:
        self.request.send('请输入密码:'.encode("utf-8"))
        passWord_md5 = self.request.recv(config.buffer)
        # print("md5:",passWord_md5.decode())
        if self.userMessDirt["passWord"] == passWord_md5.decode():
            self.request.send(b"200")
            break
        else:
            self.request.send(b"403")
            loginErrorCount -= 1
    if loginErrorCount == 0:
        sys.exit(0)
def auth_register(self):
    self.request.send('请输入用户名:'.encode("utf-8"))
    userName = self.request.recv(config.buffer).decode()  # 一定要记得解码解码
    while True:
        self.request.send('请输入密码:'.encode("utf-8"))
        passWord_md5 = self.request.recv(config.buffer).decode()

        self.request.send('请再次输入密码:'.encode("utf-8"))
        passWord_again_md5 = self.request.recv(config.buffer).decode()

        if passWord_md5 == passWord_again_md5:
            self.request.send(b'200')
            os.makedirs(config.HOME_DIR + userName + "\\root\\")  # 为用户创建家目录
            self.userMessDirt = dirt(userName, passWord_md5, config.memory, config.HOME_DIR + userName + "\\root")
            with open(config.USER_DIR + userName, "w") as fw:
                json.dump(self.userMessDirt, fw)  # 将用户的信息存到文件中

            break
        else:
            self.request.send(b'403')

server.auth

java sftp 断点续传 python ftp 断点续传_json_03

java sftp 断点续传 python ftp 断点续传_客户端_04

# cd
import os
def cd(self, *args):
    path_list = self.current_path.split("\\")
    cmd_str = args[0]
    cmd_path = cmd_str["path"]
    if cmd_path == "/":
        path_list_len = len(path_list)
        while True:
            if path_list[path_list_len - 1] == self.userMessDirt["userName"]:
                break
            else:
                path_list.pop()
                path_list_len -= 1
    elif cmd_path == "..":
        path_list.pop()
    else:
        cmd_path_list = cmd_path.split("\\")
        path_list += cmd_path_list
    cmd_current_path = "\\".join(path_list)
    if os.path.exists(cmd_current_path):
        self.current_path = cmd_current_path
        os.chdir(self.current_path)
    print(self.current_path)
    self.request.send(self.current_path.encode("utf-8"))
# ls
import os

def ls(self, *args):
    cmd_str = args[0]
    mess = os.popen("dir" ).read()
    self.request.send(mess.encode("utf-8"))
# mkdir
import os

def mkdir(self, *args):
    cmd_str = args[0]
    fileName = cmd_str["fileName"]       # 需要创建的文件名字
    os.popen("mkdir %s" %fileName)
    self.request.send(b'200')

cd & ls &mkdir

java sftp 断点续传 python ftp 断点续传_json_03

java sftp 断点续传 python ftp 断点续传_客户端_04

import os, json
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
from conf import config

def put(self, *args):
    cmd_dirt = args[0]
    fileName = cmd_dirt["fileName"]
    fileSize = cmd_dirt["fileSize"]
    overridden = cmd_dirt["overridden"]
    if overridden:
        fw = open(fileName, "wb")
    else:
        fw = open(fileName + '.new', 'wb')
    self.request.send(b"200")  # 确认准备好接收文件了
    fileReceive = 0
    while fileSize > fileReceive:
        date_md5 = self.request.recv(config.buffer)
        self.request.send(date_md5)
        http_code = self.request.recv(config.buffer).decode()
        if http_code == "200":
            self.request.send(b"ok")
            if (fileSize - fileReceive) > config.buffer:
                date = self.request.recv(config.buffer)
            else:
                date = self.request.recv(fileSize - fileReceive)
            fw.write(date)
            fileReceive += len(date)
        elif http_code == "403":
            print("文件错误。。。")
            break
    else:
        fw.close()
        print(fileName, "接收完毕。。。")