Python3学习之路~8.6 开发一个支持多用户在线的FTP程序-代码实现

时间:2023-12-28 23:55:32

作业:

开发一个支持多用户在线的FTP程序

要求:

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

README:

1.client连接server端需要验证账号密码,密码使用MD5加密传输,三次验证不成功即退出。
2.用户信息保存在服务器本地文件中,密码MD5加密存储。磁盘配额大小也保存在其中。
3.用户连接上来后,可以执行命令如下
    目录变更:cd /cd dirname / cd . /cd ..
    文件浏览:ls
    文件删除:rm filename
    目录增删:mkdir dirname /rmdir dirname
    查看当前目录:pwd
    查看当前目录大小: du
    移动和重命名: mv filename/dirname filename/dirname
    上传文件:put filename [True] (True代表覆盖)
    下载文件:get filename [True]
    上传断点续传: newput filename [o/r] (o代表覆盖,r代表断点续传)
    下载断点续传: newget filename [o/r]
4.涉及到目录的操作,用户登录后,程序会给用户一个“锚位”----以用户名字命名的家目录,使用户无论怎么操作,都只能在这个目录底下。而在发给用户的目录信息时,隐去上层目录信息。
5.用户在创建时,磁盘配额大小默认是100M,在上传文件时,程序会计算当前目录大小加文件大小是否会超过配额上限。未超过,上传;超过,返回磁盘大小不够的信息。磁盘配额可通过用户管理程序修改。
6.文件上传和下载后都会进行MD5值比对,验证文件是否一致。
7.服务端和客户端都有显示进度条功能,启用该功能会降低文件传输速度,这是好看的代价。
8.文件断点续传,支持文件上传和下载断点续传。断点续传上传功能还会检测用户磁盘空间是否足够。(断点续传命令使用前面new+put/get命名,包含put/get所有功能,由于逻辑增多,代码复杂,特地保留原put/get,以备后用)。

程序结构:

Python3学习之路~8.6 开发一个支持多用户在线的FTP程序-代码实现

完整代码:

1.客户端 

#Author:Zheng Na

import os,sys
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ####获取当前文件的上一级的上一级目录
sys.path.append(BASE_DIR) from core.client import FtpClient if __name__ == '__main__':
ftp = FtpClient()
ftp.connect('localhost', 9999) auth_tag = False
count = 0
while auth_tag != True: ####功能:3次验证不通过即退出
count += 1
if count <= 3:
auth_tag = ftp.auth()
else:
exit() ftp.interactive()
ftp.close()

main.py

####用户端配置文件####
[DEFAULT]
logfile = ../log/client.log
download_dir= ../temp ####日志文件位置####
[log]
logfile = ../log/client.log ####下载文件存放位置####
[download]
download_dir= ../temp

client.conf

#Author:Zheng Na

import  os,configparser,logging

####读取配置文件####
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
config_file = os.path.join(base_dir, 'conf/client.conf')
cf = configparser.ConfigParser()
cf.read(config_file, encoding='utf-8') ####设定日志目录####
if os.path.exists(cf.get('log', 'logfile')):
logfile = cf.get('log', 'logfile')
else:
logfile = os.path.join(base_dir, 'log/client.log') ####设定下载/上传目录####
if os.path.exists(cf.get('download', 'download_dir')):
download_dir = cf.get('download', 'download_dir')
else:
download_dir = os.path.join(base_dir, 'temp') ####设置日志格式####
logging.basicConfig(level=logging.INFO,
format='%(asctime)s %(levelname)s %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
filename=logfile,
filemode='a+')

settings.py

# Author:Zheng Na

import socket,os,json,hashlib,sys,time,getpass,logging
import core.settings def hashmd5(*args): ####MD5加密
m = hashlib.md5()
m.update(str(*args).encode())
ciphertexts = m.hexdigest() ####密文
return ciphertexts def processbar(part, total): ####进度条,运行会导致程序变慢
if total != 0:
done = int(50 * part / total)
sys.stdout.write("\r[%s%s]" % ('█' * done, ' ' * (50 - done))) ####注意:一个方块对应2个空格
sys.stdout.write('{:.2%}'.format(part / total) + ' ' * 3 + str(part) + '/' + str(total))
sys.stdout.flush() class FtpClient(object):
def __init__(self):
self.client = socket.socket() def connect(self, ip, port): ####连接
self.client.connect((ip, port)) def auth(self): ####用户认证
username = input("请输入用户名>>>:").strip()
# password = getpass.getpass("请输入密码>>>:").strip() ####在linux上输入密码不显示,此模块在pycharm中无法使用
password = input("请输入密码>>>:").strip() ####Windows测试用
password = hashmd5(password)
msg = {
'username': username,
'password': password
}
self.client.send(json.dumps(msg).encode('utf-8'))
server_response = self.client.recv(1024).decode('utf-8')
logging.info(server_response)
if server_response == 'ok':
print("认证通过!")
return True
else:
print(server_response)
return False def interactive(self): ####交互
while True:
self.pwd('pwd') ####打印家目录
cmd = input(">> ").strip()
if len(cmd) == 0: continue
cmd_str = cmd.split()[0] ####用户输入的第一个值必定是命令
if hasattr(self, cmd_str): ####反射:判断一个对象中是否有字符串对应的方法或属性
func = getattr(self, cmd_str) ####利用反射来解耦:根据字符串去获取对象里对应的方法的内存地址或对应属性的值
func(cmd) ####调用命令对应的方法
else:
self.help() def help(self): ####帮助
msg = '''
仅支持如下命令:
ls
du
pwd
cd dirname/cd ./cd ..
mkdir dirname
rm filename
rmdir dirname
mv filename/dirname filename/dirname
get filename [True] (True代表覆盖)
put filename [True] (True代表覆盖)
newget filename [o/r] (后续增加的新功能,支持断点续传,o代表覆盖,r代表断点续传)
newput filename [o/r] (后续增加的新功能,支持断点续传,o代表覆盖,r代表断点续传)
'''
print(msg) def pwd(self, *args): ####查看当前目录
cmd_split = args[0].split()
if len(cmd_split) == 1:
msg = {'action': 'pwd'}
self.exec_linux_cmd(msg)
else:
self.help() def ls(self, *args): ####文件浏览
cmd_split = args[0].split()
if len(cmd_split) == 1:
msg = {'action': 'ls'}
self.exec_linux_cmd(msg)
else:
self.help() def du(self, *args): ####查看当前目录大小
cmd_split = args[0].split()
if len(cmd_split) == 1:
msg = {'action': 'du'}
self.exec_linux_cmd(msg)
else:
self.help() def cd(self, *args): ####切换目录
cmd_split = args[0].split()
if len(cmd_split) == 1:
dirname = ''
elif len(cmd_split) == 2:
dirname = cmd_split[1]
else:
return help() msg = {
"action": 'cd',
"dirname": dirname
}
self.exec_linux_cmd(msg) def mkdir(self, *args): ####生成目录
cmd_split = args[0].split()
if len(cmd_split) == 2:
dirname = cmd_split[1]
msg = {
"action": 'mkdir',
"dirname": dirname
}
self.exec_linux_cmd(msg)
else:
help() def rm(self, *args): ####删除文件
cmd_split = args[0].split()
if len(cmd_split) == 2:
filename = cmd_split[1]
msg = {
"action": 'rm',
"filename": filename,
"confirm": True ####确认是否直接删除标志
}
self.exec_linux_cmd(msg)
else:
help() def rmdir(self, *args): ####删除目录
cmd_split = args[0].split()
if len(cmd_split) == 2:
dirname = cmd_split[1]
msg = {
"action": 'rmdir',
"dirname": dirname,
"confirm": True ####确认是否直接删除标志
}
self.exec_linux_cmd(msg)
else:
help() def mv(self, *args): ####实现功能:移动文件,移动目录,文件重命名,目录重命名
cmd_split = args[0].split()
if len(cmd_split) == 3:
objname = cmd_split[1]
dstname = cmd_split[2]
msg = {
"action": 'mv',
"objname": objname,
"dstname": dstname
}
self.exec_linux_cmd(msg)
else:
help() def exec_linux_cmd(self, dict): ####用于后面调用linux命令
logging.info(dict) ####将发送给服务端的命令保存到日志中
self.client.send(json.dumps(dict).encode('utf-8'))
server_response = json.loads(self.client.recv(4096).decode('utf-8'))
if isinstance(server_response, list): ####判断是否为list类型
for i in server_response:
print(i)
else:
print(server_response) def get(self, *args): ####下载文件
cmd_split = args[0].split()
override = cmd_split[-1] ####override:是否覆盖参数,True表示覆盖,放在最后一位
# print(override,type(override))
if override != 'True':
override = 'False'
# print(override)
if len(cmd_split) > 1:
filename = cmd_split[1]
filepath = os.path.join(core.settings.download_dir, filename)
if override != 'True' and os.path.isfile(filepath): ####判断下载目录是否已存在同名文件
override_tag = input('文件已存在,要覆盖文件请输入yes >>>:').strip()
if override_tag == 'yes':
self.put('put %s True' % filename)
else:
print('下载取消')
else:
msg = {
'action': 'get',
'filename': filename,
'filesize': 0,
'filemd5': '',
'override': 'True'
}
# logging.info(msg)
self.client.send(json.dumps(msg).encode('utf-8'))
server_response = json.loads(self.client.recv(1024).decode('utf-8'))
logging.info(server_response)
if server_response == 'Filenotfound':
print('File no found!')
else:
print(server_response)
self.client.send(b'client have been ready to receive') ####发送信号,防止粘包
filesize = server_response['filesize']
filemd5 = server_response['filemd5']
receive_size = 0
f = open(filepath, 'wb')
while filesize > receive_size:
if filesize - receive_size > 1024:
size = 1024
else:
size = filesize - receive_size
data = self.client.recv(size)
f.write(data)
receive_size += len(data)
processbar(receive_size, filesize) ####打印进度条
f.close()
# receive_filemd5 = os.popen('md5sum %s' % filepath).read().split()[0]
receive_filemd5 = 'a' ####Windows测试用
print('\r\n', filename, 'md5:', receive_filemd5, '原文件md5:', filemd5)
if receive_filemd5 == filemd5:
print('文件接收完成!')
else:
print('Error,文件接收异常!')
else:
help() def put(self, *args): ####上传文件
cmd_split = args[0].split()
override = cmd_split[-1] ####override:是否覆盖参数,True表示覆盖,放在最后一位
if override != 'True':
override = 'False'
# print(cmd_split,override) if len(cmd_split) > 1:
filename = cmd_split[1]
filepath = os.path.join(core.settings.download_dir, filename)
if os.path.isfile(filepath):
filesize = os.path.getsize(filepath) ####法1
# filesize = os.stat(filepath).st_size ####法2 ####直接调用系统命令取得MD5值,如果使用hashlib,需要写open打开文件-》read读取文件(可能文件大会很耗时)-》m.update计算三步,代码量更多,效率也低
# filemd5 = os.popen('md5sum %s' % filepath).read().split()[0]
filemd5 = 'a' ####Windows测试 msg = {
"action": 'put',
"filename": filename,
"filesize": filesize,
"filemd5": filemd5,
"override": override
}
# logging.info(msg)
self.client.send(json.dumps(msg).encode('utf-8'))
###防止粘包,等服务器确认:这里最好列出一些标准请求码,告诉客户端是否有权限传输文件,类似200 403等
server_response = self.client.recv(1024)
# logging.info(server_response)
if server_response == b'file have exits, do nothing!':
override_tag = input('文件已存在,要覆盖文件请输入yes >>>:')
if override_tag == 'yes':
self.put('put %s True' % filename)
else:
print('文件未上传')
else:
self.client.send(b'client have ready to send') ####发送确认信号,防止粘包,代号:P01
server_response = self.client.recv(1024).decode('utf-8')
print(server_response) ####注意:用于打印服务器反馈信息,例如磁盘空间不足信息,不能取消
if server_response == 'begin':
f = open(filepath, 'rb')
send_size = 0
for line in f:
send_size += len(line)
self.client.send(line)
processbar(send_size, filesize)
else:
print('\r\n', "file upload success...")
f.close()
server_response = self.client.recv(1024).decode('utf-8')
print(server_response)
else:
print(filename, 'is not exist')
else:
self.help() def newget(self, *args): ####下载文件,具有断点续传功能
cmd_split = args[0].split()
tag = cmd_split[-1] ####tag:o代表覆盖,r代表续传,放在最后一位 if len(cmd_split) > 1:
filename = cmd_split[1]
filepath = os.path.join(core.settings.download_dir, filename)
if tag not in ('o', 'r'):
if os.path.isfile(filepath): ####判断下载目录是否已存在同名文件
tag = input('文件已存在,要覆盖文件请输入o,要断点续传请输入r >>>:').strip()
else:
tag = 'o' if tag in ('o', 'r'):
if tag == 'r':
local_filesize = os.path.getsize(filepath)
else:
local_filesize = 0 # 本地文件大小 msg = {
'action': 'newget',
'filename': filename,
'filesize': local_filesize,
'filemd5': '',
}
logging.info(msg)
self.client.send(json.dumps(msg).encode('utf-8'))
server_response = json.loads(self.client.recv(1024).decode('utf-8'))
logging.info(server_response)
if server_response == 'Filenotfound':
print('File no found!')
else:
print(server_response)
self.client.send(b'client have been ready to receive') # 发送信号,防止粘包
filesize = server_response['filesize']
filemd5 = server_response['filemd5']
receive_size = local_filesize
if tag == 'r':
f = open(filepath, 'ab+') ####用于断点续传
else:
f = open(filepath, 'wb+') ####用于覆盖或者新生成文件
while filesize > receive_size:
if filesize - receive_size > 1024:
size = 1024
else:
size = filesize - receive_size
data = self.client.recv(size)
f.write(data)
receive_size += len(data)
# print(receive_size, len(data)) ####打印数据流情况
processbar(receive_size, filesize) ####打印进度条
f.close()
# receive_filemd5 = os.popen('md5sum %s' % filepath).read().split()[0]
receive_filemd5 = 'a' ####Windows测试用
print('\r\n', filename, 'md5:', receive_filemd5, '原文件md5:', filemd5)
if receive_filemd5 == filemd5:
print('文件接收完成!')
else:
print('Error,文件接收异常!')
else:
print("文件未下载")
else:
help() def newput(self, *args): ####上传文件,具有断点续传功能
cmd_split = args[0].split()
tag = cmd_split[-1] ####tag:r代表续传,o代表覆盖,放在最后一位
if tag not in ('o', 'r'):
tag = 'unknown'
# print(cmd_split,tag) if len(cmd_split) > 1:
filename = cmd_split[1]
filepath = os.path.join(core.settings.download_dir, filename)
if os.path.isfile(filepath):
filesize = os.path.getsize(filepath) ####法1
# filesize = os.stat(filepath).st_size ####法2 ####直接调用系统命令取得MD5值,如果使用hashlib,需要写open打开文件-》read读取文件(可能文件大会很耗时)-》m.update计算三部,代码量更多,效率也低
# filemd5 = os.popen('md5sum %s' % filepath).read().split()[0]
filemd5 = 'a' # Windows测试 msg = {
"action": 'newput',
"filename": filename,
"filesize": filesize,
"filemd5": filemd5,
"tag": tag
}
# logging.info(msg)
self.client.send(json.dumps(msg).encode('utf-8')) ####发送msg
server_response1 = self.client.recv(1024).decode() ####接收文件存在或者文件不存在
# logging.info(server_response)
print(server_response1) if server_response1 == '文件存在': ####再确认一遍tag
if tag == 'unknown':
tag = input('文件已存在,要覆盖文件请输入o,要断点续传请输入r >>>:').strip()
if tag not in ('o', 'r'):
tag = 'unknown'
else: ####文件不存在时
tag = 'o' self.client.send(tag.encode())
server_response2 = json.loads(self.client.recv(1024).decode('utf-8'))
# print('server_response2:', server_response2)
content = server_response2['content'] if tag == 'o' or tag == 'r':
if content == 'begin':
position = server_response2['position']
print(position)
f = open(filepath, 'rb')
f.seek(position, 0)
send_size = position
for line in f:
send_size += len(line)
self.client.send(line)
processbar(send_size, filesize)
else:
print('\n', "file upload success...")
f.close()
server_response3 = self.client.recv(1024).decode('utf-8') ####服务端对比md5后发送是否成功接收文件,成功或失败
print(server_response3)
else:
print(content) ####content:服务器已存在同名文件 或。。。
else:
print(content) ####content:文件未上传
else:
print(filename, 'is not exist')
else:
self.help() def newput2(self, *args): ####上传文件,具有断点续传功能,网友写的,与我写的newput功能差不多
cmd_split = args[0].split()
override = cmd_split[-1] ####override:是否覆盖参数,放在最后一位
if override != 'True':
override = 'False'
# print(cmd_split,override) if len(cmd_split) > 1:
filename = cmd_split[1]
filepath = os.path.join(core.settings.download_dir, filename)
if os.path.isfile(filepath):
filesize = os.path.getsize(filepath) ####法1
# filesize = os.stat(filepath).st_size ####法2
####直接调用系统命令取得MD5值,如果使用hashlib,需要写open打开文件-》read读取文件(可能文件大会很耗时)-》m.update计算三部,代码量更多,效率也低
filemd5 = os.popen('md5sum %s' % filepath).read().split()[0]
filemd5 = 'a' ####Windows测试
msg = {
"action": 'newput2',
"filename": filename,
"filesize": filesize,
"filemd5": filemd5,
"override": override
}
# logging.info(msg)
self.client.send(json.dumps(msg).encode('utf-8'))
####防止粘包,等服务器确认:这里最好列出一些标准请求码,告诉客户端是否有权限传输文件,类似200 403等
server_response = self.client.recv(1024)
# logging.info(server_response)
print(server_response)
if server_response == b'file have exits, and is a directory, do nothing!':
print('文件已存在且为目录,请先修改文件或目录名字,然后再上传')
elif server_response == b'file have exits, do nothing!':
override_tag = input('文件已存在,要覆盖文件请输入yes,要断点续传请输入r >>>:').strip()
if override_tag == 'yes':
self.client.send(b'no need to do anything') ####服务端在等待是否续传的信号,发送给服务端确认(功能号:s1)
time.sleep(0.5) ####防止黏贴,功能需改进
self.put('put %s True' % filename)
elif override_tag == 'r':
self.client.send(b'ready to resume from break point') ####服务端在等待是否续传的信号,发送给服务端确认(功能号:s1)
self.client.recv(1024) ####这边接收服务端发送过来的du信息,不显示,直接丢弃
server_response = json.loads(self.client.recv(1024).decode('utf-8'))
print(server_response)
if server_response['state'] == 'True':
exist_file_size = server_response['position']
f = open(filepath, 'rb')
f.seek(exist_file_size, 0)
send_size = exist_file_size
for line in f:
send_size += len(line)
self.client.send(line)
processbar(send_size, filesize)
else:
print('\r\n', '文件传输完毕')
f.close()
server_response = self.client.recv(1024).decode('utf-8')
print(server_response)
else:
print(server_response['content'])
else:
self.client.send(b'no need to do anything') ####服务端在等待是否续传的信号,发送给服务端确认(功能号:s1)
print('文件未上传')
else:
self.client.send(b'client have ready to send') ####发送确认信号,防止粘包,代号:P01
server_response = self.client.recv(1024).decode('utf-8')
print(server_response) ####注意:用于打印服务器反馈信息,例如磁盘空间不足信息,不能取消
if server_response == 'begin':
f = open(filepath, 'rb')
send_size = 0
for line in f:
send_size += len(line)
self.client.send(line)
processbar(send_size, filesize)
else:
print('\r\n', "file upload success...")
f.close()
server_response = self.client.recv(1024).decode('utf-8')
print(server_response)
else:
print(filename, 'is not exist')
else:
self.help() def close(self):
self.client.close()

client.py

2.服务端

# Author:Zheng Na

####os.path.abspath(__file__) 获取当前当前文件的绝对路径
####os.path.dirname()获取当前文件上一层目录
import os,sys
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ####获取当前文件的上一级的上一级目录
sys.path.append(BASE_DIR) import socketserver
from core.server import MyTCPHandler
from core.usermanagement import UserOpr if __name__ == '__main__': mainpage = '''
主页
1、启动服务器
2、进入用户管理
退出请按q
''' while True:
print('\033[1;35m{}\033[0m'.format(mainpage))
choice = input('>>>:')
if choice == 'q':
exit()
elif choice == '':
HOST, PORT = "localhost", 9999
server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)
server.serve_forever()
elif choice == '':
useropr = UserOpr()
# useropr.query_all_user() ####查询所有用户信息
useropr.interactive()
else:
print("\033[1;31m输入错误,请重新输入\033[0m")
continue

main.py

####用户端配置文件####
[DEFAULT]
logfile = ../log/server.log
usermgr_log = ../log/usermgr.log
upload_dir = ../user_files
userinfo_dir = ../user_info ####日志文件位置####
[log]
logfile = ../log/server.log
usermgr_log = ../log/usermgr.log ####上传文件存放位置####
[upload]
upload_dir = ../user_files ####用户信息存放位置####
[userinfo]
userinfo_dir = ../user_info

server.conf

#Author:Zheng Na

import os,configparser,logging

####读取配置文件####
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ####获取当前文件的上一级的上一级目录
config_file = os.path.join(base_dir, 'conf/server.conf') #####将2个路径组合后返回
cf = configparser.ConfigParser()
cf.read(config_file,encoding='utf-8') # 不编码会报错:UnicodeDecodeError: 'gbk' codec can't decode byte 0xaf in position 12: illegal multibyte sequence ####设定日志目录####
'''先判断日志文件是否存在,如果不存在,则创建'''
if os.path.exists(cf.get('log', 'usermgr_log')):
usermgr_log = cf.get('log', 'usermgr_log')
else:
usermgr_log = os.path.join(base_dir, 'log/usermgr.log') ####设定用户上传文件目录,这边用于创建用户家目录使用####
if os.path.exists(cf.get('upload', 'upload_dir')):
file_dir = cf.get('upload', 'upload_dir')
else:
file_dir = os.path.join(base_dir, 'user_files') ####设定用户信息存储位置####
if os.path.exists(cf.get('userinfo', 'userinfo_dir')):
userinfo_dir = cf.get('userinfo', 'userinfo_dir')
else:
userinfo_dir = os.path.join(base_dir, 'user_info') ####设置日志格式####
logging.basicConfig(level=logging.INFO,
format='%(asctime)s %(levelname)s %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
filename=usermgr_log,
filemode='a+')

settings.py

#Author:Zheng Na

import os,json
import core.settings def query_user(username): ####查询用户
filelist = os.listdir(core.settings.userinfo_dir) ####列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印
dict = {}
for filename in filelist:
with open (os.path.join( core.settings.userinfo_dir,filename),'r',encoding='utf-8') as f:
content = json.load(f) ####json反序列化
if content['username'] == username:
dict = {'filename':filename,'content':content}
# print("查询结果:",dict)
return dict

common.py

# Author:Zheng Na

import socketserver,sys,json,os,time,shutil
import core.common def processbar(part, total): ####进度条,运行会导致程序变慢
if total != 0:
done = int(50 * part / total)
sys.stdout.write("\r[%s%s]" % ('█' * done, ' ' * (50 - done))) ####注意:一个方块对应2个空格
sys.stdout.write('{:.2%}'.format(part / total)+' '*3+str(part)+'/'+str(total))
sys.stdout.flush() def timestamp_to_formatstringtime(timestamp): ####时间戳转化为格式化的字符串
structtime = time.localtime(timestamp)
formatstringtime = time.strftime("%Y%m%d %H:%M:%S",structtime)
return formatstringtime class MyTCPHandler(socketserver.BaseRequestHandler): def handle(self):
auth_tag = False
while auth_tag != True:
auth_result = self.auth() ####用户认证,如果通过,返回用户名,不通过为None
print("the authentication result is:",auth_result)
if auth_result != None:
self.username = auth_result['content']['username']
self.spacesize = auth_result['content']['spacesize']
auth_tag = True
print(self.username,self.spacesize)
user_homedir = os.path.join(core.settings.file_dir,self.username)
if os.path.isdir(user_homedir):
self.position = user_homedir ####定锚,用户家目录
print(self.position) while True:
print("当前连接:",self.client_address)
self.data = self.request.recv(1024).strip()
print(self.data.decode())
# logging.info(self.client_address)
if not self.data:
print(self.client_address, "断开了")
break
cmd_dic = json.loads(self.data.decode('utf-8'))
# print(cmd_dic)
action = cmd_dic["action"]
# logging.info(cmd_dic)
if hasattr(self, action):
func = getattr(self, action)
func(cmd_dic)
else:
print("未支持指令:",action)
# logging.info('current direcory: %s' % self.positoion) def auth(self): ####用户认证
self.data = json.loads(self.request.recv(1024).decode('utf-8'))
print(self.data)
recv_username = self.data['username']
recv_password = self.data['password']
query_result = core.common.query_user(recv_username)
print(query_result)
if query_result == None:
self.request.send(b'user does not exist')
elif query_result['content']['password'] == recv_password:
self.request.send(b'ok')
return query_result ####返回查询结果
elif query_result['content']['password'] != recv_password:
self.request.send(b'password error')
else:
self.request.send(b'unkonwn error') def pwd(self,*args): ####查看当前目录
current_position = self.position
result = current_position.replace(core.settings.file_dir,'') ####截断目录信息,使用户只能看到自己的家目录信息
print(result)
self.request.send(json.dumps(result).encode('utf-8')) def ls(self,*args): ####列出当前目录下的所有文件信息,类型,字节数,生成时间
result = ['%-20s%-7s%-10s%-23s' % ('filename', 'type', 'bytes', 'creationtime')] ####信息标题 #没看懂
for f in os.listdir(self.position):
f_abspath = os.path.join(self.position,f) ####给出文件的绝对路径,不然程序会找不到文件
if os.path.isdir(f_abspath):
type = 'd'
elif os.path.isfile(f_abspath):
type = 'f'
else:
type = 'unknown'
fsize = os.path.getsize(f_abspath)
ftime = timestamp_to_formatstringtime(os.path.getctime(f_abspath))
result.append('%-20s%-7s%-10s%-23s' % (f,type,fsize,ftime))
self.request.send(json.dumps(result).encode('utf-8')) def du_calc(self): # 注意不能使用os.path.getsize('D:\python-study\s14')返回的是所有目录大小的和
'''统计纯文件和目录占用空间大小,结果小于在OS上使用du -s查询,因为有一些(例如'.','..')隐藏文件未包含在内'''
totalsize = 0
if os.path.isdir(self.position):
dirsize,filesize = 0,0
for root,dirs,files in os.walk(self.position):
for d in dirs: #计算目录占用空间,Linux中每个目录占用4096bytes,实际上也可以按这个值来相加
dirsize += os.path.getsize(os.path.join(root,d))
for f in files: #计算文件占用空间
filesize += os.path.getsize(os.path.join(root,f))
totalsize = dirsize + filesize
return totalsize def du(self,*args): ####查看当前目录大小
totalsize = self.du_calc()
result = 'current directory total sizes: %d' % totalsize
print(result)
self.request.send(json.dumps(result).encode('utf-8'))
return totalsize def cd(self,*args): ####切换目录,这个函数实在是没怎么看懂
print(*args)
user_homedir = os.path.join(core.settings.file_dir,self.username)
cmd_dic = args[0]
error_tag = False
'''判断目录信息'''
if cmd_dic['dirname'] == '':
self.position = user_homedir
elif cmd_dic['dirname'] in ('.','/') or '//' in cmd_dic['dirname']: ####'.','/','//','///+'匹配
pass
elif cmd_dic['dirname'] == '..':
if user_homedir != self.position and user_homedir in self.position: ####当前目录不是家目录,并且当前目录是家目录下的子目录
self.position = os.path.dirname(self.position)
elif '.' not in cmd_dic['dirname'] and os.path.isdir(os.path.join(self.position,cmd_dic['dirname'])):####'.' not in cmd_dict['dir'] 防止../..输入
self.position = os.path.join(self.position,cmd_dic['dirname'])
else:
error_tag = True if error_tag:
result = 'Error,%s is not path here,or path does not exist!' % cmd_dic['dirname']
self.request.send(json.dumps(result).encode('utf-8'))
else:
self.pwd() def mkdir(self,*args): ####创建目录
try:
dirname = args[0]['dirname']
if dirname.isalnum(): ####判断文件是否只有数字和字母
if os.path.exists(os.path.join(self.position,dirname)):
result = 's% have existed' % dirname
else:
os.mkdir(os.path.join(self.position,dirname))
result = '%s created success' % dirname
else:
result = 'Illegal character %s, dirname can only by string and num here.' % dirname
except TypeError:
result = 'please input dirname' self.request.send(json.dumps(result).encode('utf-8')) def rm(self,*args): ####删除文件
filename = args[0]['filename']
confirm = args[0]['confirm']
file_abspath = os.path.join(self.position,filename)
if os.path.isfile(file_abspath):
if confirm == True:
os.remove(file_abspath)
result = "%s have been deleted." % filename
else:
result = 'Not file deleted'
elif os.path.isdir(file_abspath):
result = '%s is a dir,please use rmdir' % filename
else:
result = 'File %s not exist!' % filename
self.request.send(json.dumps(result).encode('utf-8')) def rmdir(self,*args):
dirname = args[0]['dirname']
confirm = args[0]['confirm']
dir_abspath = os.path.join(self.position,dirname)
if '.' in dirname or '/' in dirname: ####不能跨目录删除
result = 'should not rmdir %s this way' % dirname
elif os.path.isdir(dir_abspath):
if confirm == True:
shutil.rmtree(dir_abspath)
result = '%s have been deleted.' % dirname
else:
result = 'Not dir deleted.'
elif os.path.isfile(dir_abspath):
result = '%s is a file,please use rm' % dirname
else:
result = 'directory %s not exist!' % dirname
self.request.send(json.dumps(result).encode('utf-8')) def mv(self,*args): ####实现功能:移动文件,移动目录,文件重命名,目录重命名
try:
print(args)
objname = args[0]['objname']
dstname = args[0]['dstname']
obj_abspath = os.path.join(self.position,objname)
dst_abspath = os.path.join(self.position,dstname)
if os.path.isfile(obj_abspath):
if os.path.isdir(dst_abspath) or not os.path.exists(dst_abspath):
shutil.move(obj_abspath,dst_abspath)
result = 'move success'
elif os.path.isfile(dst_abspath):
result = 'moving cancel,file has been exist.'
elif os.path.isdir(obj_abspath):
if os.path.isdir(dst_abspath) or not os.path.exists(dst_abspath):
shutil.move(obj_abspath,dst_abspath)
result = 'move success'
elif os.path.isfile(dst_abspath):
result = 'moving cancel,%s is a file.'% dst_abspath
else:
result = 'nothing done'
self.request.send(json.dumps(result).encode('utf-8'))
except Exception as e:
print(e)
result = 'moving fail,' + e
self.request.send(json.dumps(result).encode('utf-8')) def get(self,*args): ####发送给客户端文件
cmd_dic = args[0]
filename = cmd_dic['filename']
filepath = os.path.join(self.position, filename)
if os.path.isfile(filepath):
filesize = os.path.getsize(filepath)
####直接调用系统命令取得MD5值,如果使用hashlib,需要写open打开文件-》read读取文件(可能文件大会很耗时)-》m.update计算三部,代码量更多,效率也低
# filemd5 = os.popen('md5sum %s' % filepath).read().split()[0]
filemd5 = 'a' ####Windows测试用
msg = {
'action': 'get',
'filename': filename,
'filesize': filesize,
'filemd5': filemd5,
'override': 'True'
}
# print(msg)
self.request.send(json.dumps(msg).encode('utf-8'))
'''接下来发送文件给客户端'''
self.request.recv(1024) ####接收ACK信号,下一步发送文件
f = open(filepath, 'rb')
send_size = 0
for line in f:
send_size += len(line)
self.request.send(line)
# processbar(send_size, filesize) ####服务端进度条,不需要可以注释掉
else:
print('文件传输完毕')
f.close()
else:
print(filepath, '文件未找到')
self.request.send(json.dumps('Filenotfound').encode('utf-8')) def put(self, *args): ####接收客户端文件
cmd_dic = args[0]
filename = os.path.basename(cmd_dic['filename']) ####传输进来的文件名可能带有路径,将路径去掉
filesize = cmd_dic['filesize']
filemd5 = cmd_dic['filemd5']
override = cmd_dic['override']
receive_size = 0
file_path = os.path.join(self.position, filename)
if override != 'True' and os.path.exists(file_path): ####检测文件是否已经存在
self.request.send(b'file have exits, do nothing!')
else:
if os.path.isfile(file_path): ####如果文件已经存在,先删除,再计算磁盘空间大小
os.remove(file_path)
current_size = self.du() ####调用du查看用户磁盘空间大小,但是du命令的最后会发送一个结果信息给client,会和前面和后面的信息粘包,需要注意
self.request.recv(1024) ####接收客户端ack信号,防止粘包,代号:P01
print(self.spacesize, current_size, filesize)
if self.spacesize >= current_size + filesize:
self.request.send(b'begin') ####发送开始传输信号
f = open(file_path, 'wb') while filesize > receive_size:
if filesize - receive_size > 1024:
size = 1024
else:
size = filesize - receive_size
data = self.request.recv(size)
f.write(data)
receive_size += len(data)
# print(receive_size,len(data)) ####打印每次接收的数据
# processbar(receive_size, filesize) ####服务端进度条,不需要可以注释掉
else:
print("file [%s] has uploaded..." % filename)
f.close()
# receive_filemd5 = os.popen('md5sum %s' % file_path).read().split()[0]
receive_filemd5 = 'a' ####windows 测试用
print('\r\n', file_path, 'md5:', receive_filemd5, '原文件md5:', filemd5)
if receive_filemd5 == filemd5:
self.request.send(b'file received successfully!')
else:
self.request.send(b'Error, file received have problems!')
else:
self.request.send(
b'Error, disk space do not enough! Nothing done! Total: %d, current: %d, rest:%d, filesize:%d' % (
self.spacesize,current_size, self.spacesize - current_size, filesize)) def newget(self, *args): ####发送给客户端文件,具有断点续传功能
# print('get receive the cmd',args[0])
cmd_dic = args[0]
filename = cmd_dic['filename']
send_size = cmd_dic['filesize']
print(filename)
# self.request.send(b'server have been ready to send') ####发送ACK
file_path = os.path.join(self.position, filename)
if os.path.isfile(file_path):
filesize = os.path.getsize(file_path)
####直接调用系统命令取得MD5值,如果使用hashlib,需要写open打开文件-》read读取文件(可能文件大会很耗时)-》m.update计算三部,代码量更多,效率也低
# filemd5 = os.popen('md5sum %s' % file_path).read().split()[0]
filemd5 = 'a' #Windows测试用
msg = {
'action': 'newget',
'filename': filename,
'filesize': filesize,
'filemd5': filemd5,
}
print(msg)
self.request.send(json.dumps(msg).encode('utf-8'))
self.request.recv(1024) ####接收ACK信号,下一步发送文件
f = open(file_path, 'rb')
f.seek(send_size,0)
for line in f:
send_size += len(line)
self.request.send(line)
# processbar(send_size, filesize) ####服务端进度条,不需要可以注释掉
else:
print('文件传输完毕')
f.close() else:
print(file_path, '文件未找到')
self.request.send(json.dumps('Filenotfound').encode('utf-8')) def newput(self, *args): ####接收客户端文件,具有断点续传功能
cmd_dict = args[0]
filename = os.path.basename(cmd_dict['filename']) ####传输进来的文件名可能带有路径,将路径去掉
filesize = cmd_dict['filesize']
filemd5 = cmd_dict['filemd5']
tag = cmd_dict['tag']
receive_size = 0
file_path = os.path.join(self.position, filename)
if os.path.isfile(file_path): ####检测文件是否已经存在
self.request.send('文件存在'.encode())
tag = self.request.recv(1024).decode() ####接收客户端ack信号
if tag == 'o':
os.remove(file_path)####如果文件已经存在,先删除,再计算磁盘空间大小
self.upload(tag,filename, filemd5, filesize, file_path, receive_size)
elif tag == 'r':
exist_file_size = os.path.getsize(file_path)
if exist_file_size <= filesize:
receive_size = exist_file_size
self.upload(tag,filename, filemd5, filesize, file_path, receive_size)
else:
print('服务器已存在同名文件且比原文件大')
msg = {
"content": '服务器已存在同名文件且比原文件大'
}
self.request.send(json.dumps(msg).encode('utf-8'))
else:
msg = {
"content": '文件未上传'
}
self.request.send(json.dumps(msg).encode('utf-8'))
else: ####文件不存在:如果文件不存在的话,就不用管tag了,直接计算磁盘空间,然后上传
self.request.send('文件不存在!'.encode())
tag = self.request.recv(1024).decode() ####接收客户端ack信号
self.upload(tag,filename,filemd5,filesize,file_path,receive_size) def upload(self,tag,filename,filemd5,filesize,file_path,receive_size):
current_size = self.du_calc()
print('用户总空间:',self.spacesize, '目前剩余空间:',current_size,'文件大小:', filesize)
if tag == 'r':
needrecv_size = filesize - receive_size
else:
needrecv_size = filesize
if self.spacesize >= current_size + needrecv_size:
msg = {
"position":receive_size,
"content":'begin'
}
self.request.send(json.dumps(msg).encode('utf-8')) ####发送开始传输信号
if tag == 'r':
f = open(file_path, 'ab')
else:
f = open(file_path, 'wb')
while filesize > receive_size:
if filesize - receive_size > 1024:
size = 1024
else:
size = filesize - receive_size
data = self.request.recv(size)
f.write(data)
receive_size += len(data)
# processbar(receive_size, filesize) ####服务端进度条,不需要可以注释掉
f.close()
# receive_filemd5 = os.popen('md5sum %s' % file_path).read().split()[0]
receive_filemd5 = 'a'
print('\r\n', filename, 'md5:', receive_filemd5, '原文件md5:', filemd5)
if receive_filemd5 == filemd5:
self.request.send(b'file received successfully!')
else:
self.request.send(b'Error, file received have problems!')
else:
msg = {
"content":'Error, disk space do not enough! Nothing done! Total: %d, current: %d, rest:%d, filesize:%d' % (
self.spacesize, current_size, self.spacesize - current_size, filesize)
}
self.request.send(json.dumps(msg).encode('utf-8')) ## def newput2(self, *args): ####接收客户端文件,具有断点续传功能
cmd_dict = args[0]
filename = os.path.basename(cmd_dict['filename']) ####传输进来的文件名可能带有路径,将路径去掉
filesize = cmd_dict['filesize']
filemd5 = cmd_dict['filemd5']
override = cmd_dict['override']
receive_size = 0
file_path = os.path.join(self.position, filename)
# print(file_path,os.path.isdir(file_path))
if override != 'True' and os.path.exists(file_path): ####检测文件是否已经存在
if os.path.isdir(file_path):
self.request.send(b'file have exits, and is a directory, do nothing!')
elif os.path.isfile(file_path):
self.request.send(b'file have exits, do nothing!')
resume_signal = self.request.recv(1024) ####接收客户端发来的是否从文件断点续传的信号
if resume_signal == b'ready to resume from break point': ####执行断点续传功能
exist_file_size = os.path.getsize(file_path)
current_size = self.du()
time.sleep(0.5) ####防止粘包
print('用户空间上限:%d, 当前已用空间:%d, 已存在文件大小:%d, 上传文件大小:%d ' % (self.spacesize,current_size,exist_file_size,filesize))
if self.spacesize >= (current_size - exist_file_size + filesize): ####判断剩余空间是否足够
if exist_file_size < filesize:
receive_size = exist_file_size
print('服务器上已存在的文件大小为:',exist_file_size)
msg = {
'state': True,
'position': exist_file_size,
'content': 'ready to receive file'
}
self.request.send(json.dumps(msg).encode('utf-8'))
f = open(file_path, 'ab+')
while filesize > receive_size:
if filesize - receive_size > 1024:
size = 1024
else:
size = filesize - receive_size
data = self.request.recv(size)
f.write(data)
receive_size += len(data)
# print(receive_size,len(data)) ####打印每次接收的数据
# processbar(receive_size, filesize) ####服务端进度条,不需要可以注释掉 f.close()
receive_filemd5 = os.popen('md5sum %s' % file_path).read().split()[0]
print('\r\n', file_path, 'md5:', receive_filemd5, '原文件md5:', filemd5)
if receive_filemd5 == filemd5:
self.request.send(b'file received successfully!')
else:
self.request.send(b'Error, file received have problems!') else: ####如果上传的文件小于当前服务器上的文件,则为同名但不同文件,不上传。实际还需要增加其他判断条件,判断是否为同一文件。
msg = {
'state': False,
'position': '',
'content': 'Error, file mismatch, do nothing!'
}
self.request.send(json.dumps(msg).encode('utf-8'))
else: ####如果续传后的用户空间大于上限,拒接续传
msg = {
'state': False,
'position':'',
'content':'Error, disk space do not enough! Nothing done! Total: %d, current: %d, rest:%d, need_size:%d' % (self.user_spacesize, current_size, self.user_spacesize - current_size, filesize - exits_file_size)
}
self.request.send(json.dumps(msg).encode('utf-8'))
else:
pass else:
if os.path.isfile(file_path): ####如果文件已经存在,先删除,再计算磁盘空间大小
os.remove(file_path)
current_size = self.du() ####调用du查看用户磁盘空间大小,但是du命令的最后会发送一个结果信息给client,会和前面和后面的信息粘包,需要注意
self.request.recv(1024) ####接收客户端ack信号,防止粘包,代号:P01
print(self.spacesize, current_size, filesize)
if self.spacesize >= current_size + filesize:
self.request.send(b'begin') ####发送开始传输信号
fk = open(file_path, 'wb')
while filesize > receive_size:
if filesize - receive_size > 1024:
size = 1024
else:
size = filesize - receive_size
data = self.request.recv(size)
fk.write(data)
receive_size += len(data)
# print(receive_size,len(data)) ####打印每次接收的数据
# processbar(receive_size, filesize) ####服务端进度条,不需要可以注释掉 fk.close()
receive_filemd5 = os.popen('md5sum %s' % file_path).read().split()[0]
print('\r\n', file_path, 'md5:', receive_filemd5, '原文件md5:', filemd5)
if receive_filemd5 == filemd5:
self.request.send(b'file received successfully!')
else:
self.request.send(b'Error, file received have problems!')
else:
self.request.send(
b'Error, disk space do not enough! Nothing done! Total: %d, current: %d, rest:%d, filesize:%d' % (
self.spacesize, current_size, self.spacesize - current_size, filesize))

server.py

#Author:Zheng Na
import os,time,json,shutil,hashlib
import core.common def hashmd5(self, *args):
m = hashlib.md5()
m.update(str(*args).encode())
ciphertexts = m.hexdigest() # 密文
return ciphertexts # 用户操作类
class UserOpr(object):
def __init__(self):
pass def query_userinfo(self,username):
query_result = core.common.query_user(username)
if query_result != None: # 用户存在
print(query_result)
else:
print("用户不存在") def save_userinfo(self,username): # 保存用户信息
query_result = core.common.query_user(username) # 检查是否已存在同名用户,如果没有查询结果应该为None
if query_result == None: # 用户不存在
id = time.strftime('%Y%m%d%H%M%S', time.localtime()) # 将结构化时间(即元组)转换成格式化的字符串,比如20181211110148
password = ''
userinfo = {
'username':username,
'id':id,
'phonenumber':'',
'password':hashmd5(password),
'spacesize': 104857600, ## 初始分配100MB存储空间
'level':1 # 会员等级,初始为1,普通会员
} with open(os.path.join(core.settings.userinfo_dir,id),'w',encoding='utf-8') as f:
json.dump(userinfo,f)
print("用户信息保存完毕")
try: # 创建用户家目录
os.mkdir(os.path.join(core.settings.file_dir,username))
print('用户目录创建成功!')
except Exception as e:
print('用户目录创建失败!',e) else:
print("用户名重复,信息未保存") def change_userinfo(self,username): # 修改用户信息
query_result = core.common.query_user(username) # 检测用户是否存在,不存在不处理
if query_result != None: # 用户存在
filename = query_result['filename']
userinfo = query_result['content']
print('before update: ', userinfo)
update_item = input("请输入要修改的项目,例如password,phonenumber,spacesize,level:") if update_item in ('username','id'):
print(update_item, "项不可更改")
elif update_item in ('password','phonenumber','spacesize','level'):
print("update item: %s" % update_item)
update_value = input("请输入要修改的项目的新值:")
if update_item == 'password':
userinfo[update_item] = hashmd5(update_value)
else:
userinfo[update_item] = update_value
with open(os.path.join(core.settings.userinfo_dir, filename), 'w', encoding='utf-8') as f:
json.dump(userinfo, f)
print(update_item, "项用户信息变更保存完毕")
print('after update: ', userinfo)
else:
print('输入信息错误,', update_item, '项不存在')
else:
print('用户不存在,无法修改') def delete_user(self,username):
query_result = core.common.query_user(username) # 检测用户是否存在,不存在不处理
if query_result != None: # 用户存在
filename = query_result['filename']
userfile_path = os.path.join(core.settings.userinfo_dir,filename)
os.remove(userfile_path)
query_result_again = core.common.query_user(username)
if query_result_again == None:
print('用户信息文件删除成功!')
try:
shutil.rmtree(os.path.join(core.settings.file_dir,username))
print('用户家目录删除成功')
except Exception as e:
print('用户家目录删除失败:',e)
else:
print('用户信息文件删除失败!')
else:
print('用户不存在或者已经被删除') def query_all_user(self): # 查询所有用户信息,用于调试使用
filelist = os.listdir(core.settings.userinfo_dir)
if filelist != []:
for filename in filelist:
with open(os.path.join(core.settings.userinfo_dir,filename),'rb') as f:
userinfo = json.load(f)
print(filename,userinfo)
else:
print("用户信息为空") def interactive(self):
userpage = '''
用户管理界面
1、新增用户
2、查询用户
3、修改用户
4、删除用户
退出请按q
返回上一界面请按r
''' userpage_data = {
'': 'save_userinfo',
'': 'query_userinfo',
'': 'change_userinfo',
'': 'delete_user'
} while True:
print('\033[1;35m{}\033[0m'.format(userpage))
choice = input('请输入你的选择:').strip() if choice == 'q':
exit("退出程序!")
elif choice == 'r':
break
elif choice in userpage_data:
username = input("请输入用户名:").strip()
if username == '':
print('用户不能为空')
continue
if hasattr(self,userpage_data[choice]):
f = getattr(self, userpage_data[choice])
f(username)
else:
print("\033[1;31m输入错误,请重新输入\033[0m")
continue

usermanagement.py

运行示例:

D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/ftp/ftp_server/bin/main.py

    主页
1、启动服务器
2、进入用户管理
退出请按q >>>:2 用户管理界面
1、新增用户
2、查询用户
3、修改用户
4、删除用户
退出请按q
返回上一界面请按r 请输入你的选择:1
请输入用户名:xiaoming
用户信息保存完毕
用户目录创建成功! 用户管理界面
1、新增用户
2、查询用户
3、修改用户
4、删除用户
退出请按q
返回上一界面请按r 请输入你的选择:2
请输入用户名:xiaoming
{'filename': '', 'content': {'username': 'xiaoming', 'id': '', 'phonenumber': '', 'password': 'd41d8cd98f00b204e9800998ecf8427e', 'spacesize': 104857600, 'level': 1}} 用户管理界面
1、新增用户
2、查询用户
3、修改用户
4、删除用户
退出请按q
返回上一界面请按r 请输入你的选择:3
请输入用户名:xiaoming
before update: {'username': 'xiaoming', 'id': '', 'phonenumber': '', 'password': 'd41d8cd98f00b204e9800998ecf8427e', 'spacesize': 104857600, 'level': 1}
请输入要修改的项目,例如password,phonenumber,spacesize,level:phonenumber
update item: phonenumber
请输入要修改的项目的新值:1234567890
phonenumber 项用户信息变更保存完毕
after update: {'username': 'xiaoming', 'id': '', 'phonenumber': '', 'password': 'd41d8cd98f00b204e9800998ecf8427e', 'spacesize': 104857600, 'level': 1} 用户管理界面
1、新增用户
2、查询用户
3、修改用户
4、删除用户
退出请按q
返回上一界面请按r 请输入你的选择:4
请输入用户名:xiaoming
用户信息文件删除成功!
用户家目录删除成功 用户管理界面
1、新增用户
2、查询用户
3、修改用户
4、删除用户
退出请按q
返回上一界面请按r 请输入你的选择:q
退出程序! Process finished with exit code 1

用户管理程序运行示例

D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/ftp/ftp_server/bin/main.py

    主页
1、启动服务器
2、进入用户管理
退出请按q >>>:1 D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/ftp/ftp_client/bin/main.py
请输入用户名>>>:zhengna
请输入密码>>>:123123
认证通过!
\zhengna
>> pwd
\zhengna
\zhengna
>> ls
filename type bytes creationtime
test d 0 20181214 17:17:05
test.txt f 5028331 20181220 15:43:55
vedio2.avi f 86453774 20181214 17:17:35
\zhengna
>> du
current directory total sizes: 96510422
\zhengna
>> cd test
\zhengna\test
\zhengna\test
>> ls
filename type bytes creationtime
test2 d 0 20181217 11:21:07
\zhengna\test
>> rmdir test2
test2 have been deleted.
\zhengna\test
>> cd ..
\zhengna
\zhengna
>> rm test
test is a dir,please use rmdir
\zhengna
>> rm test.txt
test.txt have been deleted.
\zhengna
>> mkdir aa
aa created success
\zhengna
>> mv aa bb
move success
\zhengna
>> put test.ttx
test.ttx is not exist
\zhengna
>> put test.txt
begin
[██████████████████████████████████████████████████]100.00% 5028331/5028331
file upload success...
file received successfully!
\zhengna
>> put test.txt
文件已存在,要覆盖文件请输入yes >>>:yes
begin
[██████████████████████████████████████████████████]100.00% 5028331/5028331
file upload success...
file received successfully!
\zhengna
>> put test.txt True
begin
[██████████████████████████████████████████████████]100.00% 11178154/11178154
file upload success...
file received successfully!
\zhengna
>> get test.ttx
File no found!
\zhengna
>> get test.txt
{'action': 'get', 'filename': 'test.txt', 'filesize': 11178154, 'filemd5': 'a', 'override': 'True'}
[██████████████████████████████████████████████████]100.00% 11178154/11178154
test.txt md5: a 原文件md5: a
文件接收完成!
\zhengna
>> get test.txt
文件已存在,要覆盖文件请输入yes >>>:yes
begin
[██████████████████████████████████████████████████]100.00% 11178154/11178154
file upload success...
file received successfully!
\zhengna
>> get test.txt True
{'action': 'get', 'filename': 'test.txt', 'filesize': 11178154, 'filemd5': 'a', 'override': 'True'}
[██████████████████████████████████████████████████]100.00% 11178154/11178154
test.txt md5: a 原文件md5: a
文件接收完成!
\zhengna
>> newput test.txt
文件存在
文件已存在,要覆盖文件请输入o,要断点续传请输入r >>>:o
0
[██████████████████████████████████████████████████]100.00% 11178154/11178154
file upload success...
file received successfully!
\zhengna
>> newput test.txt
文件存在
文件已存在,要覆盖文件请输入o,要断点续传请输入r >>>:r
11178154 file upload success...
file received successfully!
\zhengna
>> newget test.txt
文件已存在,要覆盖文件请输入o,要断点续传请输入r >>>:o
{'action': 'newget', 'filename': 'test.txt', 'filesize': 11178154, 'filemd5': 'a'}
[██████████████████████████████████████████████████]100.00% 11178154/11178154
test.txt md5: a 原文件md5: a
文件接收完成!
\zhengna
>> newget test.txt
文件已存在,要覆盖文件请输入o,要断点续传请输入r >>>:r
{'action': 'newget', 'filename': 'test.txt', 'filesize': 11178154, 'filemd5': 'a'} test.txt md5: a 原文件md5: a
文件接收完成!
\zhengna
>> newput vedio.avi
文件不存在!
Error, disk space do not enough! Nothing done! Total: 104857600, current: 97631928, rest:7225672, filesize:86453774
\zhengna
>>

主程序运行示例

参考:http://blog.51cto.com/tryagaintry/1969589

总结:这是我第一次写一个这么复杂的程序,虽然大多数的代码都是参考别人写好的。实现它我大概用了2周左右的时间,在这过程中,我一直都在努力思考,尽量让自己弄明白每段代码实现了什么功能?为什么这么写?有没有更好的实现方式?我知道最终的程序并不完美,但是对我来说,重要的不是我在上方贴的大段大段的代码,而是在这2周码代码的过程中,我从中学到了什么。