高级FTP


一、作业需求

1. 用户加密认证(已完成)

2. 多用户同时登陆(已完成)

3. 每个用户有自己的家目录且只能访问自己的家目录(已完成)

4. 对用户进行磁盘配额、不同用户配额可不同(已完成)

5. 用户可以登陆server后,可切换目录(已完成)

6. 查看当前目录下文件(已完成)

7. 上传下载文件,保证文件一致性(已完成)

8. 传输过程中现实进度条(已完成)

9. 支持断点续传(未完成)

readme:

 

一、作业需求:
1. 用户加密认证(已完成)
2. 多用户同时登陆(已完成)
3. 每个用户有自己的家目录且只能访问自己的家目录(已完成)
4. 对用户进行磁盘配额、不同用户配额可不同(已完成)
5. 用户可以登陆server后,可切换目录(已完成)
6. 查看当前目录下文件(已完成)
7. 上传下载文件,保证文件一致性(已完成)
8. 传输过程中现实进度条(已完成)
9. 支持断点续传(未完成)
二、博客地址:http://www.cnblogs.com/catepython/p/8616018.html
三、运行环境
操作系统:Win10
Python:3.6.2rcl
Pycharm:2017.1.14
四、功能实现
1)多用户同时登录,并做了用户不得重复登录判断(现为测试方便此调用方法已注释)
2)区分不同用户不同的文件目录
3)可在当前目录下上传/下载文件并保存
4)上传/下载文件进度显示
5)区分了用户本地/服务端文件目录
6)只能移动到自己家目录下的目录
cd /:移动到根目录下 cd ..:返回上一级目录 cd + 目录名:移动到指定目录下
7)新增pwd查看当前路径操作
8)查看当前目录下文件信息
新增dir home:查看用户本地目录文件信息 dir server:查看用户服务端目录文件信息
9)每个用户有不同的磁盘配额
10)上传/下载文件后进行加密认证
11)新增mkdir操作:在当前目录下创建新目录文件
五、测试
1)文件名为空判断
2)用户信息判断
3)指令格式化判断
4)用户使用cd指令对其做了isdir()判断
5)用户使用mkdir指令时对其做了当前目录下已有同名目录判断
6)上传/下载到指定路径判断
例:
1、当前在根目录下:E:.....\user_home
上传/下载文件完成后文件保存至根目录下
2、当前路径:E:.....\user_home\test\test2
上传/下载文件完成后文件保存在test2目录下
7)在当前路径下创建新目录文件
例:
1、当前在根目录下:E:.....\user_home
使用mkdir命令在根目录下创建新目录
2、当前路径:E:.....\user_home\test\test2
使用mkdir命令在E:.....\user_home\test\test2目录下创建新目录
8)上传/下载文件后进行加密认证:对本地文件与服务端文件做了mk5加密认证
9)做了多用户登录上传/下载
10)当用户配额<上传/下载文件时会做“磁盘配额不足无法上传/下载文件”提示
六、备注
1、断点续传功能有空时可以新增并完善
readme

 

 

 

二、流程图

 

三、目录结构图

 

四、代码区

bin目录下程序开始文件

#-*- Coding:utf-8 -*-
# Author: D.Gray
import os,sys
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)
from core import ftp_client
fc = ftp_client.Ftp_client()
start_client.py
#-*- Coding:utf-8 -*-
# Author: D.Gray
import os,sys
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)
from core import ftp_server
fs = ftp_server.Ftp_server()
start_server.py

conf目下的setting.py系统配置文件

#-*- Coding:utf-8 -*-
# Author: D.Gray
import os,sys,socket
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)
#IP和端口信息
IP_PORT = ("localhost",6969)
#用户数据文件
USER_FILE = os.path.join(BASE_DIR,'db\\user_info')
#用户文件目录
USER_HOME = BASE_DIR
setting.py

core目录下主程序文件

#-*- Coding:utf-8 -*-
# Author: D.Gray
import sys,os,socket,hashlib,time,json
from conf import setting
from core import users
class Ftp_client(object):
'''
FTP客服端
'''
def __init__(self):
'''
构造函数
'''
self.client = setting.socket.socket()
self.client.connect(setting.IP_PORT)
self.help_info = """\033[33;1m
请用'put'+'空格'+'文件名'的格式下载文件
请用'get'+'空格'+'文件名'的格式上传文件
请用'cd'+'空格'+'目录名'的格式进入家目录下的子文件夹
请用'cd'+'空格'+'..'的格式返回上级目录
请用'mkdir'+'空格'+'目录名'的格式创建家目录的文件夹
输入'dir'+'空格'+'home'查看用户家目录
输入'dir'+'空格'+'server'查看用户服务端家目录
\033[0m"""
if self.auth():
self.start()
def auth(self):
'''
用户登录认证函数
1、用户输入账号密码
2、序列化用户信息字典发送给服务端
3、接收服务端用户登录认证消息
4、认证成功返回True给构造函数
5、用户进入start()函数进行指令操作
:return:
'''
while True:
username = input("请输入账户名>>>:").strip()
password = input('请输入用户密码>>>:').strip()
#auth = 'auth %s %s'%(username,password)
mesg = {
"action":'auth',
"username":username,
"password":password
}
self.client.send(json.dumps(mesg).encode())
self.user_obj = users.Users(username)
back_res = self.client.recv(1024).decode()
if back_res == 'ok':
print("\033[32;1m认证成功\033[0m")
user = self.user_obj.get_user()
self.user_name = user["username"]
self.user_type = user["type"]
self.user_path = user['home']
self.disk_quota = user["disk_quota"]
self.pwd_path = os.path.join(setting.USER_HOME,self.user_path,"user_home") #定义一个默认路径
return True
elif back_res == "302":
print("\033[31;1m密码错误\033[0m")
elif back_res == "301":
print("\033[31;1m该用户已登录\033[0m")
else:
print("\033[31;1m用户不存在\033[0m")
def start(self):
'''
用户操作函数
1、用户输入操作指令
2、判断操作指令是否有效
3、反射指令
:return:
'''
while True:
user_inport = input("%s>>>:"%(self.user_name)).strip()
if len(user_inport) == 0 :continue
user_inport = user_inport.split()
if user_inport[0] == 'q':
break
if hasattr(self,user_inport[0]):
func = getattr(self,user_inport[0])
func(user_inport)
else:
print("\033[31;1m请输入有效指令:\033[0m",self.help_info)
continue
def put(self,cmd):
'''
下载服务端文件函数
1、接收服务端回调信息(305 = 服务端文件不存在或下载文件大小)
2、判断磁盘配额和文件大小
3、接收服务端回调信息
4、开始接收文件并打印进度条
5、加密认证
6、重新计算磁盘配额 调用Users类中update_disk_quota()方法将最新磁盘配额参数重新写入用户文件中
:param cmd:
:return:
'''
if len(cmd) < 2:
print("\033[31;1m请输入有效指令:\033[0m", self.help_info)
else:
'''
下载服务端文件
'''
mesg = {
"action": cmd[0],
"file_name": cmd[1],
"disk_quota": self.disk_quota
}
self.client.send(json.dumps(mesg).encode())
server_back = self.client.recv(1024).decode()
print("\033[32;1m收到服务器回调:\033[0m",server_back)
if server_back == '305':
print("\033[31;1m文件不存在\033[0m")
else:
file_total_size = int(server_back)
print("\033[32;1m下载文件总大小:\033[0m", file_total_size)
print("\033[32;1m磁盘配额还剩:%sM\033[0m" % mesg["disk_quota"])
if file_total_size >= mesg["disk_quota"] * (2 ** 20):
print('\033[31;1m磁盘配额不够无法下载文件\033[0m')
else:
revered_size = 0
# file_path = os.path.join(setting.USER_HOME,self.user_path,"user_home",cmd[1])
file_path = os.path.join(self.pwd_path,cmd[1])
print('in the put_pwd_path:',file_path)
self.client.send(b"ok")
self.m = hashlib.md5()
i = 0
with open(file_path,'wb') as f:
while revered_size < file_total_size:
if file_total_size - revered_size < 1024:
size = file_total_size - revered_size
else:
size = 1024
data = self.client.recv(size)
revered_size += len(data)
'''
打印进度条
'''
str1 = "已接受 %sByte"%revered_size
str2 = '%s%s'%(round((revered_size/file_total_size)*100,2),'%')
str3 = '[%s%s]'%('*'*i,str2)
sys.stdout.write('\033[32;1m\r%s%s\033[0m'%(str1,str3))
sys.stdout.flush()
i += 2
time.sleep(0.3)
'''
加密认证
'''
self.m.update(data)
f.write(data)
self.encryption()
'''
磁盘配额
'''
new_disk_quota = round((mesg["disk_quota"] * (2 ** 20) - file_total_size) / (2 ** 20), 2)
# mesg["disk_quota"]* (2 ** 20) 将用户文件中磁盘参数转成相应的Bytes数值
 self.user_obj.update_disk_quota(new_disk_quota)
print("\033[32;1m磁盘配额还剩:%sM\033[0m"%new_disk_quota)
def get(self,cmd):
'''
客户端上传文件至服务端函数
1、判断指令格式是否正确
2、上传文件或文件路径是否有效和存在
3、获取文件大小
4、判断磁盘配额是否大于文件大小
5、获取服务端上传文件回调请求
6、发送文件并打印进度条
7、加密认证
8、重新计算磁盘配额 调用Users类中update_disk_quota()方法将最新磁盘配额参数重新写入用户文件中
:param cmd:
:return:
'''
if len(cmd) < 2:
print("\033[31;1m请输入有效指令:\033[0m", self.help_info)
else:
'''
上传文件
'''
file_path = os.path.join(self.pwd_path,cmd[1])
if os.path.isfile(file_path):
file_total_size = os.stat(file_path).st_size
mesg = {
"action": cmd[0],
"file_name": cmd[1],
"disk_quota": self.disk_quota,
"file_size" : file_total_size
}
print("\033[32;1m磁盘配额还剩:%sM\033[0m" % mesg["disk_quota"])
if file_total_size >= mesg["disk_quota"]*(2**20):
print("\033[31;1m磁盘配额不够无法上传文件\033[0m")
else:
self.client.send(json.dumps(mesg).encode())
print("\033[32;1m上传文件总大小:\033[0m", file_total_size)
self.client.recv(1024)
print("开始发送文件")
self.m = hashlib.md5()
send_size = 0
i = 0
with open(file_path,'rb')as f:
while send_size < file_total_size:
if file_total_size - send_size <1024:
size = file_total_size - send_size
data = f.read(size)
send_size += len(data)
else:
data = f.read(1024)
send_size += len(data)
self.client.send(data)
'''
打印进度条
'''
str1 = "已上传 %sByte:" %send_size
str2 = '%s%s' % (round((send_size / file_total_size) * 100, 2), '%')
str3 = '[%s%s]' % ('*'*i, str2)
sys.stdout.write('\033[32;1m\r%s%s\033[0m' % (str1, str3))
sys.stdout.flush()
i += 2
time.sleep(0.3)
'''
文件加密
'''
self.m.update(data)
self.encryption()
'''
磁盘配额
'''
new_disk_quota = round((mesg["disk_quota"]*(2**20) - file_total_size)/(2**20),2)
self.user_obj.update_disk_quota(new_disk_quota)
print("\033[32;1m磁盘配额还剩:%sM\033[0m"%new_disk_quota)
else:
print("\033[31;1m文件不存在\033[0m")
def encryption(self):
'''
文件加密函数
1、判断用户是否需要加密
2、取消加密发送'401'信息给服务端
3、确认加密发送'400'信息给服务端
4、接收服务端文件加密信息
5、判断客户端和服务端文件加密信息是否一致
:return:
'''
encryption = input("\n文件已接收是否需要加密认证...按q取消加密>>>")
if encryption != 'q':
self.client.send(b'400')
print('\033[32;1m确认加密\033[0m')
file_md5 = self.m.hexdigest()
server_back_md5 = self.client.recv(1024).decode()
print("\033[32;1m本地文件加密:%s\n服务端文件加密:%s\033[0m" % (file_md5, server_back_md5))
if file_md5 == server_back_md5:
print("\033[32;1m加密认证成功\033[0m")
else:
print("加密认证失败")
else:
self.client.send(b'401')
print("\033[32;1m\n已取消加密.文件接收成功\033[0m")
def dir(self,cmd):
'''
查看根目录下文件信息函数
1、dir_home 查看用户本地文件内容
2、dir_server 查看用户服务器文件内容
3、接收服务端指令文件大小
4、发送接收目录信息指令
5、接收目录信息
:param cmd:
:return:
'''
if len(cmd) < 2:
print("\033[31;1m请输入有效指令:\033[0m", self.help_info)
else:
if cmd[1] == "home" or cmd[1] == 'server':
mesg = {
"action":cmd[0],
"object":cmd[1]
}
self.client.send(json.dumps(mesg).encode())
server_back = self.client.recv(1024).decode()
print('\033[32;1m收到服务端回调指令大小:\033[0m',server_back)
self.client.send("ok".encode())
revered_size = 0
revered_data = b''
while revered_size < int(server_back):
data = self.client.recv(1024)
revered_data += data
revered_size = len(data)
print('\033[32;1m实际收到指令大小:\033[0m',revered_size)
else:
print(revered_data.decode())
else:
print("\033[31;1m请输入有效指令:\033[0m", self.help_info)
def mkdir(self,cmd):
'''
添加目录文件函数
1、判断指令是否正确
2、先获取当前路径
3、判断所添加目录是否已存在
4、使用os.mkdir()函数添加新目录
5、新目录添加成功
:param cmd:
:return:
'''
if len(cmd) < 2:
print("\033[31;1m请输入有效指令:\033[0m", self.help_info)
else:
# file_path = os.path.join(setting.USER_HOME,self.user_path,"user_home",cmd[1])
file_path = os.path.join(self.pwd_path,cmd[1])
print("当前路径:", file_path)
if os.path.exists(file_path):
print("\033[31;1m该目录文件夹已存在\033[0m")
else:
os.mkdir(file_path)
print("该目录文件夹创建成功")
def cd(self,cmd):
'''
CD:移动到指定目录函数
1、先判断指令是否正确
2、判断路径是否有效
3、根据输入做相应操作如:cd ..:移动到上一级目录 cd / :移动到根目录 cd 目录名:移动到指定目录
4、拆分路径重新拼接新路径
5、返回self.pwd_path当前所在目录
:param cmd:
:return:
'''
if len(cmd) < 2:
print("\033[31;1m请输入有效指令:\033[0m", self.help_info)
else:
if cmd[1] == '..':
list = []
pwd_path = os.path.join(self.pwd_path)
for index in pwd_path.split('\\'): #列表形式拆分当前目录路径以'\\'分隔
list.append(index) #将目录路径参数添加至list列表中
list[0] = '%s%s'%(list[0],'/') #将列表第一个元素 E: 字符串拼接成 E:/
if list[-1] == "user_home":
print("已在根目录下")
else:
del list[-1] #删除列表最后个元素也就是上一级目录路径
self.pwd_path = ''
for item in list: #重新拼接成新的路径
self.pwd_path = os.path.join(self.pwd_path,item)
print("当前路径:",self.pwd_path)
#print(os.listdir(self.pwd_path))
elif cmd[1] == '/':
self.pwd_path = os.path.join(setting.USER_HOME,self.user_path,"user_home")
print("已返回根目录:", self.pwd_path)
else:
pwd_path = os.path.join(self.pwd_path,cmd[1]) #移动到指定目录 cmd[1]目录名
if os.path.isdir(pwd_path):
#print(os.listdir(pwd_path))
self.pwd_path = pwd_path #返回用户当前路径
print("当前路径:", self.pwd_path)
else:
print("\033[31;1m系统找不到指定的路径\033[0m")
def pwd(self,cmd):
'''
显示当前目录路径
:param cmd:
:return:
'''
print("当前路径:", self.pwd_path)
print(os.listdir(self.pwd_path))
def help(self,cmd):
'''
帮助文档函数
:param cmd:
:return:
'''
print(self.help_info)
ftp_client.py
#-*- Coding:utf-8 -*-
# Author: D.Gray
import sys,os,socket,hashlib,socketserver,json,time
from conf import setting
from core import users
class MyServer(socketserver.BaseRequestHandler):
print('等待链接...')
'''
FTP服务端类
'''
def auth(self,*args):
'''
用户登录认证函数
1、接收客户端用户字典信息
2、序列化字典信息
3、调用Users类中get_user()函数
4、判断用户是否有效
5、发送认证信息至客户端
:param args:
:return:
'''
cmd = args[0]
self.user_obj = users.Users(cmd['username'])
auth_user = self.user_obj.get_user()
if auth_user:
if auth_user['password'] == cmd["password"]:
if auth_user['status'] == 0:
self.request.send(b"ok")
#self.user_obj.update_status_close()
self.user_home = auth_user["home"]
self.user_type = auth_user["type"]
else:
self.request.send(b"301")
print("\033[31;1m该用户已登录\033[0m")
else:
self.request.send(b'302')
print("\033[31;1m密码错误\033[0m")
else:
self.request.send(b"300")
print("\033[31;1m用户名不存在\033[0m")
def put(self,*args):
'''
服务端发送文件给客户端
1、判断文件是否存在
2、获取文件总大小
3、发送文件大小给客户端
4、接收客户端下载文件请求
5、开始循环发送文件给客户端
6、发送完成后调用加密函数
:param args:
:return:
'''
cmd = args[0]
file_path = os.path.join(setting.USER_HOME,self.user_home,'server_home',cmd["file_name"])
if os.path.isfile(file_path):
file_total_size = os.stat(file_path).st_size
print("\033[32;1m获取文件大小:\033[0m",file_total_size)
self.request.send(str(file_total_size).encode())
self.request.recv(1024)
print("开始发送文件")
self.m = hashlib.md5()
with open(file_path,'rb') as f:
for line in f:
self.m.update(line)
self.request.send(line)
self.encryption()
else:
self.request.send(b'305')
print("文件不存在")
def get(self,*args):
'''
服务端接收客户端发送文件函数
1、接收客户端发送文件大小
2、发送接收客户端文件请求
3、开始接收文件
4、跟踪文件接收并写入相应目录
5、接收完成后调用加密函数
:param args:
:return:
'''
cmd = args[0]
#print(cmd)
file_path = os.path.join(setting.USER_HOME,self.user_home,"server_home",cmd["file_name"])
print("\033[32;1m收到客户端发送文件大小:\033[0m", cmd["file_size"])
self.request.send(b"ok")
print("开始接收文件")
file_total_size = cmd["file_size"]
rever_size = 0
self.m = hashlib.md5()
with open(file_path,'wb') as f:
while rever_size < file_total_size:
if file_total_size - rever_size <1024:
size = file_total_size - rever_size
else:
size = 1024
data = self.request.recv(size)
rever_size += len(data)
self.m.update(data)
f.write(data)
self.encryption()
def encryption(self):
'''
加密认证函数
1、发送确认加密'400'请求
2、发送服务端文件加密信息至客户端
:return:
'''
client_back = self.request.recv(1024).decode()
if client_back == "400":
print("\033[32;1m确认加密请求\033[0m")
server_file_md5 = self.m.hexdigest()
self.request.send(server_file_md5.encode())
print("\033[32;1m服务端文件加密:\033[0m", server_file_md5)
else:
print("\033[32;1m\n已取消加密.客户端文件接收完成\033[0m")
def dir(self,*args):
'''
服务端查看目录文件信息函数
1、序列化客户端字典信息
2、popen()获取目录文件信息
3、判断目录信息长度
4、发送目录长度信息至客户端
5、接收客户端回调请求
6、发送目录信息至客户端
:param args:
:return:
'''
cmd = args[0]
if cmd["object"] == 'home':
file_name = 'user_home'
else:
file_name = 'server_home'
file_path = os.path.join(setting.USER_HOME,self.user_home,file_name)
res = os.popen("%s %s"%(cmd["action"],file_path)).read()
print("in the dir:",res)
if len(res) == 0:
res = "has not output"
self.request.send(str(len(res.encode())).encode())
self.request.recv(1024)
self.request.send(res.encode())
def handle(self):
'''
与客户端交互函数(解析客户端操作指令)
1、接收客户端链接信息
2、接收客户端操作指令(action)需序列化
3、反射操作指令
:return:
'''
while True:
try:
self.data = self.request.recv(1024).strip()
print("{}已链接".format(self.client_address))
actin_dict = json.loads(self.data.decode())
#print(actin_dict)
print('in the handle',actin_dict["action"])
if hasattr(self,str(actin_dict["action"])):
func = getattr(self,str(actin_dict["action"]))
func(actin_dict)
except ConnectionResetError as e:
print("%s客户端已断开%s"%(self.client_address,e))
#self.user_obj.update_status_open()
break
server = socketserver.ThreadingTCPServer((setting.IP_PORT),MyServer) #支持多用户操作:ThreadingTCPServer
server.serve_forever()
ftp_server.py
#-*- Coding:utf-8 -*-
# Author: D.Gray
import os,sys,json
from conf import setting
class Users(object):
'''
用户类
'''
def __init__(self,username):
self.username = username
self.user_file = setting.USER_FILE + "\\%s.json"%(self.username)
#print(self.user_file)
self.users_read = self.read_users()
def read_users(self):
'''
读取用户文件信息函数
1、判断用户文件是否存在(用户是否存在)
2、遍历用户json文件中内容
3、返回遍历内容
:return:
'''
if os.path.exists(self.user_file):
with open(self.user_file, 'r') as f:
user_read = eval(f.read())
return user_read
def get_user(self):
'''
1、判断服务端传过来的用户名参数是否与文件中用户名参数
2、异常处理:用户名与用户文件参数类型不一直
:return:
'''
#print('in the User_get_user:',user)
try:
if self.users_read["username"] == self.username:
return self.users_read
except TypeError as e:
pass
def update_status_close(self):
'''
修改用户登录状态函数
0-未登录
1-已登录
无法重复登录
:return:
'''
with open(self.user_file,'r') as f:
fr = f.read()
fd = eval(fr)
with open(self.user_file,'w') as fw:
res = fr.replace(str(fd['status']),str(1))
fw.write(res)
def update_status_open(self):
'''
修改用户登录状态函数
0-未登录
1-已登录
无法重复登录
:return:
'''
with open(self.user_file, 'r') as f:
fr = f.read()
fd = eval(fr)
with open(self.user_file, 'w') as fw:
res = fr.replace(str(fd['status']), str(0))
fw.write(res)
def update_disk_quota(self,new_disk_quota):
'''
更改用户磁盘配额函数
:param new_disk_quota:接收客户端新磁盘配额参数
:return:
'''
with open(self.user_file,'r') as f:
fr = f.read()
fd = eval(fr)
with open(self.user_file,'w') as fw:
res = fr.replace(str(fd["disk_quota"]),str(new_disk_quota))
fw.write(res)
user

 

db/user_info目录下的数据文件

{
"username":"alex",
"password":"admin",
"status":0,
"type":0,
"home":"home\\alex",
"disk_quota":0.97
}
student_view

 

转载于:https://www.cnblogs.com/gaodi2345/p/11412816.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值