转载自: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(" 33[34;1m[+]Server>>>recv: %s 33[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' 33[34;1mWelcome,-bash version 0.0.1-release 33[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''' 33[32;1m 1.登陆 2.注册 3.退出 33[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 + 'dbpub') path_file = os.path.join(BASE_DIR + 'db\user_pathpath') bin_path = os.path.join(BASE_DIR+'\bin') else: data_path = os.path.join(BASE_DIR + '/db') ftp_path = os.path.join(BASE_DIR + 'dbpub') 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. 查看用户家目录文件(粘包处理)