• 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. 查看用户家目录文件(粘包处理)

  • 相关阅读:
    用DOS把一个文件夹下的所有文件名打印到txt文件里
    多线程时的CreateDispatch问题
    [转载]惟楚有才
    [转] 数学专业参考书整理推荐
    大整数处理类(cpp文件)
    [转]英文文献检索
    大整数处理类(头文件)
    国外遥感GIS期刊整理(转)
    [转载]Hibernate中No row with the given identifier exis
    在武汉,总有那么一个词~~~
  • 原文地址:https://www.cnblogs.com/saolv/p/10139741.html
Copyright © 2020-2023  润新知