• Python实现ftp服务(1)


    bin目录的文件:

    #client
    #-*- Coding:utf-8 -*-
    import os,sys
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    #获取该项目根目录的完整路径
    sys.path.append(BASE_DIR)
    
    from core import ftp_client
    fc = ftp_client.Ftp_client()
    
    start_client.py
    #server
    #-*- Coding:utf-8 -*-
    import os,sys
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    sys.path.append(BASE_DIR)
    
    from core import ftp_server
    fs = ftp_server.Ftp_server()
    
    start_server.py

    conf目录下

    #setting
    #-*- Coding:utf-8 -*-
    import os,sys,socket
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    sys.path.append(BASE_DIR)
    #注意这个方法是在程序运行的时候起作用,无法保存。
    
    #IP和端口信息
    IP_PORT = ("localhost",6969)
    
    #用户数据文件
    USER_FILE = os.path.join(BASE_DIR,'db\user_info')
    #用户文件目录
    USER_HOME = BASE_DIR
    
    setting.py
    #-*- Coding:utf-8 -*-
    import sys,os,socket,hashlib,time,json
    from conf import setting
    from core import users
    class Ftp_client(object):
        '''
        FTP客服端
        '''
        def __init__(self):
            '''
            构造函数
            '''
            self.client = setting.socket.socket()
            self.client.connect(setting.IP_PORT)
            self.help_info = """33[33;1m
                        请用'put'+'空格'+'文件名'的格式下载文件
                        请用'get'+'空格'+'文件名'的格式上传文件
                        请用'cd'+'空格'+'目录名'的格式进入家目录下的子文件夹
                        请用'cd'+'空格'+'..'的格式返回上级目录
                        请用'mkdir'+'空格'+'目录名'的格式创建家目录的文件夹
                        输入'dir'+'空格'+'home'查看用户家目录
                        输入'dir'+'空格'+'server'查看用户服务端家目录
                33[0m"""
            if self.auth():
                self.start()
    
        def auth(self):
            '''
            用户登录认证函数
            1、用户输入账号密码
            2、序列化用户信息字典发送给服务端
            3、接收服务端用户登录认证消息
            4、认证成功返回True给构造函数
            5、用户进入start()函数进行指令操作
            :return:
            '''
            while True:
                username = input("请输入账户名>>>:").strip()
                password = input('请输入用户密码>>>:').strip()
                #auth = 'auth %s %s'%(username,password)
                mesg = {
                    "action":'auth',
                    "username":username,
                    "password":password
                }
                self.client.send(json.dumps(mesg).encode())
                self.user_obj = users.Users(username)
                back_res = self.client.recv(1024).decode()
                if back_res == 'ok':
                    print("33[32;1m认证成功33[0m")
                    user = self.user_obj.get_user()
                    self.user_name = user["username"]
                    self.user_type = user["type"]
                    self.user_path = user['home']
                    self.disk_quota = user["disk_quota"]
                    self.pwd_path = os.path.join(setting.USER_HOME,self.user_path,"user_home") #定义一个默认路径
                    return True
                elif back_res == "302":
                    print("33[31;1m密码错误33[0m")
                elif back_res == "301":
                    print("33[31;1m该用户已登录33[0m")
                else:
                    print("33[31;1m用户不存在33[0m")
    
        def start(self):
            '''
            用户操作函数
            1、用户输入操作指令
            2、判断操作指令是否有效
            3、反射指令
            :return:
            '''
            while True:
                user_inport = input("%s>>>:"%(self.user_name)).strip()
                if len(user_inport) == 0 :continue
                user_inport = user_inport.split()
                if user_inport[0] == 'q':
                    break
                if hasattr(self,user_inport[0]):
                    func = getattr(self,user_inport[0])
                    func(user_inport)
                else:
                    print("33[31;1m请输入有效指令:33[0m",self.help_info)
                    continue
    
        def put(self,cmd):
            '''
            下载服务端文件函数
            1、接收服务端回调信息(305 = 服务端文件不存在或下载文件大小)
            2、判断磁盘配额和文件大小
            3、接收服务端回调信息
            4、开始接收文件并打印进度条
            5、加密认证
            6、重新计算磁盘配额  调用Users类中update_disk_quota()方法将最新磁盘配额参数重新写入用户文件中
            :param cmd:
            :return:
            '''
            if len(cmd) < 2:
                print("33[31;1m请输入有效指令:33[0m", self.help_info)
            else:
                '''
                下载服务端文件
                '''
                mesg = {
                    "action": cmd[0],
                    "file_name": cmd[1],
                    "disk_quota": self.disk_quota
                }
                self.client.send(json.dumps(mesg).encode())
                server_back = self.client.recv(1024).decode()
                print("33[32;1m收到服务器回调:33[0m",server_back)
                if server_back == '305':
                    print("33[31;1m文件不存在33[0m")
                else:
                    file_total_size = int(server_back)
                    print("33[32;1m下载文件总大小:33[0m", file_total_size)
                    print("33[32;1m磁盘配额还剩:%sM33[0m" % mesg["disk_quota"])
                    if file_total_size >= mesg["disk_quota"] * (2 ** 20):
                        print('33[31;1m磁盘配额不够无法下载文件33[0m')
                    else:
                        revered_size = 0
                        # file_path = os.path.join(setting.USER_HOME,self.user_path,"user_home",cmd[1])
                        file_path = os.path.join(self.pwd_path,cmd[1])
                        print('in the put_pwd_path:',file_path)
                        self.client.send(b"ok")
                        self.m = hashlib.md5()
                        i = 0
                        with open(file_path,'wb') as f:
                            while revered_size < file_total_size:
                                if file_total_size - revered_size < 1024:
                                    size = file_total_size - revered_size
                                else:
                                    size = 1024
                                data = self.client.recv(size)
                                revered_size += len(data)
                                '''
                                打印进度条
                                '''
                                str1 = "已接受 %sByte"%revered_size
                                str2 = '%s%s'%(round((revered_size/file_total_size)*100,2),'%')
                                str3 = '[%s%s]'%('*'*i,str2)
                                sys.stdout.write('33[32;1m
    %s%s33[0m'%(str1,str3))
                                sys.stdout.flush()
                                i += 2
                                time.sleep(0.3)
                                '''
                                加密认证
                                '''
                                self.m.update(data)
                                f.write(data)
                            self.encryption()
                            '''
                            磁盘配额
                            '''
                            new_disk_quota = round((mesg["disk_quota"] * (2 ** 20) - file_total_size) / (2 ** 20), 2)
                            # mesg["disk_quota"]* (2 ** 20)  将用户文件中磁盘参数转成相应的Bytes数值
                            self.user_obj.update_disk_quota(new_disk_quota)
                            print("33[32;1m磁盘配额还剩:%sM33[0m"%new_disk_quota)
    
        def get(self,cmd):
            '''
            客户端上传文件至服务端函数
            1、判断指令格式是否正确
            2、上传文件或文件路径是否有效和存在
            3、获取文件大小
            4、判断磁盘配额是否大于文件大小
            5、获取服务端上传文件回调请求
            6、发送文件并打印进度条
            7、加密认证
            8、重新计算磁盘配额  调用Users类中update_disk_quota()方法将最新磁盘配额参数重新写入用户文件中
            :param cmd:
            :return:
            '''
            if len(cmd) < 2:
                print("33[31;1m请输入有效指令:33[0m", self.help_info)
            else:
                '''
                上传文件
                '''
                file_path = os.path.join(self.pwd_path,cmd[1])
                if os.path.isfile(file_path):
                    file_total_size = os.stat(file_path).st_size
                    mesg = {
                        "action": cmd[0],
                        "file_name": cmd[1],
                        "disk_quota": self.disk_quota,
                        "file_size" : file_total_size
                    }
                    print("33[32;1m磁盘配额还剩:%sM33[0m" % mesg["disk_quota"])
                    if file_total_size >= mesg["disk_quota"]*(2**20):
                        print("33[31;1m磁盘配额不够无法上传文件33[0m")
                    else:
                        self.client.send(json.dumps(mesg).encode())
                        print("33[32;1m上传文件总大小:33[0m", file_total_size)
                        self.client.recv(1024)
                        print("开始发送文件")
                        self.m = hashlib.md5()
                        send_size = 0
                        i = 0
                        with open(file_path,'rb')as f:
                            while send_size < file_total_size:
                                if file_total_size - send_size <1024:
                                    size = file_total_size - send_size
                                    data = f.read(size)
                                    send_size += len(data)
                                else:
                                    data = f.read(1024)
                                    send_size += len(data)
                                self.client.send(data)
                                '''
                                打印进度条
                                '''
                                str1 = "已上传 %sByte:" %send_size
                                str2 = '%s%s' % (round((send_size / file_total_size) * 100, 2), '%')
                                str3 = '[%s%s]' % ('*'*i, str2)
                                sys.stdout.write('33[32;1m
    %s%s33[0m' % (str1, str3))
                                sys.stdout.flush()
                                i += 2
                                time.sleep(0.3)
                                '''
                                文件加密
                                '''
                                self.m.update(data)
                        self.encryption()
                        '''
                        磁盘配额
                        '''
                        new_disk_quota = round((mesg["disk_quota"]*(2**20) - file_total_size)/(2**20),2)
                        self.user_obj.update_disk_quota(new_disk_quota)
                        print("33[32;1m磁盘配额还剩:%sM33[0m"%new_disk_quota)
                else:
                    print("33[31;1m文件不存在33[0m")
    
        def encryption(self):
            '''
            文件加密函数
            1、判断用户是否需要加密
            2、取消加密发送'401'信息给服务端
            3、确认加密发送'400'信息给服务端
            4、接收服务端文件加密信息
            5、判断客户端和服务端文件加密信息是否一致
            :return:
            '''
            encryption = input("
    文件已接收是否需要加密认证...按q取消加密>>>")
            if encryption != 'q':
                self.client.send(b'400')
                print('33[32;1m确认加密33[0m')
                file_md5 = self.m.hexdigest()
                server_back_md5 = self.client.recv(1024).decode()
                print("33[32;1m本地文件加密:%s
    服务端文件加密:%s33[0m" % (file_md5, server_back_md5))
                if file_md5 == server_back_md5:
                    print("33[32;1m加密认证成功33[0m")
                else:
                    print("加密认证失败")
            else:
                self.client.send(b'401')
                print("33[32;1m
    已取消加密.文件接收成功33[0m")
    
        def dir(self,cmd):
            '''
            查看根目录下文件信息函数
            1、dir_home 查看用户本地文件内容
            2、dir_server 查看用户服务器文件内容
            3、接收服务端指令文件大小
            4、发送接收目录信息指令
            5、接收目录信息
            :param cmd:
            :return:
            '''
            if len(cmd) < 2:
                print("33[31;1m请输入有效指令:33[0m", self.help_info)
            else:
                if cmd[1] == "home" or cmd[1] == 'server':
                    mesg = {
                        "action":cmd[0],
                        "object":cmd[1]
                    }
                    self.client.send(json.dumps(mesg).encode())
                    server_back = self.client.recv(1024).decode()
                    print('33[32;1m收到服务端回调指令大小:33[0m',server_back)
                    self.client.send("ok".encode())
                    revered_size = 0
                    revered_data = b''
                    while revered_size < int(server_back):
                        data = self.client.recv(1024)
                        revered_data += data
                        revered_size = len(data)
                        print('33[32;1m实际收到指令大小:33[0m',revered_size)
                    else:
                        print(revered_data.decode())
                else:
                    print("33[31;1m请输入有效指令:33[0m", self.help_info)
    
        def mkdir(self,cmd):
            '''
            添加目录文件函数
            1、判断指令是否正确
            2、先获取当前路径
            3、判断所添加目录是否已存在
            4、使用os.mkdir()函数添加新目录
            5、新目录添加成功
            :param cmd:
            :return:
            '''
            if len(cmd) < 2:
                print("33[31;1m请输入有效指令:33[0m", self.help_info)
            else:
                # file_path = os.path.join(setting.USER_HOME,self.user_path,"user_home",cmd[1])
                file_path = os.path.join(self.pwd_path,cmd[1])
                print("当前路径:", file_path)
                if os.path.exists(file_path):
                    print("33[31;1m该目录文件夹已存在33[0m")
                else:
                    os.mkdir(file_path)
                    print("该目录文件夹创建成功")
    
        def cd(self,cmd):
            '''
            CD:移动到指定目录函数
            1、先判断指令是否正确
            2、判断路径是否有效
            3、根据输入做相应操作如:cd ..:移动到上一级目录   cd / :移动到根目录  cd 目录名:移动到指定目录
            4、拆分路径重新拼接新路径
            5、返回self.pwd_path当前所在目录
            :param cmd:
            :return:
            '''
            if len(cmd) < 2:
                print("33[31;1m请输入有效指令:33[0m", self.help_info)
            else:
                if cmd[1] == '..':
                    list = []
                    pwd_path = os.path.join(self.pwd_path)
                    for index in pwd_path.split('\'):  #列表形式拆分当前目录路径以'\'分隔
                        list.append(index)          #将目录路径参数添加至list列表中
                    list[0] = '%s%s'%(list[0],'/')  #将列表第一个元素 E: 字符串拼接成 E:/
                    if list[-1] == "user_home":
                        print("已在根目录下")
                    else:
                        del list[-1]    #删除列表最后个元素也就是上一级目录路径
                        self.pwd_path = ''
                        for item in list:       #重新拼接成新的路径
                            self.pwd_path = os.path.join(self.pwd_path,item)
                        print("当前路径:",self.pwd_path)
                        #print(os.listdir(self.pwd_path))
                elif cmd[1] == '/':
                    self.pwd_path = os.path.join(setting.USER_HOME,self.user_path,"user_home")
                    print("已返回根目录:", self.pwd_path)
                else:
                    pwd_path = os.path.join(self.pwd_path,cmd[1])   #移动到指定目录  cmd[1]目录名
                    if os.path.isdir(pwd_path):
                        #print(os.listdir(pwd_path))
                        self.pwd_path = pwd_path    #返回用户当前路径
                        print("当前路径:", self.pwd_path)
                    else:
                        print("33[31;1m系统找不到指定的路径33[0m")
    
        def pwd(self,cmd):
            '''
            显示当前目录路径
            :param cmd:
            :return:
            '''
            print("当前路径:", self.pwd_path)
            print(os.listdir(self.pwd_path))
    
        def help(self,cmd):
            '''
            帮助文档函数
            :param cmd:
            :return:
            '''
            print(self.help_info)
    
    ftp_client.py
    View client
    #server
    #-*- Coding:utf-8 -*-
    import sys,os,socket,hashlib,socketserver,json,time
    from conf import setting
    from core import users
    class MyServer(socketserver.BaseRequestHandler):
        print('等待链接...')
        '''
        FTP服务端类
        '''
        def auth(self,*args):
            '''
            用户登录认证函数
            1、接收客户端用户字典信息
            2、序列化字典信息
            3、调用Users类中get_user()函数
            4、判断用户是否有效
            5、发送认证信息至客户端
            :param args:
            :return:
            '''
            cmd = args[0]
            self.user_obj = users.Users(cmd['username'])
            auth_user = self.user_obj.get_user()
            if auth_user:
                if auth_user['password'] == cmd["password"]:
                    if auth_user['status'] == 0:
                        self.request.send(b"ok")
                        #self.user_obj.update_status_close()
                        self.user_home = auth_user["home"]
                        self.user_type = auth_user["type"]
                    else:
                        self.request.send(b"301")
                        print("33[31;1m该用户已登录33[0m")
                else:
                    self.request.send(b'302')
                    print("33[31;1m密码错误33[0m")
            else:
                self.request.send(b"300")
                print("33[31;1m用户名不存在33[0m")
    
        def put(self,*args):
            '''
            服务端发送文件给客户端
            1、判断文件是否存在
            2、获取文件总大小
            3、发送文件大小给客户端
            4、接收客户端下载文件请求
            5、开始循环发送文件给客户端
            6、发送完成后调用加密函数
            :param args:
            :return:
            '''
            cmd = args[0]
            file_path = os.path.join(setting.USER_HOME,self.user_home,'server_home',cmd["file_name"])
            if os.path.isfile(file_path):
                file_total_size = os.stat(file_path).st_size
                print("33[32;1m获取文件大小:33[0m",file_total_size)
                self.request.send(str(file_total_size).encode())
                self.request.recv(1024)
                print("开始发送文件")
                self.m = hashlib.md5()
                with open(file_path,'rb') as f:
                    for line in f:
                        self.m.update(line)
                        self.request.send(line)
                    self.encryption()
            else:
                self.request.send(b'305')
                print("文件不存在")
    
        def get(self,*args):
            '''
            服务端接收客户端发送文件函数
            1、接收客户端发送文件大小
            2、发送接收客户端文件请求
            3、开始接收文件
            4、跟踪文件接收并写入相应目录
            5、接收完成后调用加密函数
            :param args:
            :return:
            '''
            cmd = args[0]
            #print(cmd)
            file_path = os.path.join(setting.USER_HOME,self.user_home,"server_home",cmd["file_name"])
            print("33[32;1m收到客户端发送文件大小:33[0m", cmd["file_size"])
            self.request.send(b"ok")
            print("开始接收文件")
            file_total_size = cmd["file_size"]
            rever_size = 0
            self.m = hashlib.md5()
            with open(file_path,'wb') as f:
                while rever_size < file_total_size:
                    if file_total_size - rever_size <1024:
                        size = file_total_size - rever_size
                    else:
                        size = 1024
                    data = self.request.recv(size)
                    rever_size += len(data)
                    self.m.update(data)
                    f.write(data)
                self.encryption()
    
        def encryption(self):
            '''
            加密认证函数
            1、发送确认加密'400'请求
            2、发送服务端文件加密信息至客户端
            :return:
            '''
            client_back = self.request.recv(1024).decode()
            if client_back == "400":
                print("33[32;1m确认加密请求33[0m")
                server_file_md5 = self.m.hexdigest()
                self.request.send(server_file_md5.encode())
                print("33[32;1m服务端文件加密:33[0m", server_file_md5)
            else:
                print("33[32;1m
    已取消加密.客户端文件接收完成33[0m")
    
        def dir(self,*args):
            '''
            服务端查看目录文件信息函数
            1、序列化客户端字典信息
            2、popen()获取目录文件信息
            3、判断目录信息长度
            4、发送目录长度信息至客户端
            5、接收客户端回调请求
            6、发送目录信息至客户端
            :param args:
            :return:
            '''
            cmd = args[0]
            if cmd["object"] == 'home':
                file_name = 'user_home'
            else:
                file_name = 'server_home'
            file_path = os.path.join(setting.USER_HOME,self.user_home,file_name)
            res = os.popen("%s %s"%(cmd["action"],file_path)).read()
            print("in the dir:",res)
            if len(res) == 0:
                res = "has not output"
            self.request.send(str(len(res.encode())).encode())
            self.request.recv(1024)
            self.request.send(res.encode())
    
    
        def handle(self):
            '''
            与客户端交互函数(解析客户端操作指令)
            1、接收客户端链接信息
            2、接收客户端操作指令(action)需序列化
            3、反射操作指令
            :return:
            '''
            while True:
                try:
                    self.data = self.request.recv(1024).strip()
                    print("{}已链接".format(self.client_address))
                    actin_dict = json.loads(self.data.decode())
                    #print(actin_dict)
                    print('in the handle',actin_dict["action"])
                    if hasattr(self,str(actin_dict["action"])):
                        func = getattr(self,str(actin_dict["action"]))
                        func(actin_dict)
                except ConnectionResetError as e:
                    print("%s客户端已断开%s"%(self.client_address,e))
                    #self.user_obj.update_status_open()
                    break
    
    server = socketserver.ThreadingTCPServer((setting.IP_PORT),MyServer)    #支持多用户操作:ThreadingTCPServer
    server.serve_forever()
    
    ftp_server.py
    View Code
    #users
    #-*- Coding:utf-8 -*-
    import os,sys,json
    from conf import setting
    class Users(object):
        '''
        用户类
        '''
        def __init__(self,username):
            self.username = username
            self.user_file = setting.USER_FILE + "\%s.json"%(self.username)
            #print(self.user_file)
            self.users_read = self.read_users()
    
        def read_users(self):
            '''
            读取用户文件信息函数
            1、判断用户文件是否存在(用户是否存在)
            2、遍历用户json文件中内容
            3、返回遍历内容
            :return:
            '''
            if os.path.exists(self.user_file):
                with open(self.user_file, 'r') as f:
                    user_read = eval(f.read())
                    return user_read
    
        def get_user(self):
            '''
            1、判断服务端传过来的用户名参数是否与文件中用户名参数
            2、异常处理:用户名与用户文件参数类型不一直
            :return:
            '''
            #print('in the User_get_user:',user)
            try:
                if self.users_read["username"] == self.username:
                    return self.users_read
            except TypeError as e:
                pass
    
        def update_status_close(self):
            '''
            修改用户登录状态函数
            0-未登录
            1-已登录
            无法重复登录
            :return:
            '''
            with open(self.user_file,'r') as f:
                fr = f.read()
                fd = eval(fr)
    
            with open(self.user_file,'w') as fw:
                res = fr.replace(str(fd['status']),str(1))
                fw.write(res)
    
        def update_status_open(self):
            '''
                    修改用户登录状态函数
                    0-未登录
                    1-已登录
                    无法重复登录
                    :return:
                    '''
            with open(self.user_file, 'r') as f:
                fr = f.read()
                fd = eval(fr)
    
            with open(self.user_file, 'w') as fw:
                res = fr.replace(str(fd['status']), str(0))
                fw.write(res)
    
        def update_disk_quota(self,new_disk_quota):
            '''
            更改用户磁盘配额函数
            :param new_disk_quota:接收客户端新磁盘配额参数
            :return:
            '''
            with open(self.user_file,'r') as f:
                fr = f.read()
                fd = eval(fr)
    
            with open(self.user_file,'w') as fw:
                res = fr.replace(str(fd["disk_quota"]),str(new_disk_quota))
                fw.write(res)
    
    user
    View Code
    {
      "username":"alex",
      "password":"admin",
      "status":0,
      "type":0,
      "home":"home\alex",
      "disk_quota":0.97
    }
    
    alex.json
    View Code
    好好学习,天天向上
  • 相关阅读:
    P2639 [USACO09OCT]Bessie的体重问题Bessie's We…
    P2871 [USACO07DEC]手链Charm Bracelet
    P1983 车站分级
    P1038 神经网络
    P1991 无线通讯网
    P1546 最短网络 Agri-Net
    P1197 [JSOI2008]星球大战
    P1004 方格取数
    P1111 修复公路
    pd_ds 之 hash
  • 原文地址:https://www.cnblogs.com/topass123/p/12917182.html
Copyright © 2020-2023  润新知