• 开发一个支持多用户在线的FTP程序


    一,项目题目:开发一个支持多用户在线的FTP程序

    二,项目要求:

    1.用户加密认证
    
    2.允许同时多用户登录
    
    3.每个用户有自己的家目录 ,且只能访问自己的家目录
    
    4.对用户进行磁盘配额,每个用户的可用空间不同
    
    5.允许用户在ftp server上随意切换目录
    
    6.允许用户查看当前目录下文件
    
    7.允许上传和下载文件,保证文件一致性(md5)
    
    8.文件传输过程中显示进度条
    
    9.附加功能:支持文件的断点续传
    

    三,注意事项: 

    基本要求. 完成1,2,3,5,6,7,8 
    实力选手. 完成 上条 及需求4 ,     
    大神操作. 完成 9 且项目目录结构良好、代码逻辑清晰,
    

      

    四,项目分析:

    1,用户加密认证

      这个肯定需要用到configparser 和hashlib模块,用md5进行加密,服务端与用户端
    进行交互前,肯定需要进行认证,在服务端进行认证,客户端需要发送用户名及密码,但
    是为了安全起见,服务端数据库中的密码应该是加密后的密文,客户端登陆认证时也应该
    发送密文到服务端,服务端接受到密文与数据库中对应的密文进行比较。
    

      

    2,查看自己的当前目录下的文件

      这个只需要写一个dir就ok
    简单的说,使用configparse模块就可以完成
    

      

    3,文件传输中显示进度条

      下载的进度条比较好实现,我们可以从服务端受到将要下载的文件的大小,
    
      上传的进度条,我们可以利用文件操作的tell()方法,获取当前指针位置(字节)
    

      

    4,小编的主要思路

    - 1 对于此项目,最初的想法是写出上传,和下载文件的程序,包括客户端和服务端。
    
    -  2 在此基础上扩展程序,包括提出开始程序到bin里面,配置文件在config里面
    
    -  3 然后把上传文件和下载文件的程序进行断点续传的程序重构
    
    -   4 在此基础上,对文件进行加密
    
    -   5 增加功能,包括设置进度条,增加查看功能,增加目录功能,删除文件功能,切换目录功能等
    
    -   6 然后再设置磁盘分配功能,完善内容
    
    -   7 然后添加用户登陆,包括对用户的密码加密等功能
    
    -   8 写完后检查程序
    

      

     五,项目流程图

    六,README文件

    ## 作者:zhanzhengrecheng
    ## 版本:示例版本 v0.1
    ## 程序介绍:
    - 实现了支持多用户在线的FTP程序 常用功能
    - 功能全部用python的基础知识实现,用到了sockethashlibconfigparseossyspickle函数模块类知识
    
    ## 概述
    本次作业文件夹一共包含了以下5个文件:
    - 流程图: FTP_homework思路流程图
    - 程序结构图:整个FTP_homework的程序文件结构
    - 程序结构文件:整个FTP_homework的程序文件结构
    - 程序文件: FTP_homework
    - 程序说明文件:README.md
    
    ## 程序要求
    - 1.用户加密认证
    - 2.允许同时多用户登录
    - 3.每个用户有自己的家目录 ,且只能访问自己的家目录
    - 4.对用户进行磁盘配额,每个用户的可用空间不同
    - 5.允许用户在ftp server上随意切换目录
    - 6.允许用户查看当前目录下文件
    - 7.允许上传和下载文件,保证文件一致性(md5)
    - 8.文件传输过程中显示进度条
    - 9.附加功能:支持文件的断点续传
    ## 本项目思路
    - 1 对于此项目,最初的想法是写出上传,和下载文件的程序,包括客户端和服务端。
    -  2 在此基础上扩展程序,包括提出开始程序到bin里面,配置文件在config里面
    -  3 然后把上传文件和下载文件的程序进行断点续传的程序重构
    -   4 在此基础上,对文件进行加密
    -   5 增加功能,包括设置进度条,增加查看功能,增加目录功能,删除文件功能,切换目录功能等
    -   6 然后再设置磁盘分配功能,完善内容
    -   7 然后添加用户登陆,包括对用户的密码加密等功能
    -   8 写完后检查程序
    
    
    ##### 备注(程序结构)
    > 目前还不会把程序树放在README.md里面,所以做出程序结构的txt版本和图片版本,放在文件外面方便查看
    
    ## 对几个实例文件的说明
    ### 几个实例文件全是为了上传和下载使用,自己随便找的素材
    
    ## 不足及其改进的方面
    ### 每次程序从用户登陆到使用只能完成一次功能,不能重复使用
    
    ## 程序结构
    
    │  FTP_homework
    │  __init__.py
    │  
    ├─client                # 客户端程序入口
    │  │  __init__.py
    │  ├─bin                # 可执行程序入口目录
    │  │      run.py
    │  │      __init__.py
    │  ├─config             # 配置文件目录
    │  │  │  settings.py    # 配置文件
    │  │  │  __init__.py       
    │  ├─core               # 主要逻辑程序目录
    │  │  │  ftp_client.py  # client端主程序模块
    │  │  │  __init__.py       
    │  ├─download           # 下载内容模块
    │  │      a.jpg 
    │  │      a.txt
    │  │      c.mp4  
    │  └─upload             # 上传内容模块
    │          a.txt
    │          aa.avi
    └─server                 # 服务端程序入口
        ├─bin
        │      run.py        # 可执行程序入口目录
        │      __init__.py 
        ├─config             # 配置文件目录
        │  │  accounts.ini   # 账号密码配置文件
        │  │  settings.py    # 配置文件
        │  │  __init__.py        
        ├─core               # 主要逻辑程序目录
        │  │  ftp_server.py  # server端主程序模块
        │  │  main.py        # 主程序模块
        │  │  user_handle.py # 用户注册登录模块  
        └─home               # 家目录
            │  __init__.py
            ├─curry          # curry用户的家目录
            │  │  aa.avi
            │  └─test
            └─james           # james用户的家目录
                │  a.jpg
                │  aa.avi
                │  c.mp4
                └─test1
    

      

    七,程序结构图

     八,程序代码

    1,server

    1.1 bin

    run.py

    # _*_ coding: utf-8 _*_
    import os
    import sys
    
    
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    sys.path.append(BASE_DIR)
    
    
    from core import ftp_server
    from core import main
    from config import settings
    
    if __name__ == '__main__':
        a = main.Manager()
        a.interactive()
    

      

    1.2config

    settings.py

    # _*_ coding: utf-8 _*_ 
    import os
    import sys
    import socket
    
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    sys.path.append(BASE_DIR)
    
    ACCOUNTS_FILE = os.path.join(BASE_DIR,'config','accounts.ini')
    
    address_family = socket.AF_INET
    socket_type = socket.SOCK_STREAM
    
    BIND_HOST = '127.0.0.1'
    BIND_PORT = 9999
    ip_port = (BIND_HOST,BIND_PORT)
    coding = 'utf-8'
    listen_count = 5
    max_recv_bytes = 8192
    allow_reuser_address = False
    

      

    1.3core

    ftp_server.py

    # _*_ coding: utf-8 _*_
    import socket
    import struct
    import json
    import os
    import pickle
    import subprocess
    import hashlib
    
    from config import settings
    from core.user_handle import UserHandle
    
    class FTPServer():
    
        def __init__(self,server_address,bind_and_listen = True):
            self.server_address = server_address
            self.socket = socket.socket(settings.address_family,settings.socket_type)
            if bind_and_listen:
                try:
                    self.server_bind()
                    self.server_listen()
                except Exception:
                    self.server_close()
    
        def server_bind(self):
            allow_reuse_address = False
            if allow_reuse_address:
                self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.socket.bind(self.server_address)
    
        def server_listen(self):
            self.socket.listen(settings.listen_count)
    
        def server_close(self):
            self.socket.close()
    
        def server_accept(self):
            return self.socket.accept()
    
        def conn_close(self, conn):
            conn.close()
    
        def getfile_md5(self):
            '''获取文件的md5'''
            return hashlib.md5(self.readfile()).hexdigest()
    
        def readfile(self):
            '''读取文件,得到文件内容的bytes类型'''
            with open(self.file_path,'rb') as f:
                filedata = f.read()
            return filedata
    
        def send_filedata(self,exist_file_size=0):
            """下载时,将文件打开,send(data)"""
            with open(self.file_path, 'rb') as f:
                f.seek(exist_file_size)
                while True:
                    data = f.read(1024)
                    if data:
                        self.conn.send(data)
                    else:
                        break
    
        def get(self, cmds):
            '''
           下载,首先查看文件是否存在,然后上传文件的报头大小,上传文件,以读的方式发开文件
           找到下载的文件
        发送 header_size
        发送 header_bytes file_size
        读文件 rb 发送 send(line)
        若文件不存在,发送0 client提示:文件不存在
           :param cmds:
           :return:
                   '''
            if len(cmds) > 1:
                filename = cmds[1]
                self.file_path = os.path.join(os.getcwd(), filename)
                if os.path.isfile(self.file_path):
                    file_size = os.path.getsize(self.file_path)
                    obj = self.conn.recv(4)
                    exist_file_size = struct.unpack('i', obj)[0]
                    header = {
                        'filename': filename,
                        'filemd5': self.getfile_md5(),
                        'file_size': file_size
                    }
                    header_bytes = pickle.dumps(header)
                    self.conn.send(struct.pack('i', len(header_bytes)))
                    self.conn.send(header_bytes)
                    if exist_file_size:  # 表示之前被下载过 一部分
                        if exist_file_size != file_size:
                            self.send_filedata(exist_file_size)
                        else:
                            print('33[31;1mbreakpoint and file size are the same33[0m')
                    else:  # 文件第一次下载
                        self.send_filedata()
                else:
                    print('33[31;1merror33[0m')
                    self.conn.send(struct.pack('i', 0))
    
            else:
                print("33[31;1muser does not enter file name33[0m")
    
        def recursion_file(self, dir):
            """递归查询用户目录下的所有文件,算出文件的大小"""
            res = os.listdir(dir)
            for i in res:
                path = os.path.join(dir,i)
                if os.path.isdir(path):
                    self.recursion_file(path)
                elif os.path.isfile(path):
                    self.home_bytes_size += os.path.getsize(path)
    
        def current_home_size(self):
            """得到当前用户目录的大小,字节/M"""
            self.home_bytes_size =0
            self.recursion_file(self.homedir_path)
            home_m_size = round(self.home_bytes_size / 1024 / 1024, 1)
    
        def put(self,cmds):
            """从client上传文件到server当前工作目录下
            """
            if len(cmds) >1:
                obj = self.conn.recv(4)
                state_size = struct.unpack('i', obj)[0]
                if state_size ==0:
                    print("33[31;1mfile does not exist!33[0m")
                else:
                    # 算出了home下已被占用的大小self.home_bytes_size
                    self.current_home_size()
                    header_bytes = self.conn.recv(struct.unpack('i', self.conn.recv(4))[0])
                    header_dic = pickle.loads(header_bytes)
                    filename = header_dic.get('filename')
                    file_size = header_dic.get('file_size')
                    file_md5 = header_dic.get('file_md5')
                    self.file_path = os.path.join(os.getcwd(),filename)
                    if os.path.exists(self.file_path):
                        self.conn.send(struct.pack('i',1))
                        has_size = os.path.getsize(self.file_path)
                        if has_size == file_size:
                            print("33[31;1mfile already does exist!33[0m")
                            self.conn.send(struct.pack('i', 0))
                        else:
                            print('33[31;1mLast file not finished,this time continue33[0m')
                            self.conn.send(struct.pack('i', 1))
                            if self.home_bytes_size + int(file_size-has_size)>self.quota_bytes:
                                print('33[31;1mSorry exceeding user quotas33[0m')
                                self.conn.send(struct.pack('i', 0))
                            else:
                                self.conn.send(struct.pack('i', 1))
                                self.conn.send(struct.pack('i', has_size))
                                with open(self.file_path, 'ab') as f:
                                    f.seek(has_size)
                                    self.write_file(f, has_size, file_size)
                                self.verification_filemd5(file_md5)
                    else:
                        self.conn.send(struct.pack('i', 0))
                        print('33[31;1mSorry file does not exist now first put33[0m')
                        if self.home_bytes_size + int(file_size) > self.quota_bytes:
                            print('33[31;1mSorry exceeding user quotas33[0m')
                            self.conn.send(struct.pack('i', 0))
                        else:
                            self.conn.send(struct.pack('i', 1))
                            with open(self.file_path,'wb') as f:
                                recv_size = 0
                                self.write_file(f, recv_size, file_size)
                            self.verification_filemd5(file_md5)
    
            else:
                print("33[31;1muser does not enter file name33[0m")
        def write_file(self,f,recv_size,file_size):
            '''上传文件时,将文件内容写入到文件中'''
            while recv_size < file_size:
                res = self.conn.recv(settings.max_recv_bytes)
                f.write(res)
                recv_size += len(res)
                self.conn.send(struct.pack('i', recv_size))  # 为了进度条的显示
    
        def verification_filemd5(self,filemd5):
            # 判断文件内容的md5
            if self.getfile_md5() == filemd5:
                print('33[31;1mCongratulations download success33[0m')
                self.conn.send(struct.pack('i', 1))
            else:
                print('33[31;1mSorry download failed33[0m')
                self.conn.send(struct.pack('i', 0))
    
        def ls(self,cmds):
            '''查看当前工作目录下,先返回文件列表的大小,在返回查询的结果'''
            print("33[34;1mview current working directory33[0m")
            subpro_obj = subprocess.Popen('dir',shell=True,
                                          stdout=subprocess.PIPE,
                                          stderr=subprocess.PIPE)
            stdout = subpro_obj.stdout.read()
            stderr = subpro_obj.stderr.read()
            self.conn.send(struct.pack('i',len(stdout + stderr)))
            self.conn.send(stdout)
            self.conn.send(stderr)
    
        def mkdir(self,cmds):
            '''增加目录
            在当前目录下,增加目录
            1.查看目录名是否已经存在
            2.增加目录成功,返回 1
            2.增加目录失败,返回 0'''
            print("33[34;1madd working directory33[0m")
            if len(cmds) >1:
                mkdir_path = os.path.join(os.getcwd(),cmds[1])
                if not os.path.exists(mkdir_path):
                    os.mkdir(mkdir_path)
                    print('33[31;1mCongratulations add directory success33[0m')
                    self.conn.send(struct.pack('i',1))
                else:
                    print("33[31;1muser directory already does exist33[0m")
                    self.conn.send(struct.pack('i',0))
            else:
                print("33[31;1muser does not enter file name33[0m")
    
        def cd(self,cmds):
            '''切换目录
            1.查看是否是目录名
            2.拿到当前目录,拿到目标目录,
            3.判断homedir是否在目标目录内,防止用户越过自己的home目录 eg: ../../....
            4.切换成功,返回 1
            5.切换失败,返回 0'''
            print("33[34;1mSwitch working directory33[0m")
            if len(cmds) > 1:
                dir_path = os.path.join(os.getcwd(),cmds[1])
                if os.path.isdir(dir_path):
                    #os.getcwd 获取当前工作目录
                    previous_path = os.getcwd()
                    #os.chdir改变当前脚本目录
                    os.chdir(dir_path)
                    target_dir = os.getcwd()
                    if self.homedir_path in target_dir:
                        print('33[31;1mCongratulations switch directory success33[0m')
                        self.conn.send(struct.pack('i', 1))
                    else:
                        print('33[31;1mSorry switch directory failed33[0m')
                        # 切换失败后,返回到之前的目录下
                        os.chdir(previous_path)
                        self.conn.send(struct.pack('i', 0))
                else:
                    print('33[31;1mSorry switch directory failed,the directory is not current directory33[0m')
                    self.conn.send(struct.pack('i', 0))
            else:
                print("33[31;1muser does not enter file name33[0m")
    
        def remove(self,cmds):
            """删除指定的文件,或者空文件夹
                   1.删除成功,返回 1
                   2.删除失败,返回 0
                   """
            print("33[34;1mRemove working directory33[0m")
            if len(cmds) > 1:
                file_name = cmds[1]
                file_path = os.path.join(os.getcwd(),file_name)
                if os.path.isfile(file_path):
                    os.remove(file_path)
                    self.conn.send(struct.pack('i', 1))
                elif os.path.isdir(file_path):  # 删除空目录
                    if not len(os.listdir(file_path)):
                        os.removedirs(file_path)
                        print('33[31;1mCongratulations remove success33[0m')
                        self.conn.send(struct.pack('i', 1))
                    else:
                        print('33[31;1mSorry remove directory failed33[0m')
                        self.conn.send(struct.pack('i', 0))
                else:
                    print('33[31;1mSorry remove directory failed33[0m')
                    self.conn.send(struct.pack('i', 0))
            else:
                print("33[31;1muser does not enter file name33[0m")
    
        def get_recv(self):
            '''从client端接收发来的数据'''
            return pickle.loads(self.conn.recv(settings.max_recv_bytes ))
    
        def handle_data(self):
            '''处理接收到的数据,主要是将密码转化为md5的形式'''
            user_dic = self.get_recv()
            username = user_dic['username']
            password = user_dic['password']
            md5_obj = hashlib.md5()
            md5_obj.update(password)
            check_password = md5_obj.hexdigest()
    
        def auth(self):
            '''
            处理用户的认证请求
            1,根据username读取accounts.ini文件,然后查看用户是否存在
            2,将程序运行的目录从bin.user_auth修改到用户home/username方便之后查询
            3,把客户端返回用户的详细信息
            :return:
            '''
            while True:
                user_dic = self.get_recv()
                username = user_dic['username']
                password = user_dic['password']
                md5_obj = hashlib.md5(password.encode('utf-8'))
                check_password = md5_obj.hexdigest()
                user_handle  = UserHandle(username)
                # 判断用户是否存在 返回列表,
                user_data = user_handle.judge_user()
                if user_data:
                    if user_data[0][1] ==check_password:
                        self.conn.send(struct.pack('i',1))  # 登录成功返回 1
                        self.homedir_path = os.path.join(settings.BASE_DIR,'home',username)
                        # 将程序运行的目录名修改到 用户home目录下
                        os.chdir(self.homedir_path)
                        # 将用户配额的大小从M 改到字节
                        self.quota_bytes = int(user_data[2][1])*1024*1024
                        user_info_dic = {
                            'username':username,
                            'homedir':user_data[1][1],
                            'quota':user_data[2][1]
                        }
                        # 用户的详细信息发送到客户端
                        self.conn.send(pickle.dumps(user_info_dic))
                        return True
                    else:
                        self.conn.send(struct.pack('i', 0))  # 登录失败返回 0
                else:
                    self.conn.send(struct.pack('i', 0))  # 登录失败返回 0
    
        def server_link(self):
            print("33[31;1mwaiting client .....33[0m")
            while True:  # 链接循环
                self.conn,self.client_addr = self.server_accept()
                while True:  # 通信循环
                    try:
                        self.server_handle()
                    except Exception:
                        break
                self.conn_close(self.conn)
        def server_handle(self):
            '''处理与用户的交互指令'''
            if self.auth():
                print("33[32;1m-------user authentication successfully-------33[0m")
                res = self.conn.recv(settings.max_recv_bytes)
                # 解析命令,提取相应的参数
                cmds = res.decode(settings.coding).split()
                if hasattr(self, cmds[0]):
                    func = getattr(self, cmds[0])
                    func(cmds)
    

      

    main.py

    # _*_ coding: utf-8 _*_ 
    from core.user_handle import UserHandle
    from core.ftp_server import FTPServer
    from config import settings
    
    
    class Manager():
        '''
        主程序,包括启动server,创建用户,退出
        :return:
        '''
        def start_ftp(self):
            '''启动server端'''
            server = FTPServer(settings.ip_port)
            server.server_link()
            server.close()
    
        def create_user(self):
            '''创建用户,执行创建用户的类'''
            username =  input("33[32;1mplease input your username>>>33[0m").strip()
            UserHandle(username).add_user()
    
        def logout(self):
            '''
            退出登陆
            :return:
            '''
            print("33[32;1m-------Looking forward to your next login-------33[0m")
            exit()
        def interactive(self):
            '''交互函数'''
            msg = '''33[32;1m
                           1   启动ftp服务端
                           2   创建用户
                           3   退出
                   33[0m'''
            menu_dic = {
                "1": 'start_ftp',
                "2": 'create_user',
                "3": 'logout',
            }
            exit_flag = False
            while not exit_flag:
                print(msg)
                user_choice = input("Please input a command>>>").strip()
                if user_choice in menu_dic:
                    getattr(self,menu_dic[user_choice])()
                else:
                    print("33[31;1myou choice doesn't exist33[0m")
    

      

    user_handle.py

    # _*_ coding: utf-8 _*_ 
    import configparser
    import hashlib
    import os
    
    from config import settings
    
    class UserHandle():
        '''
        创建用户名称,密码
        如果用户存在,则返回,如果用户不存在,则注册成功
        '''
        def __init__(self,username):
            self.username = username
            self.config = configparser.ConfigParser()
            self.config.read(settings.ACCOUNTS_FILE)
    
        @property
        def password(self):
            '''生成用户的默认密码 '''
            password_inp = input("33[32;1mplease input your password>>>33[0m").strip()
            md5_obj = hashlib.md5()
            md5_obj.update(password_inp.encode())
            md5_password = md5_obj.hexdigest()
            return md5_password
    
        @property
        def disk_quota(self):
            '''生成每个用户的磁盘配额'''
            quota = input('33[32;1mplease input Disk quotas>>>:33[0m').strip()
            if quota.isdigit():
                return quota
            else:
                exit('33[31;1mdisk quotas must be integer33[0m')
    
        def add_user(self):
            """创建用户,存到accounts.ini"""
            if not self.config.has_section(self.username):
                print('33[31;1mcreating username is :%s 33[0m' %self.username)
                self.config.add_section(self.username)
                self.config.set(self.username, 'password', self.password)
                self.config.set(self.username, 'homedir', 'home/' + self.username)
                self.config.set(self.username, 'quota', self.disk_quota)
                with open(settings.ACCOUNTS_FILE, 'w') as f:
                    self.config.write(f)
                os.mkdir(os.path.join(settings.BASE_DIR, 'home', self.username))  # 创建用户的home文件夹
                print('33[1;32msuccessfully create userdata33[0m')
            else:
                print('33[1;31musername already existing33[0m')
    
        def judge_user(self):
            """判断用户是否存在"""
            if self.config.has_section(self.username):
                return self.config.items(self.username)
    

      

    2,client

    2.1bin

    run.py

    # _*_ coding: utf-8 _*_
    import os
    import sys
    
    
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    sys.path.append(BASE_DIR)
    
    
    from core import ftp_client
    from config import settings
    if __name__ == '__main__':
        run = ftp_client.FTPClient(settings.ip_port)
        run.execute()
    

      

    2.2config

    settings.py

    # _*_ coding: utf-8 _*_
    import os
    import sys
    import socket
    
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    sys.path.append(BASE_DIR)
    # 下载的文件存放路径
    down_filepath = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'download')
    # 上传的文件存放路径
    upload_filepath = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'upload')
    #绑定的IP地址
    BIND_HOST = '127.0.0.1'
    #绑定的端口号
    BIND_PORT = 9999
    ip_port = (BIND_HOST,BIND_PORT)
    address_family = socket.AF_INET
    socket_type = socket.SOCK_STREAM
    
    coding = 'utf-8'
    listen_count = 5
    max_recv_bytes = 8192
    allow_reuser_address = False
    

      

    2.3core

    ftp_client.py

    # _*_ coding: utf-8 _*_ 
    import socket
    import struct
    import json
    import os
    import sys
    import pickle
    import hashlib
    
    from config import settings
    
    class FTPClient:
    
        def __init__(self,server_address,connect = True):
            self.server_address = server_address
            self.socket = socket.socket(settings.address_family,settings.socket_type)
            if connect:
                try:
                    self.client_connect()
                except Exception:
                    self.client_close()
    
        def client_connect(self):
            try:
                self.socket.connect(self.server_address)
            except Exception as e:
                print("33[31;1merror:%s33[0m"%e)
                exit("33[31;1m
    The server is not activated 33[0m")
    
        def client_close(self):
            self.socket.close()
    
        def readfile(self):
            '''读取文件'''
            with open(self.file_path,'rb') as f:
                filedata = f.read()
            return filedata
    
        def appendfile_content(self,file_path,temp_file_size,file_size):
            '''追加文件内容'''
            with open(file_path,'ab') as f:
                f.seek(temp_file_size)
                get_size = temp_file_size
                while get_size < file_size:
                    res = self.socket.recv(settings.max_recv_bytes)
                    f.write(res)
                    get_size += len(res)
                    self.progress_bar(1,get_size,file_size)  #1表示下载
    
        def getfile_md5(self):
            '''对文件内容进行加密,也就是保持文件的一致性'''
            md5 = hashlib.md5(self.readfile())
            print("md5是:
    ",md5.hexdigest())
            return md5.hexdigest()
    
        def progress_bar(self,num,get_size,file_size):
            float_rate = float(get_size) / float(file_size)
            rate_num = round(float_rate * 100,2)
            if num ==1: #1表示下载
                sys.stdout.write('33[31;1m
    finish downloaded perentage:{0}%33[0m'.format(rate_num))
            elif num ==2:  #2表示上传
                sys.stdout.write('33[31;1m
    finish uploaded perentage:{0}%33[0m'.format(rate_num))
            sys.stdout.flush()
    
        def recv_file_header(self,header_size):
            """接收文件的header, filename file_size file_md5"""
            header_types = self.socket.recv(header_size)
            header_dic = pickle.loads(header_types)
            print(header_dic, type(header_dic))
            total_size = header_dic['file_size']
            filename = header_dic['filename']
            filemd5 = header_dic['filemd5']
            return (filename,total_size,filemd5)
    
        def verification_filemd5(self,filemd5):
            # 判断下载下来的文件MD5值和server传过来的MD5值是否一致
            if self.getfile_md5() == filemd5:
                print('33[31;1mCongratulations download success33[0m')
            else:
                print('33[31;1mSorry download failed,download again support breakpoint continuation33[0m')
        def write_file(self,f,get_size,file_size):
            '''下载文件,将内容写入文件中'''
            while get_size < file_size:
                res = self.socket.recv(settings.max_recv_bytes)
                f.write(res)
                get_size += len(res)
                self.progress_bar(1,get_size,file_size)  #1表示下载
    
        def get(self,cmds):
            """从server下载文件到client
            """
            if len(cmds) >1:
                filename = cmds[1]
                self.file_path = os.path.join(settings.down_filepath, filename)
                if os.path.isfile(self.file_path):  #如果文件存在,支持断电续传
                    temp_file_size = os.path.getsize(self.file_path)
                    self.socket.send(struct.pack('i',temp_file_size))
                    header_size = struct.unpack('i',self.socket.recv(4))[0]
                    if header_size:
                        filename,file_size,filemd5 = self.recv_file_header(header_size)
                        if temp_file_size == file_size:
                            print('33[34;1mFile already does exist33[0m')
                        else:
                            print('33[34;1mFile now is breakpoint continuation33[0m')
                            self.appendfile_content(self.file_path,temp_file_size)
                            self.verification_filemd5(filemd5)
                    else:
                        print("33[34;1mFile was downloaded before,but now server's file is not exist33[0m")
                else:#如果文件不存在,则是直接下载
                    self.socket.send(struct.pack('i',0))
                    obj = self.socket.recv(1024)
                    header_size = struct.unpack('i', obj)[0]
                    if header_size==0:
                        print("33[31;1mfile does not exist!33[0m")
                    else:
                        filename, file_size, filemd5 = self.recv_file_header(header_size)
                        download_filepath = os.path.join(settings.down_filepath, filename)
                        with open(download_filepath, 'wb') as f:
                            get_size = 0
                            self.write_file(f, get_size, file_size)
                        self.verification_filemd5(filemd5)
            else:
                print("33[31;1muser does not enter file name33[0m")
    
        def ls(self,cmds):
            '''查看当前工作目录,文件列表'''
            print("33[34;1mview current working directory33[0m")
            obj = self.socket.recv(4)
            dir_size = struct.unpack('i',obj)[0]
            recv_size = 0
            recv_bytes = b''
            while recv_size <dir_size:
                temp_bytes = self.socket.recv(settings.max_recv_bytes)
                recv_bytes +=temp_bytes
                recv_size += len(temp_bytes)
            print(recv_bytes.decode('gbk'))
    
        def mkdir(self,cmds):
            '''增加目录
            1,server返回1 增加成功
            2,server返回2 增加失败'''
            print("33[34;1madd working directory33[0m")
            obj = self.socket.recv(4)
            res = struct.unpack('i',obj)[0]
            if res:
                print('33[31;1mCongratulations add directory success33[0m')
            else:
                print('33[31;1mSorry add directory failed33[0m')
    
        def cd(self,cmds):
            '''切换目录'''
            print("33[34;1mSwitch working directory33[0m")
            if len(cmds) >1:
                obj = self.socket.recv(4)
                res = struct.unpack('i', obj)[0]
                if res:
                    print('33[31;1mCongratulations switch directory success33[0m')
                else:
                    print('33[31;1mSorry switch directory failed33[0m')
            else:
                print("33[31;1muser does not enter file name33[0m")
    
        def remove(self,cmds):
            '''表示删除文件或空文件夹'''
            print("33[34;1mRemove working directory33[0m")
            obj = self.socket.recv(4)
            res = struct.unpack('i', obj)[0]
            if res:
                print('33[31;1mCongratulations remove success33[0m')
            else:
                print('33[31;1mSorry remove directory failed33[0m')
    
        def open_sendfile(self,file_size,recv_size =0):
            '''打开要上传的文件(由于本程序上传文件的原理是先读取本地文件,再写到上传地址的文件)'''
    
            with open(self.file_path, 'rb') as f:
                # send_bytes = b''
                # send_size = 0
                f.seek(recv_size)
                while True:
                    data = f.read(1024)
                    if data:
                        self.socket.send(data)
                        obj = self.socket.recv(4)
                        recv_size = struct.unpack('i', obj)[0]
                        self.progress_bar(2, recv_size, file_size)
                    else:
                        break
            success_state = struct.unpack('i', self.socket.recv(4))[0]
            if success_state:
                print('33[31;1mCongratulations upload success33[0m')
            else:
                print('33[31;1mSorry upload directory failed33[0m')
    
        def put_situation(self,file_size,condition=0):
            '''上传的时候有两种情况,文件已经存在,文件不存在'''
            quota_state= struct.unpack('i', self.socket.recv(4))[0]
            if quota_state:
                if condition:
                    obj = self.socket.recv(4)
                    recv_size = struct.unpack('i', obj)[0]
                    self.open_sendfile(file_size,recv_size)
                else:
                    self.open_sendfile(file_size)
            else:
                print('33[31;1mSorry exceeding user quotas33[0m')
    
        def put(self,cmds):
            """往server端登录的用户目录下上传文件
            """
            if len(cmds) > 1:
                filename = cmds[1]
                self.file_path = os.path.join(settings.upload_filepath, filename)
                if os.path.isfile(self.file_path):  # 如果文件存在,支持断电续传
                    self.socket.send(struct.pack('i', 1))
                    file_size = os.path.getsize(self.file_path)
                    header_dic = {
                        'filename': os.path.basename(filename),
                        'file_md5': self.getfile_md5(),
                        'file_size': file_size
                    }
                    header_bytes = pickle.dumps(header_dic)
                    self.socket.send(struct.pack('i', len(header_bytes)))
                    self.socket.send(header_bytes)
                    state = struct.unpack('i', self.socket.recv(4))[0]
                    if state:  #已经存在
                        has_state = struct.unpack('i', self.socket.recv(4))[0]
                        if has_state:
                            self.put_situation(file_size, 1)
                        else:  # 存在的大小 和文件大小一致 不必再传
                            print("33[31;1mfile already does exist!33[0m")
                    else:  # 第一次传
                            self.put_situation(file_size)
                else:  # 文件不存在
                    print("33[31;1mfile does not exist!33[0m")
                    self.socket.send(struct.pack('i', 0))
            else:
                print("33[31;1muser does not enter file name33[0m")
    
        def get_recv(self):
            '''从client端接受发来的数据'''
            return pickle.loads(self.socket.recv(settings.max_recv_bytes))
    
        def login(self):
            '''
            登陆函数,当登陆失败超过三次,则退出
            用户密码发送到server短
            接受server端返回的信息,如果成功返回1,失败返回0
            :return: 如果用户账号密码正确,则返回用户数据的字典
            '''
            retry_count = 0
            while retry_count <3:
                username = input('33[34;1mplease input Username:33[0m').strip()
                if not username:
                    continue
                password = input('33[34;1mplease input Password:33[0m').strip()
                user_dic = {
                    'username':username,
                    'password':password
                }
                #将用户信息发送到客户端,然后接受客户端的数据
                data = pickle.dumps(user_dic)
                self.socket.send(pickle.dumps(user_dic))
                #为了防止出现黏包问题,所以先解压报头,读取报头,再读数据
                obj = self.socket.recv(4)
                res = struct.unpack('i',obj)[0]
                #此处,如果返回的是代码4001,则成功 4002则失败
                if res:
                    print("33[32;1m-----------------welcome to ftp client-------------------33[0m")
                    user_info_dic = self.get_recv()
                    recv_username = user_info_dic['username']
                    return True
                else:
                    print("33[31;1mAccount or Passwordoes not correct!33[0m")
            retry_count +=1
    
        def execute(self):
            '''
            执行,或者实施
            :return:
            '''
            if self.login():
                while True:
                    try:
                        self.help_info()
                        inp = input("Please input a command>>>").strip()
                        if not inp:
                            continue
                        self.socket.send(inp.encode(settings.coding))
                        cmds = inp.split()
                        if hasattr(self, cmds[0]):
                            func = getattr(self, cmds[0])
                            func(cmds)
                            break
                        else:
                            print('33[31;1mNo such command ,please try again33[0m')
                    except Exception as e:  # server关闭了
                        print('33[31;1m%s33[0m'%e)
                        break
    
        def help_info(self):
            print ('''33[34;1m
                  get + (文件名)    表示下载文件
                  put + (文件名)    表示上传文件
                  ls                 表示查询当前目录下的文件列表(只能访问自己的文件列表) 
                  mkdir + (文件名)  表示创建文件夹  
                  cd + (文件名)     表示切换目录(只能在自己的文件列表中切换)
                  remove + (文件名) 表示删除文件或空文件夹
            33[0m''')
    

      

  • 相关阅读:
    leetcode 150 逆波兰表达式求值
    leetcode 15 三数之和
    leetcode 12题 数字转罗马数字
    leetcode 134 加油站问题
    socket编程之多次收发数据
    socket编程
    random实现验证码功能
    ECMAScript运算符
    JavaScript数据类型
    window对象
  • 原文地址:https://www.cnblogs.com/wj-1314/p/8707787.html
Copyright © 2020-2023  润新知