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("33[34;1m[+]Server>>>recv: %s33[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. 查看用户家目录文件(粘包处理)

原文地址:https://www.cnblogs.com/saolv/p/10139741.html