作业需求:

1. 用户加密认证

2. 多用户同时登陆

3. 每个用户有自己的家目录且只能访问自己的家目录

4. 对用户进行磁盘配额、不同用户配额可不同

5. 用户可以登陆server后,可切换目录

6. 查看当前目录下文件

7. 上传下载文件,保证文件一致性

8. 传输过程中现实进度条

9. 支持断点续传

思路分析:

上一个简单服务器的升级版本,先一个版本链接:http://www.cnblogs.com/sean-yao/p/7772159.html,在原有代码中,重构并实现9个需求,提升程序健壮性:

更新如下:

1. FTP数据存储目录,使用相对路径 ,提升迁移性。

2. FTP在上传下载的时候有粘包处理,其他命令如ls,pls等也做了粘包处理。

3.增加了ConnectionResetError,Exception,UnicodeDecodeError,socket.error等异常处理。

对于高级FTP需求:

1. 用hashlib加密用户输入的密码让Server保存Md5值,实现简单认证加密。

2. 多用户同时登陆使用socketserver模块(上一版本已经实现),控制线程并发多用户同时登陆和操作。

3. 创建用户时候将密码文件FTP目录,相对路径写到密码文件中,认证成功后可以调用相对路径,然后使用OS模块的os.chdir($dir)进行切换用户家目录操作。

4. 用random函数随机一个512M-1024M之间的磁盘配额,(用户剩余空间 = 限额 - 家目录总文件大小)对比文件大小就可以进行磁盘配额的操作。

5. 用户操作使用cd命令,可以切换到家目录的任意目录(前一版本已经实现)。

6. 通过ls查看家目录下的二级目录三级目录等文件(前一版本已经实现)。

7. 上传下载文件,保证文件一致性使用hashlib,让服务端传送客户端进行校验。

8. 传输过程中现实进度条 上传和下载的进度条都已经完成,使用progressbar模块。

9. 断点续传,创建临时文件,客户端上传时,服务器检查临时文件,有就发大小发给客户端,客户端seek到文件断点处给服务器端发送数据。

代码示例:

  此次作业是以上一个版本代码做的重构http://www.cnblogs.com/sean-yao/p/7772159.html,所以这里只放入新增加的部分包含ftp_client.py,ftp_server.py,total_size_class.py,auth.py

README:

作者:yaobin
版本:高级Ftp示例版本 v0.2
开发环境: python3.6 程序介绍: 1. 用户加密认证
2. 多用户同时登陆
3. 每个用户有自己的家目录且只能访问自己的家目录
4. 对用户进行磁盘配额、不同用户配额可不同
5. 用户可以登陆server后,可切换目录
6. 查看当前目录下文件
7. 上传下载文件,保证文件一致性
8. 传输过程中现实进度条
9. 支持断点续传 使用说明:
1.可以在Linux和Windows都可以运行
2.Linux调用了cd,mkdir,ls,rm,命令
3.Windows调用了cd,md,dir,del,命令
On Linux,Windows
Client: Python3 ./Ftp_Client.py
put 上传
get 下载
mkdir 创建目录
ls 查看文件信息
rm 删除文件
drm 删除目录
Server:Python3 ./Ftp_Server.py
put 上传
get 下载
mkdir 创建目录
ls 查看文件信息
rm 删除文件
drm 删除目录 文件目录结构:
├─简单Ftp #程序执行目录
│ README
│ __init__.py

├─bin
│ Ftp_Client.py #客户端程序
│ Ftp_Server.py #服务器端程序
│ __init__.py

├─conf
│ │ setting.py #配置文件
│ │ __init__.py
│ │
│ └─__pycache__
│ setting.cpython-36.pyc
│ __init__.cpython-36.pyc

├─core
│ │ auth.py #用户验证逻辑交互
│ │ commands.py #命令逻辑交互
│ │ ftp_client.py #sock_客户端逻辑交互
│ │ ftp_server.py #sock_服务端逻辑交互
│ │ logger.py #日志逻辑交互---未完成
│ │ main.py #客户端程序
│ │ __init__.py
│ │
│ └─__pycache__
│ auth.cpython-36.pyc
│ commands.cpython-36.pyc
│ ftp_client.cpython-36.pyc
│ ftp_server.cpython-36.pyc
│ main.cpython-36.pyc
│ __init__.cpython-36.pyc

├─db
│ │ __init__.py
│ │
│ ├─colin #用户目录
│ │ │ colin.bak
│ │ │ colin.dat #用户账号密码文件
│ │ │ colin.dir
│ │ │ __init__.py
│ │ │
│ │ └─colin #用户ftp家目录
│ │ │ __init__.py
│ │ │
│ │ └─aaa
│ ├─pub #ftp程序模拟pub目录
│ │ FTP作业.7z
│ │ socket通信client.py
│ │ __init__.py
│ │ 选课系统.png
│ │
│ └─user_path #用户路径文件,判断用户家目录
│ path

├─logs #日志未完成
│ access.log
│ transmission.log
│ __init__.py

├─src
│ │ auth_class.py #用户验证类
│ │ linux_cmd_class.py #linux命令类
│ │ server_class.py #server_socket类
│ │ windows_cmd_class.py #server命令类
│ │ __init__.py
│ │ total_size_class.py #磁盘配额类
│ └─__pycache__
│ auth_class.cpython-36.pyc
│ linux_cmd_class.cpython-36.pyc
│ server_class.cpython-36.pyc
│ windows_cmd_class.cpython-36.pyc
│ __init__.cpython-36.pyc

└─test #测试
args_test.py
__init__.py

ftp_client.py: FTP客户端交互程序

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: Colin Yao
import os
import sys
import socket
import time
import hashlib
import json
import progressbar
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)
from conf import setting class Ftp_client(object): def link(self):
try:
self.sending_msg_list = []
self.ip_addr = '127.0.0.1'
self.ip_port = 62000
self.client = socket.socket()
self.client.connect((self.ip_addr, self.ip_port))
while True:
self.sending_msg = None
self.data = self.client.recv(1024)
print("\033[34;1m[+]Server>>>recv: %s\033[0m" %self.data.decode())
self.menu()
sending_msg = input('请输入命令>>>:')
self.sending_msg_list = sending_msg.split()
if len(self.sending_msg_list) == 0:
data_header = {"test1": {
"action": "",
"file_name": "",
"size": 0}}
self.client.send(json.dumps(data_header).encode())
elif len(self.sending_msg_list) == 1:
if self.sending_msg_list[0] == 'ls' or self.sending_msg_list[0] == 'pls':
self.cmd()
else:
#get BUG fix
data_header ={"test1": {
"action": self.sending_msg_list[0],
"file_name": ".",
"size": 0}}
self.client.send(json.dumps(data_header).encode())
elif len(self.sending_msg_list) >= 2 :
#windows/linux文件路径处理
if setting.os_res == 'Windows':
try :
new_path = self.sending_msg_list[1].encode('utf-8')
self.res_new = self.sending_msg_list[1].strip().split('\\')
self.file_name1 = self.res_new[-1] except IndexError:
pass
elif setting.os_res == 'Linux':
try:
self.res_new = self.sending_msg_list[1].strip().split('/')
self.file_name1 = self.res_new[-1]
except IndexError:
pass
if self.sending_msg_list[0] == "put":
try:
self.put(self.sending_msg_list[1])
except IndexError:
self.client.send('put'.encode())
elif self.sending_msg_list[0] == "get":
try:
self.get(self.file_name1)
except IndexError and ValueError:
self.client.send('get'.encode())
elif self.sending_msg_list[0] == "exit":
break
elif self.sending_msg_list[0] == "ls" or self.sending_msg_list[0] == "pls":
try:
self.cmd()
except AttributeError:
self.cmd()
else:#cd rm drm mkdir 命令等
try:
data_header = {"test1": {
"action": self.sending_msg_list[0],
"file_name": self.file_name1,
"size": 0}}
self.client.send(json.dumps(data_header).encode())
except AttributeError:
data_header = {"test1": {
"action": self.sending_msg_list[0],
"file_name": "",
"size": 0}}
self.client.send(json.dumps(data_header).encode()) except ConnectionResetError and ConnectionAbortedError:
print("[+]Server is Down ....Try to Reconnect......")
self.link()
#cmd方法
def cmd(self):
if len(self.sending_msg_list) == 1:
data_header = {"test1": {
"action": self.sending_msg_list[0],
"file_name": "",
"size": 0}}
elif len(self.sending_msg_list) >= 2:
data_header = { "test1": {
"action": self.sending_msg_list[0],
"file_name": self.file_name1,
"size": 0}}
self.client.send(json.dumps(data_header).encode()) #发送cmd请求主要是ls会有粘包的可能
cmd_res_size = self.client.recv(1024)
self.client.send('准备分割粘包'.encode('utf-8'))
cmd_res_size1 = int(cmd_res_size.decode())
received_size = 0
received_data = b''
while received_size < int(cmd_res_size.decode()):
data = self.client.recv(1024)
received_size += len(data)
received_data += data
else:
print(received_data.decode())
#get方法
def get(self,file_name):
md5_file = hashlib.md5()
data_header = {"test1": {
"action": "get",
"file_name": file_name,
"size": 0}}
self.client.send(json.dumps(data_header).encode()) #发送get请求
self.data = self.client.recv(1024) #拿到size
if self.data.decode() == '':
self.client.send(b'come on')
else:
self.client.send(b'come on')
file_size = int(self.data.decode())
def file_tr():
P = progressbar.ProgressBar()
N = int(self.data.decode())
P.start(N)
file_object = open(file_name, 'wb')
received_size = 0
while received_size < file_size :
#粘包处理
if file_size -received_size > 1024:
size = 1024
#小于1024处理'''
elif file_size < 1024:
size = file_size
else:
size = file_size - received_size
recv_data = self.client.recv(size)
#接收数据的时候和进度条保持一致
received_size += len(recv_data)
md5_file.update(recv_data)
P.update(received_size)
file_object.write(recv_data)
else:
P.finish()
new_file_md5 = md5_file.hexdigest()
file_object.close()
time.sleep(0.1)
print('[+]Client:New_File[%s]Recv Done File_Md5:%s' % (file_name, new_file_md5))
if os.path.exists(file_name) == False:
file_tr()
else:
user_choice = input('文件已经存在即将要删除并下载 [y/删掉旧文件 | n/覆盖旧文件] >>>:')
if user_choice == 'y':
os.remove(file_name)
file_tr()
elif user_choice == 'n':
file_tr()
else:
file_tr()
#put方法
def put(self,file_name):
if os.path.exists(file_name)== True:
if os.path.isfile(file_name):
file_obj = open(file_name, "rb")
data_header = {"test1": {
"action": "put",
"file_name": self.file_name1,
"size": os.path.getsize(self.sending_msg_list[1].encode())}}
self.client.send(json.dumps(data_header).encode())
self.data = self.client.recv(1024)
#有 not enough 数据 还有数字字符的数据
resume_message = (self.data.decode())
if resume_message == 'not enough Spare_size':
print('[+]----Server Space not enough put smaller----')
data_header = {"test1": {
"action": "e1930b4927e6b6d92d120c7c1bba3421",
"file_name": "",
"size": 0}}
self.client.send(json.dumps(data_header).encode())
else:
resume_size = int(self.data.decode())
file_send_size = os.path.getsize(self.sending_msg_list[1])
#断点续传处理
if resume_size < file_send_size and resume_size !=0 :
file_obj = open(file_name, "rb")
md5_file = hashlib.md5()
file_obj.seek(resume_size)#seek到断点位置
file_resume_send_size = (os.path.getsize(self.sending_msg_list[1])-resume_size)#断点大小
data_header = {"test1": {
"action": "resume_put",
"file_name": self.file_name1,
"size": file_resume_send_size}}
self.client.send(json.dumps(data_header).encode())
self.data = self.client.recv(1024)
#测试发送
P = progressbar.ProgressBar()
P.start(file_send_size)
new_size = resume_size
for line in file_obj:
self.client.send(line)
new_size += len(line)
#time.sleep(1)查看断点续传效果
P.update(new_size)
md5_file.update(line)
P.finish()
file_obj.close()
print("[+]Client>>>recv:Send Resume File Done Md5",md5_file.hexdigest()) #文件下载处理
else:
file_obj = open(file_name, "rb")
md5_file = hashlib.md5()
new_size =0
P = progressbar.ProgressBar()
P.start(file_send_size)
for line in file_obj:
self.client.send(line)
new_size += len(line)
P.update(new_size)
md5_file.update(line)
P.finish()
file_obj.close()
print("[+]Client>>>recv:Send File Done Md5:",md5_file.hexdigest())
else:
print('[+]file is no valid.')
self.client.send('cmd'.encode())
else:
print('[+] File Not Found')
data_header = {"test1": {
"action": "aaaa",
"file_name": "",
"size": 0}}
self.client.send(json.dumps(data_header).encode()) def menu(self):
menu = '''
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
进入目录 cd eg: cd /tmp/python
查看文件 ls eg: ls /tmp/README
创建目录 mkdir eg: mkdir /tmp/python
删除文件 rm eg: rm /tmp/README
删除目录 drm eg: drm /tmp/python
上传文件 put eg: put /python/README
下载文件 get eg: get /python/README
新增命令 pls eg: pls 查看db/pub目录文件
注销用户 exit
注意事项 notice windows和linux的路径不同
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
'''
print(menu)

ftp_server.py:FTP服务器端交互程序

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: Colin Yao
import os
import sys
import time
import json
import shelve
import hashlib
import socket
import traceback
import socketserver
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)
from conf import setting
from core.commands import Commands
from src.total_size_class import quota class Ftp_server(socketserver.BaseRequestHandler):
def parsecmd(self,data):
data = json.loads(data.decode())
file_action = data["test1"]["action"]
file_path = data["test1"]["file_name"]
file_size = int(data["test1"]["size"])
file_obj = (setting.data_path+setting.file_object)
file_md5 = hashlib.md5()
print('from ip : %s information : %s' % (self.client_address[0], self.data.decode()))
#固定用户工作家目录
if setting.os_res == 'Windows':
if os.getcwd() == (setting.bin_path):
os.chdir(file_obj )
else:
try:
with open(setting.path_file, 'r') as f:
f1 = []
f2 = f.readline().split('\\')
f1.append(f2)
f3 = os.getcwd()
f4 = f3.split('\\')
if f4[5] == f1[0][1] and f4[6] == f1[0][2]:
pass
else:
os.chdir(file_obj )
except IndexError as e:
os.chdir(file_obj)
elif setting.os_res == 'Linux':
if os.getcwd() == (setting.bin_path):
os.chdir(file_obj )
else:
try:
with open(setting.path_file, 'r') as f:
f1 = []
f2 = f.readline().split('/')
f1.append(f2)
f3 = os.getcwd()
f4 = f3.split('/')
if f4[5] == f1[0][1] and f4[6] == f1[0][2]:
pass
else:
os.chdir(file_obj )
except IndexError as e:
os.chdir(file_obj)
#'用户家目录文件大小file_obj_size用户磁盘配额大小 quota_size
file_obj_size = quota(file_obj).directory_size()
user_info = shelve.open(setting.data_path + setting.file_object)
if setting.os_res == 'Windows':
with open(setting.path_file, 'r') as f:
f1 = []
f2 = f.readline().split('\\')
f1.append(f2)
user_name_key = f1[0][1]
self.quota_user_size = user_info[user_name_key][3]
user_info.close()
elif setting.os_res == 'Linux':
with open(setting.path_file, 'r') as f:
f1 = []
f2 = f.readline().split('/')
f1.append(f2)
user_name_key = f1[0][1]
self.quota_user_size = user_info[user_name_key][3]
user_info.close()
try:
#上传方法
if file_action == 'put':
spare_size = (self.quota_user_size - file_obj_size)
def file_tr():
md5_file = hashlib.md5()
self.request.send(str(file_size).encode())
file_object = open((file_path + '.new'), 'wb')
received_size = 0
while received_size < file_size:
if file_size - received_size > 1024:
size = 1024
elif file_size < 1024:
size = file_size
else:
size = file_size - received_size
recv_data = self.request.recv(size)
received_size += len(recv_data)
md5_file.update(recv_data)
file_object.write(recv_data)
#print(file_size, received_size)
else:
print('[+]File Recv Successful')
file_object.close()
#重命名文件
self.request.send(b'File Data Recv Successful Md5:%s'%(md5_file.hexdigest().encode()))
os.rename((file_path + '.new'),file_path)
#self.request.send(b'File Data Recv Successful')
def put_size():
#磁盘限额和断点续传的处理
if file_size <= spare_size:
if os.path.exists(file_path + '.new'):
new_size = os.path.getsize(file_path + '.new')
if new_size == 0 or new_size>file_size:
file_tr()
else:
self.request.send(str(new_size).encode())
#如果不存在.new的临时文件
else:
file_tr()
elif file_size > spare_size or spare_size == 0:
print('[+] Server Spare_size not enough',self.data.decode())
self.request.send(b'not enough Spare_size')
#文件路径不存在
if os.path.exists(file_path) == False:
put_size()
#路径存在处理
else:
#保持文件最新,put bug fix
if file_path == '.':
self.request.send(b"-b:bash:[+]Server[%s]---file is no valid." % file_path.encode())
else:
os.remove(file_path)
put_size()
#***断点续传方法***
elif file_action == 'resume_put':
spare_size = (self.quota_user_size - file_obj_size)
def resume_put_file_tr():
md5_file = hashlib.md5()
self.request.send(b'read recv resume data')
if os.path.exists(file_path + '.new'):
file_object = open((file_path + '.new'), 'ab')
received_size = 0
while received_size < file_size:
if file_size - received_size > 1024:
size = 1024
elif file_size < 1024:
size = file_size
else:
size = file_size - received_size
recv_data = self.request.recv(size)
received_size += len(recv_data)
md5_file.update(recv_data)
file_object.write(recv_data)
#print(file_size, received_size)
else:
file_object.close()
print('[+]File Resume Recv Successful',time.time())
os.rename((file_path + '.new'), (file_path))
self.request.sendall(b'File Resume Recv Successful Md5 %s'%(md5_file.hexdigest().encode())) #断点续传只要判断磁盘限额即可
def resume_put_size():
if file_size <= spare_size:
resume_put_file_tr()
elif file_size > spare_size or spare_size == 0:
print('[+] Server Spare_size not enough', self.data.decode())
self.request.send(b'not enough Spare_size') #文件路径不存在处理
if os.path.exists(file_path) == False:
resume_put_size()
else:
# 保持文件最新
os.remove(file_path)
resume_put_size()
#下载方法
elif file_action == 'get':
#公共下载目录为db/pub,客户端默认下载路径为用户家目录'
os.chdir(setting.ftp_path)
if os.path.isfile(file_path) and os.path.exists(file_path):
if setting.os_res == 'Windows':
file_size = os.path.getsize(setting.ftp_path + '\\' + file_path)
file_obj_path = (setting.ftp_path + '\\' + file_path)
elif setting.os_res == 'Linux':
file_size = os.path.getsize(setting.ftp_path + '/' + file_path)
file_obj_path = (setting.ftp_path + '/' + file_path)
file_obj = open(file_path, "rb")
#磁盘配额-用户家文件总大小=剩余磁盘空间,用剩余磁盘空间与下载文件大小做对比
spare_size = (self.quota_user_size - file_obj_size)
if file_size <= spare_size:
self.request.send(str(file_size).encode())
self.request.recv(1024)
for line in file_obj:
#md5校验
file_md5.update(line)
self.request.send(line)
file_obj.close()
self.request.send(b"[+]File[%s]Send Done File_Md5:%s" %(file_path.encode(),file_md5.hexdigest().encode()))
#磁盘配额处理
elif file_size > spare_size or spare_size ==0 :#文件总大小>剩余空间发送消息给客户端
self.request.send(str(0).encode())
self.request.recv(1024)
self.request.send(b'-b:bash: There is Not Enough Space The rest is %smb'
%str(round(spare_size / 1024 / 1024)).encode())
else:
#get不存在文件,导致json.decoder.JSONDecodeError,处理方式传一个json
if file_path == '.':
self.request.send(b"-b:bash:[+]Server[%s]---file is no valid."%file_path.encode())
else:
self.request.send(str(0).encode())
self.request.recv(1024)
self.request.send(b"-b:bash:[+]Server[%s]---file is no valid."%file_path.encode()) #查看FTP文件方法
elif file_action == 'pls':
os.chdir(setting.ftp_path)
res = Commands(file_path).ls()
if setting.os_res == 'Windows':
res1 = res.decode('gbk')
elif setting.os_res == 'Linux':
res1 = res.decode()
if len(res1) == 0:#粘包处理
pass
self.request.send(str(len(res1.encode())).encode('utf-8'))
client_send = self.request.recv(1024)
self.request.send(res1.encode('utf-8'))
self.request.send(b'-bash: [%s] [%s]:' %(file_action.encode(),file_path.encode()))
os.chdir(file_obj)
#查看文件方法
elif file_action == 'ls':
res = Commands(file_path).ls()#上一版本没有文件大小信息,只是传送了列表
if setting.os_res == 'Windows':
res1 = res.decode('gbk')
elif setting.os_res == 'Linux':
res1 = res.decode()
if len(res1) == 0:#粘包处理
pass
self.request.send(str(len(res1.encode())).encode('utf-8'))
client_send = self.request.recv(1024)
self.request.send(res1.encode('utf-8'))
self.request.send(b'-bash: [%s] [%s]:'% (file_action.encode(),file_path.encode()))
#CD方法
elif file_action== 'cd':
if os.path.exists(file_obj + '\\' + file_path) == True:
os.chdir(file_obj + '\\'+ file_path)
self.request.send(b'-bash: [%s] [%s]:'%(file_action.encode(),file_path.encode()))
else:
self.request.send(b'-bash:Directory Exitis')
#创建目录方法
elif file_action == 'mkdir':
if os.path.exists(file_path) == True:
self.request.send(b'-bash: directory exitis ')
else:
res = Commands(file_path).mkdir()
self.request.send(b'-bash: [%s] [%s]:'%(file_action.encode(),file_path.encode()))
#文件删除方法
elif file_action == 'rm':
if os.path.isfile(file_path) == True:
res = Commands(file_path).rm()
self.request.send(b'-bash: [%s] [%s]:'%(file_action.encode(),file_path.encode()))
else:
self.request.send(b'-bash: [%s]: Not file'%file_path.encode()) #目录删除方法
elif file_action == 'drm':
if os.path.isdir(file_path) == True:
Commands(file_path).drm()
self.request.send(b'-bash: %s: Delete OK'%file_path.encode())
else:
self.request.send(b'-bash: [%s]: No such File or Directory '%file_path.encode()) elif file_action == 'e1930b4927e6b6d92d120c7c1bba3421':
spare_size = (self.quota_user_size - file_obj_size)
self.request.send(b'-bash: [+] Not Enough Space Spare_size is %smb'%str(round(spare_size/1024/1024)).encode())
else:
self.request.send(b'-bash:Command or File Not Found ')
#异常处理
except Exception and UnicodeDecodeError:
traceback.print_exc() def handle(self):
print("[+] Server is running on port:62000", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
while True:
try:
#调整一下socket.socket()的位置每次重新连接都生成新的socket实例,避免因为意外而导致socket断开连接
print("[+] Connect success -> %s at ", self.client_address, time.strftime("%Y%m%d %H:%M:%S", time.localtime()))
self.request.send(b'\033[34;1mWelcome,-bash version 0.0.1-release \033[0m ')
while True:
self.data = self.request.recv(1024)
data = self.data
self.parsecmd(data)
if not self.data:
print("[+]Error: Client is lost")
break
except socket.error :
print("[+]Error get connect error")
break
continue

total_size_class.py:磁盘配额类

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: Colin Yao
import os,sys
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)
from conf import setting class quota(object):
def __init__(self,file_obj):
self.file_obj = file_obj
self.t1_size = 0
def directory_size(self):
rootdir = self.file_obj # 获取当前路径
t_size = 0
for dirname in os.listdir(rootdir): #获取当前路径所有文件和文件夹
if setting.os_res == 'Windows':
Dir = os.path.join(rootdir+'\\'+ dirname) # 路径补齐
elif setting.os_res == 'Linux':
Dir = os.path.join(rootdir + '/' + dirname) # 路径补齐
if (os.path.isdir(Dir)):
for r, ds, files in os.walk(Dir):
for file in files: # 遍历所有文件
size = os.path.getsize(os.path.join(r, file)) # 获取文件大小
self.t1_size += size
size = os.path.getsize(Dir)
t_size += size
total_size = (self.t1_size+t_size)
return total_size

auth.py:用户认证

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: Colin Yao
import os,sys,shelve,random
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)
from conf import setting
from src.auth_class import Auth
from core.commands import Commands class Auth_ftp(object):
def __init__(self,username,user_passwd):
self.user_data = {}
self.username = username
self.username_passwd = user_passwd
os_res = setting.platform.system()
#使用相对路径适合迁移
if os_res == 'Windows': # 用户密码文件
self.passwd_data_path = os.path.join('\\' + username + '\\' + username + '.' + 'dat')
self.passwd_data = os.path.join('\\' + username + '\\' + username)
self.file_object = os.path.join( '\\' + self.username)
else:
self.passwd_data_path = \
os.path.join('/' + username + '/' + username + '.' + 'dat')
self.passwd_data = \
os.path.join('/' + username + '/' + username)
self.file_object = os.path.join( '/' + username) #磁盘配额512M-1024M用户名key,写入用户名密码路径磁盘配额到字典
user_obj = (self.username,self.username_passwd,self.passwd_data,random.randint(536870912, 1073741824))
self.user_data[self.username] = user_obj
#验证用户是否存在
def auth_user_passwd(self):
#根据用户字典文件判断用户是否存在
os_res = os.path.exists(setting.data_path+self.passwd_data_path)
if os_res !=False:
user_file = shelve.open(setting.data_path+self.passwd_data)
if self.user_data[self.username][0] in user_file \
and user_file[self.username][1] == self.username_passwd:
print("Welcome,%s,您的身份验证成功"%self.username)
user_file.close()
else:
return False
else:
return True def add_user_passwd(self):
res = os.path.exists(setting.data_path+self.file_object)
if res != True:
Commands(setting.data_path+self.file_object).mkdir() #用户账号密码文件
Commands(setting.data_path+self.passwd_data).mkdir() #用户上传下载目录
user_file = shelve.open(setting.data_path+self.passwd_data)
user_file.update(self.user_data)
print("用户创建成功")
else:
print("账号信息出现问题,请联系管理员....")

main.py:登陆认证主交互程序

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: Colin Yao
import os
import sys
import json
import hashlib
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)
from conf import setting
from core.auth import Auth_ftp
from core.ftp_client import Ftp_client class Admin(object):
def run(self):
exit_flag = False
print('欢迎来到简单FTP程序,本地测试请启动server')
#用户加密认证方法密码进行md5加密与服务器做验证
def md5(user_passwd):
md5 = hashlib.md5()
md5.update(user_passwd.encode())
passwd = md5.hexdigest()
return passwd
menu = u'''
\033[32;1m
1.登陆
2.注册
3.退出\033[0m
'''
while not exit_flag:
print(menu)
user_option = input('请输入您的操作,输入q退出>>>:').strip()
#登陆
if user_option == '':
user_name = input('请输入用户名>>>:').strip()
new_user_passwd = input('请输入您的密码>>>:').strip()
user_passwd = md5(new_user_passwd)
file_object = (Auth_ftp(user_name, user_passwd).passwd_data) #传入路径变量
res = Auth_ftp(user_name,user_passwd).auth_user_passwd()
if res == None:
with open(setting.path_file, 'w',encoding='utf-8') as f:
f.write(file_object);f.close()
os.chdir(setting.data_path +file_object)
Ftp_client().link()
elif res == False:
print('%s用户密码不正确' % user_name)
else:
print('请先注册')
elif user_option == '':
user_name = input('请输入用户名>>>:').strip()
new_user_passwd = input('请输入您的密码>>>:').strip()
user_passwd = md5(new_user_passwd)
user_res = Auth_ftp(user_name, user_passwd).auth_user_passwd()
if user_res == None:
print("%s用户不需要注册"%user_name)
file_object = (Auth_ftp(user_name, user_passwd).passwd_data)
with open(setting.path_file, 'w',encoding='utf-8') as f:
f.write(file_object);f.close()
Ftp_client().link()
elif user_res == False:
print("%已注册用户,密码不正确" % user_name)
elif user_res == True:
Auth_ftp(user_name, user_passwd).add_user_passwd() #创建用户名密码文件等
file_object = (Auth_ftp(user_name, user_passwd).passwd_data)
with open(setting.path_file, 'w',encoding='utf-8') as f:
f.write(file_object);f.close()
Ftp_client().link()
else:
sys.exit('异常退出')
elif user_option == 'q' or user_option == '':
sys.exit()
else:
print('输入的选项不正确,请重新输入')
#Admin().run()

setting.py:配置文件

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: Colin Yao
import os
import sys
import platform
import logging
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
Relative_Path = os.path.dirname(os.path.abspath(__file__))
sys.path.append(BASE_DIR)
os_res = platform.system() if os_res == 'Windows':
data_path = os.path.join(BASE_DIR + '\db')
ftp_path = os.path.join(BASE_DIR + '\db\pub')
path_file = os.path.join(BASE_DIR + '\db\\user_path\path')
bin_path = os.path.join(BASE_DIR+'\\bin')
else:
data_path = os.path.join(BASE_DIR + '/db')
ftp_path = os.path.join(BASE_DIR + '\db\pub')
path_file = os.path.join(BASE_DIR + '/db/user_path/path')
bin_path = os.path.join(BASE_DIR + '/bin')
#路径文件判断
if os.path.exists(path_file):
with open(path_file, 'r', encoding='utf-8')as f:
file = f.readlines()
if len(file):
file_object=file[0]
else:
with open(path_file, 'w', encoding='utf-8')as f:
f.write('touch something');f.close()
else:
with open(path_file, 'w', encoding='utf-8')as f:
f.write('touch something');f.close()

程序测试样图

1. 断点续传

创造断点文件

续传文件

2. 下载进度条和Md5校验

3. 上传进度条和Md5校验

4. 用户可以登陆server后,可切换目录,可查看文件

5. 查看用户家目录文件(粘包处理)

python作业高级FTP(第八周)的更多相关文章

  1. python作业高级FTP

    转载自:https://www.cnblogs.com/sean-yao/p/7882638.html 作业需求: 1. 用户加密认证 2. 多用户同时登陆 3. 每个用户有自己的家目录且只能访问自己 ...

  2. python作业ATM(第五周)

    作业需求: 额度 15000或自定义. 实现购物商城,买东西加入 购物车,调用信用卡接口结账. 可以提现,手续费5%. 支持多账户登录. 支持账户间转账. 记录每月日常消费流水. 提供还款接口. AT ...

  3. python作业简单FTP(第七周)

    作业需求: 1. 用户登陆 2. 上传/下载文件 3. 不同用户家目录不同 4. 查看当前目录下文件 5. 充分使用面向对象知识 思路分析: 1.用户登陆保存文件对比用户名密码. 2.上传用json序 ...

  4. python作业堡垒机(第十三周)

    作业需求: 1. 所有的用户操作日志要保留在数据库中 2. 每个用户登录堡垒机后,只需要选择具体要访问的设置,就连接上了,不需要再输入目标机器的访问密码 3. 允许用户对不同的目标设备有不同的访问权限 ...

  5. python作业三级菜单day1(第一周)

    一.作业需求: 1. 运行程序输出第一级菜单 2. 选择一级菜单某项,输出二级菜单,同理输出三级菜单 3. 菜单数据保存在文件中 4. 让用户选择是否要退出 5. 有返回上一级菜单的功能 二三级菜单文 ...

  6. python作业:购物车(第二周)

    一.作业需求: 1.启动程序后,输入用户名密码后,如果是第一次登录,让用户输入工资,然后打印商品列表 2.允许用户根据商品编号购买商品 3.用户选择商品后,检测余额是否够,够就直接扣款,不够就提醒 4 ...

  7. Python学习笔记第十八周

    目录: 一.JavaScript正则表达式 1.test  2.exec 二.BootStrap  1.响应式  2.图标.字体  3.基本使用 三.Django 1.安装  2.创建目录  3.进入 ...

  8. Python09作业思路及源码:高级FTP服务器开发(仅供参考)

    高级FTP服务器开发 一,作业要求 高级FTP服务器开发 用户加密认证(完成) 多用户同时登陆(完成) 每个用户有不同家目录且只能访问自己的家目录(完成) 对用户进行磁盘配额,不同用户配额可不同(完成 ...

  9. matplotlib python高级绘图库 一周总结

    matplotlib python高级绘图库 一周总结 官网 http://matplotlib.org/ 是一个python科学作图库,可以快速的生成很多非常专业的图表. 只要你掌握要领,画图将变得 ...

随机推荐

  1. 使用JsonConfig中的setExcludes方法过滤不需要转换的属性

    Hibernate的many-to-one双向关联中,查询many方时会将one方数据顺带着查询,同时one中会有List<Many>,然后又会去查Many中的数据... 周而复始,结果j ...

  2. hibernate.cfg.xml案例

    一.概念. hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库.既然学习Hibernate那么第 ...

  3. [Git/Github] ubuntu 14.0 下github 配置

    转载自:http://www.faceye.net/search/77573.html 一:创建Repositories1:首先在github下创建一个帐号.这个不用多说,然后创建一个Reposito ...

  4. 对xml的操作使用的类XElement的使用

    操作xml的类比较多,发现XElement类操作xml极其方便,下面列举一些操作方法 1.创建xml XElement xml = new XElement("root", new ...

  5. 如果使用引用方式引用了js后 则不能再本地写js 因为写了后不会有效果

    如果使用引用方式引用了js后 则不能再本地写js 因为写了后不会有效果

  6. poj3107 Godfather 求树的重心

    Description Last years Chicago was full of gangster fights and strange murders. The chief of the pol ...

  7. appium手机操作

    1.按键操作 pressKeyCode(key, metastate) key为按键事件,metastate为辅助功能键 举例: pressKeyCode(AndroidKeyCode.HOME)   ...

  8. python传参

    写在前面 Python唯一支持的参数传递方式是『共享传参』(call by sharing) 多数面向对象语言都采用这一模式,包括Ruby.Smalltalk和Java(Java的引用类型是这样,基本 ...

  9. 洛谷 [USACO09OPEN]工作调度

    题面 读完题,我们会发现有一个很重要的信息,每件物品代价相同,但价值不同.那么我们很容易想到,在满足限制的情况下,我们肯定会选择价值尽可能大的物品. 我们可否用背包来实现呢,答案是否定的,或者说我不会 ...

  10. 解题:POI 2009 Lyz

    题面 板板讲的霍尔定理 霍尔定理:一张二分图有完全匹配的充要条件是对于任$i$个左部点都有至少$i$个右部点与它们相邻.放在这个题里就是说显然最容易使得鞋不够的情况是一段连续的人,那就维护一下最大子段 ...