python作业高级FTP
转载自:https://www.cnblogs.com/sean-yao/p/7882638.html
作业需求:
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() == '0':
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 == '1':
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 == '2':
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 == '3':
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的更多相关文章
- python作业高级FTP(第八周)
作业需求: 1. 用户加密认证 2. 多用户同时登陆 3. 每个用户有自己的家目录且只能访问自己的家目录 4. 对用户进行磁盘配额.不同用户配额可不同 5. 用户可以登陆server后,可切换目录 6 ...
- python作业简单FTP(第七周)
作业需求: 1. 用户登陆 2. 上传/下载文件 3. 不同用户家目录不同 4. 查看当前目录下文件 5. 充分使用面向对象知识 思路分析: 1.用户登陆保存文件对比用户名密码. 2.上传用json序 ...
- Python09作业思路及源码:高级FTP服务器开发(仅供参考)
高级FTP服务器开发 一,作业要求 高级FTP服务器开发 用户加密认证(完成) 多用户同时登陆(完成) 每个用户有不同家目录且只能访问自己的家目录(完成) 对用户进行磁盘配额,不同用户配额可不同(完成 ...
- python第四十八天--高级FTP
高级FTP服务器1. 用户加密认证2. 多用户同时登陆3. 每个用户有自己的家目录且只能访问自己的家目录4. 对用户进行磁盘配额.不同用户配额可不同5. 用户可以登陆server后,可切换目录6. 查 ...
- (转)python高级FTP
原文地址:http://www.itnose.net/detail/6754889.html高级FTP服务器1. 用户加密认证2. 多用户同时登陆3. 每个用户有自己的家目录且只能访问自己的家目录4. ...
- 进击的Python【第七章】:Python的高级应用(四)面向对象编程进阶
Python的高级应用(三)面向对象编程进阶 本章学习要点: 面向对象高级语法部分 静态方法.类方法.属性方法 类的特殊方法 反射 异常处理 Socket开发基础 一.面向对象高级语法部分 静态方法 ...
- 进击的Python【第六章】:Python的高级应用(三)面向对象编程
Python的高级应用(三)面向对象编程 本章学习要点: 面向对象编程介绍 面向对象与面向过程编程的区别 为什么要用面向对象编程思想 面向对象的相关概念 一.面向对象编程介绍 面向对象程序设计(英语: ...
- 进击的Python【第五章】:Python的高级应用(二)常用模块
Python的高级应用(二)常用模块学习 本章学习要点: Python模块的定义 time &datetime模块 random模块 os模块 sys模块 shutil模块 ConfigPar ...
- 进击的Python【第四章】:Python的高级应用(一)
Python的高级应用(一) 本章内容: 内置函数 生成器 迭代器 装饰器 JSON和PICKLE的简单用法 软件目录结构规范 一.内置函数 1.数学运算类 abs(x) 求绝对值1.参数可以是整型, ...
随机推荐
- JS学习笔记Day3
一.什么是循环结构 满足一定条件,(((重复)))执行一段相同的代码 二.循环思想是什么(循环三要素) 开始 结束 步长(步进) 三.可以实现循环语句的有哪些 while do while for 四 ...
- Vue(基础七)_webpack(webpack异步加载原理)
---恢复内容开始--- 一.前言 1.webpack异步加载原理’ 2.webpack.ensure原理 ...
- 字节输入流 FileInputStream
字节输入流 InputStream : 方法介绍: read(); 读取下一个字节 返回-1读取文件结束 close(); 复制文件 将数据aaa.txt复制到d盘 字节输入流读---->字节输 ...
- Go-day04
今日概要: 1.内置函数.递归函数.闭包 2.数组与切片 3.map数据结构 4.package介绍 5.互斥锁和读写锁 一.内置函数 1.close:主要用来关闭channel 2.len:用来求长 ...
- Shell 同步时间脚本
Linux系统同步时间脚本 Linux操作系统,如果时间和网络时间差距太大的话.可能会导致程序,进程启动不了.所以linux系统时间同步显得尤为重要,本文在借鉴网上众多资料后,以centos_6.X系 ...
- diff补丁格式
title: diff补丁格式 tags: 学习 categories: 学习 date: 2018-09-20 21:03:53 --- diff补丁格式 在Uboot学习中,接触到了打补丁这个操作 ...
- 《老梁四大名著情商课》笔记-学学TA,你就是聚会的万人迷
<老梁四大名著情商课>笔记-学学TA,你就是聚会的万人迷 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 现在社会学家有一个统计,说中国处在单身状态大概有2个亿.这些人中 ...
- jdk和jvm基本介绍
一.JDK和JRE? 在刚入门java开发的时候,第一步都要从官网下载JDK来帮助开发,下载下来安装之后看到安装目录结构如下 bin目录:存放Java的编译器.解释器等工具(可执行文件). db目录: ...
- 【C#】C#获取文件夹下的所有文件
#基础知识 1.获得当前运行程序的路径 string rootPath = Directory.GetCurrentDirectory(); 2.获得该文件夹下的文件,返回类型为FileInfo st ...
- golang实现tcp编程
实现简单的tcp服务 package main import ( "fmt" "net" ) func main() { fmt.Println("服 ...