作業需求:
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 settingclass Ftp_client(object):def link(self):try:self.sending_msg_list = []self.ip_addr = '127.0.0.1'self.ip_port = 62000self.client = socket.socket()self.client.connect((self.ip_addr, self.ip_port))while True:self.sending_msg = Noneself.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 fixdata_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:passelif setting.os_res == 'Linux':try:self.res_new = self.sending_msg_list[1].strip().split('/')self.file_name1 = self.res_new[-1]except IndexError:passif 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":breakelif 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 = 0received_data = b''while received_size < int(cmd_res_size.decode()):data = self.client.recv(1024)received_size += len(data)received_data += dataelse: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) #拿到sizeif 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 = 0while received_size < file_size :#粘包處理if file_size -received_size > 1024:size = 1024#小于1024處理'''elif file_size < 1024:size = file_sizeelse:size = file_size - received_sizerecv_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_sizefor 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 =0P = 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 quotaclass 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]:passelse: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]:passelse:os.chdir(file_obj )except IndexError as e:os.chdir(file_obj)#'用戶家目錄文件大小file_obj_size用戶磁盤配額大小 quota_sizefile_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 = 0while received_size < file_size:if file_size - received_size > 1024:size = 1024elif file_size < 1024:size = file_sizeelse:size = file_size - received_sizerecv_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 fixif 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 = 0while received_size < file_size:if file_size - received_size > 1024:size = 1024elif file_size < 1024:size = file_sizeelse:size = file_size - received_sizerecv_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,處理方式傳一個jsonif 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:#粘包處理passself.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:#粘包處理passself.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.dataself.parsecmd(data)if not self.data:print("[+]Error: Client is lost")breakexcept socket.error :print("[+]Error get connect error")breakcontinue
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 settingclass quota(object):def __init__(self,file_obj):self.file_obj = file_objself.t1_size = 0def directory_size(self):rootdir = self.file_obj # 獲取當前路徑t_size = 0for 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 += sizesize = os.path.getsize(Dir)t_size += sizetotal_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 Commandsclass Auth_ftp(object):def __init__(self,username,user_passwd):self.user_data = {}self.username = usernameself.username_passwd = user_passwdos_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 Falseelse:return Truedef 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_clientclass Admin(object):def run(self):exit_flag = Falseprint('歡迎來到簡單FTP程序,本地測試請啟動server')#用戶加密認證方法密碼進行md5加密與服務器做驗證def md5(user_passwd):md5 = hashlib.md5()md5.update(user_passwd.encode())passwd = md5.hexdigest()return passwdmenu = u'''\033[32;1m1.登陸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. 查看用戶家目錄文件(粘包處理)
?