作业:

开发一个支持多用户在线的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,以备后用)。

程序结构:

完整代码:

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周码代码的过程中,我从中学到了什么。

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

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

    要求: 用户加密认证 允许同时多用户登录 每个用户有自己的家目录 ,且只能访问自己的家目录 对用户进行磁盘配额,每个用户的可用空间不同 允许用户在ftp server上随意切换目录 允许用户查看当前目 ...

  2. python 开发一个支持多用户在线的FTP

    ### 作者介绍:* author:lzl### 博客地址:* http://www.cnblogs.com/lianzhilei/p/5813986.html### 功能实现 作业:开发一个支持多用 ...

  3. 老男孩python作业7-开发一个支持多用户在线的FTP程序

    作业6:开发一个支持多用户在线的FTP程序 要求: 用户加密认证 允许同时多用户登录 每个用户有自己的家目录 ,且只能访问自己的家目录 对用户进行磁盘配额,每个用户的可用空间不同 允许用户在ftp s ...

  4. (转)Python开发程序:支持多用户在线的FTP程序

    原文链接:http://www.itnose.net/detail/6642756.html 作业:开发一个支持多用户在线的FTP程序 要求: 用户加密认证 允许同时多用户登录 每个用户有自己的家目录 ...

  5. Python3学习之路~0 目录

    目录 Python3学习之路~2.1 列表.元组操作 Python3学习之路~2.2 简单的购物车程序 Python3学习之路~2.3 字符串操作 Python3学习之路~2.4 字典操作 Pytho ...

  6. 开发一个支持多用户同时在线的FTP程序

    FTP 要求: .用户加密认证 .允许同时多用户登录 .每个用户有自己的家目录,且只能访问自己的家目录 .对用户进行磁盘配额,每个用户的可用空间不同 .允许用户在ftp server上随意切换目录 . ...

  7. 我是怎么开发一个小型java在线学习网站的

    2016/1/27 11:55:14 我是怎么开发一个小型java在线学习网站的 一直想做一个自己的网站(非博客),但是又不知道做什么内容的好,又一次看到了w3schools,就萌发了开发一个在线ja ...

  8. 1   开发一个注重性能的JDBC应用程序不是一件容易的事. 当你的代码运行很慢的时候JDBC驱动程序并不会抛出异常告诉你。   本系列的性能提示将为改善JDBC应用程序的性能介绍一些基本的指导原则,这其中的原则已经被许多现有的JDBC应用程序编译运行并验证过。 这些指导原则包括:    正确的使用数据库MetaData方法    只获取需要的数据    选用最佳性能的功能    管理连

    1 开发一个注重性能的JDBC应用程序不是一件容易的事. 当你的代码运行很慢的时候JDBC驱动程序并不会抛出异常告诉你. 本系列的性能提示将为改善JDBC应用程序的性能介绍一些基本的指导原则,这其中的 ...

  9. Android开发学习之路-Android Studio开发小技巧

    上一次发过了一个介绍Studio的,这里再发一个补充下. 我们都知道,Android Studio的功能是非常强大的,也是很智能的.如果有人告诉你学Android开发要用命令行,你可以告诉他Andro ...

随机推荐

  1. oracle查看执行最慢与查询次数最多的sql语句

    前言 在ORACLE数据库应用调优中,一个SQL的执行次数/频率也是常常需要关注的,因为某个SQL执行太频繁,要么是由于应用设计有缺陷,需要在业务逻辑上做出优化处理,要么是业务特殊性所导致.如果执行频 ...

  2. Java_修饰符

    目录 访问控制修饰符 非访问修饰符 在java中修饰符主要分为两类:++访问修饰符++和++非访问修饰符++ 访问控制修饰符 修饰符 当前类 同一包内 子孙类 其他包 其他包子孙类 public Y ...

  3. 十三python基础之socket编程

      阅读目录 一 客户端/服务器架构 二 osi七层 三 socket层 四 socket是什么 五 套接字发展史及分类 六 套接字工作流程 七 基于TCP的套接字 八 基于UDP的套接字 九 粘包现 ...

  4. 关于Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/springmvc-demo-01-start]]出错的解决方法

    出错的详情: 严重: A child container failed during start java.util.concurrent.ExecutionException: org.apache ...

  5. 金蝶K3 WISE BOM多级展开_销售成本表

    /****** Object: StoredProcedure [dbo].[pro_bobang_SaleCost] Script Date: 07/29/2015 16:13:43 ******/ ...

  6. Make Eudict for reviewing example sentences

    Recently, I've started an activity of recording typical and nice English sentences from dictionaries ...

  7. Exceptionless 生产部署笔记

    参考 部署用于生产的Exceptionlees(一个强大易用的日志收集服务) 1. 安装配置 redis 4.0  点击下载redis教学脑图 cd /opt wget http://download ...

  8. PSO:利用PSO算法优化二元函数,寻找最优个体适应度—Jason niu

    figure [x,y] = meshgrid(-5:0.1:5,-5:0.1:5); z = x.^2 + y.^2 - 10*cos(2*pi*x) - 10*cos(2*pi*y) + 20; ...

  9. antd中按需加载使用react-app-rewired报错

    [描述] 按照antd官网步骤 https://ant.design/docs/react/use-with-create-react-app-cn 最后yarn start会报错 [解决方法] 原因 ...

  10. linux操作笔记记录

    export https_proxy=https://10.10.2.91:8888export http_proxy=http://10.10.2.91:8888 桥接模式:需要配一个静态ip,可以 ...