设为首页 收藏本站
查看: 861|回复: 0

[经验分享] python 多用户在线的FTP程序

[复制链接]

尚未签到

发表于 2018-8-10 11:03:11 | 显示全部楼层 |阅读模式
#!/usr/bin/env python  
# -*- coding:utf-8 -*-
  
# filename:server.py
  
import socketserver, json, os, sys, time, shutil, configparser, logging
  
from usermanagement import useropr
  

  
####读取配置文件####
  
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  
config_file = os.path.join(base_dir, 'conf/server.conf')
  
cf = configparser.ConfigParser()
  
cf.read(config_file)
  
####设定日志目录####
  
if os.path.exists(cf.get('log', 'logfile')):
  
    logfile = cf.get('log', 'logfile')
  
else:
  
    logfile = os.path.join(base_dir, 'log/server.log')
  
####设定用户上传文件目录####
  
if os.path.exists(cf.get('upload', 'upload_dir')):
  
    file_dir = cf.get('upload', 'upload_dir')
  
else:
  
    file_dir = os.path.join(base_dir, 'user_files')
  

  
####设置日志格式###
  
logging.basicConfig(level=logging.INFO,
  
                    format='%(asctime)s %(levelname)s %(message)s',
  
                    datefmt='%Y-%m-%d    %H:%M:%S',
  
                    filename=logfile,
  
                    filemode='a+')
  

  

  
def TimeStampToTime(timestamp):  ####输入timestamp格式化输出时间,输出格式如:2017-09-16 16:32:35
  
    timeStruct = time.localtime(timestamp)
  
    return time.strftime('%Y-%m-%d %H:%M:%S', timeStruct)
  

  

  
def ProcessBar(part, total):  ####进度条模块,运行会导致程序变慢
  
    if total != 0:
  
        i = round(part * 100 / total)
  
        sys.stdout.write(
  
            '[' + '>' * i + '-' * (100 - i) + ']' + str(i) + '%' + ' ' * 3 + str(part) + '/' + str(total) + '\r')
  
        sys.stdout.flush()
  
        # if part == total:
  
        #     print()
  

  

  
class MyTCPHandler(socketserver.BaseRequestHandler):
  

  
    def put(self, *args):  ####接收客户端文件
  
        # self.request.send(b'server have been ready to receive')    ####发送ACK
  
        cmd_dict = args[0]
  
        filename = os.path.basename(cmd_dict['filename'])  ####传输进来的文件名可能带有路径,将路径去掉
  
        filesize = cmd_dict['filesize']
  
        filemd5 = cmd_dict['filemd5']
  
        override = cmd_dict['override']
  
        receive_size = 0
  
        file_path = os.path.join(self.position, filename)
  
        if override != 'True' and os.path.exists(file_path):  ####检测文件是否已经存在
  
            self.request.send(b'file have exits, do nothing!')
  
        else:
  
            if os.path.isfile(file_path):  ####如果文件已经存在,先删除,再计算磁盘空间大小
  
                os.remove(file_path)
  
            current_size = self.du()  ####调用du查看用户磁盘空间大小,但是du命令的最后会发送一个结果信息给client,会和前面和后面的信息粘包,需要注意
  
            self.request.recv(1024)  ####接收客户端ack信号,防止粘包,代号:P01
  
            print(self.user_spacesize, current_size, filesize)
  
            if self.user_spacesize >= current_size + filesize:
  
                self.request.send(b'begin')  ####发送开始传输信号
  
                fk = open(file_path, 'wb')
  
                while filesize > receive_size:
  
                    if filesize - receive_size > 1024:
  
                        size = 1024
  
                    else:
  
                        size = filesize - receive_size
  
                    data = self.request.recv(size)
  
                    fk.write(data)
  
                    receive_size += len(data)
  
                    # print(receive_size,len(data))   ####打印每次接收的数据
  
                    # ProcessBar(receive_size, filesize)  ####服务端进度条,不需要可以注释掉
  

  
                fk.close()
  
                receive_filemd5 = os.popen('md5sum %s' % file_path).read().split()[0]
  
                print('\r\n', file_path, 'md5:', receive_filemd5, '原文件md5:', filemd5)
  
                if receive_filemd5 == filemd5:
  
                    self.request.send(b'file received successfully!')
  
                else:
  
                    self.request.send(b'Error, file received have problems!')
  
            else:
  
                self.request.send(
  
                    b'Error, disk space do not enough! Nothing done! Total: %d, current: %d, rest:%d, filesize:%d' % (
  
                    self.user_spacesize, current_size, self.user_spacesize - current_size, filesize))
  

  
    def get(self, *args):  ####发送给客户端文件
  
        # print('get receive the cmd',args[0])
  
        filename = args[0]['filename']
  
        print(filename)
  
        # self.request.send(b'server have been ready to send')  ####发送ACK
  
        file_path = os.path.join(self.position, filename)
  
        if os.path.isfile(file_path):
  
            filesize = os.path.getsize(file_path)
  
            ####直接调用系统命令取得MD5值,如果使用hashlib,需要写open打开文件-》read读取文件(可能文件大会很耗时)-》m.update计算三部,代码量更多,效率也低
  
            filemd5 = os.popen('md5sum %s' % file_path).read().split()[0]
  
            msg = {
  
                'action': 'get',
  
                'filename': filename,
  
                'filesize': filesize,
  
                'filemd5': filemd5,
  
                'override': 'True'
  
            }
  
            print(msg)
  
            self.request.send(json.dumps(msg).encode('utf-8'))
  

  
            '''接下来发送文件给客户端'''
  
            self.request.recv(1024)  ####接收ACK信号,下一步发送文件
  
            fk = open(file_path, 'rb')
  
            send_size = 0
  
            for line in fk:
  
                send_size += len(line)
  
                self.request.send(line)
  
                # ProcessBar(send_size, filesize)     ####服务端进度条,不需要可以注释掉
  
            else:
  
                print('文件传输完毕')
  
                fk.close()
  

  
        else:
  
            print(file_path, '文件未找到')
  
            self.request.send(json.dumps('Filenotfound').encode('utf-8'))
  

  
    def newput(self, *args):  ####接收客户端文件,具有断点续传功能
  
        # self.request.send(b'server have been ready to receive')    ####发送ACK
  
        cmd_dict = args[0]
  
        filename = os.path.basename(cmd_dict['filename'])  ####传输进来的文件名可能带有路径,将路径去掉
  
        filesize = cmd_dict['filesize']
  
        filemd5 = cmd_dict['filemd5']
  
        override = cmd_dict['override']
  
        receive_size = 0
  
        file_path = os.path.join(self.position, filename)
  
        print(file_path,os.path.isdir(file_path))
  
        if override != 'True' and os.path.exists(file_path):  ####检测文件是否已经存在
  
            if os.path.isdir(file_path):
  
                self.request.send(b'file have exits, and is a directory, do nothing!')
  
            elif os.path.isfile(file_path):
  
                self.request.send(b'file have exits, do nothing!')
  
                resume_signal = self.request.recv(1024)     ####接收客户端发来的是否从文件断点续传的信号
  
                if resume_signal == b'ready to resume from break point':           ####执行断点续传功能
  
                    exits_file_size = os.path.getsize(file_path)
  
                    current_size = self.du()
  
                    time.sleep(0.5) ####防止粘包
  
                    print('用户空间上限:%d, 当前已用空间:%d, 已存在文件大小:%d, 上传文件大小:%d ' % (self.user_spacesize,current_size,exits_file_size,filesize))
  
                    if self.user_spacesize >= (current_size - exits_file_size + filesize):  ####判断剩余空间是否足够
  
                        if exits_file_size < filesize:
  
                            receive_size = exits_file_size
  
                            print('服务器上已存在的文件大小为:',exits_file_size)
  
                            msg = {
  
                                'state': True,
  
                                'position': exits_file_size,
  
                                'content': 'ready to receive file'
  
                            }
  
                            self.request.send(json.dumps(msg).encode('utf-8'))
  
                            fk = open(file_path, 'ab+')
  
                            while filesize > receive_size:
  
                                if filesize - receive_size > 1024:
  
                                    size = 1024
  
                                else:
  
                                    size = filesize - receive_size
  
                                data = self.request.recv(size)
  
                                fk.write(data)
  
                                receive_size += len(data)
  
                                # print(receive_size,len(data))   ####打印每次接收的数据
  
                                # ProcessBar(receive_size, filesize)  ####服务端进度条,不需要可以注释掉
  

  
                            fk.close()
  
                            receive_filemd5 = os.popen('md5sum %s' % file_path).read().split()[0]
  
                            print('\r\n', file_path, 'md5:', receive_filemd5, '原文件md5:', filemd5)
  
                            if receive_filemd5 == filemd5:
  
                                self.request.send(b'file received successfully!')
  
                            else:
  
                                self.request.send(b'Error, file received have problems!')
  

  
                        else:       ####如果上传的文件小于当前服务器上的文件,则为同名但不同文件,不上传。实际还需要增加其他判断条件,判断是否为同一文件。
  
                            msg = {
  
                                'state': False,
  
                                'position': '',
  
                                'content': 'Error, file mismatch, do nothing!'
  
                            }
  
                            self.request.send(json.dumps(msg).encode('utf-8'))
  
                    else:       ####如果续传后的用户空间大于上限,拒接续传
  
                        msg = {
  
                            'state': False,
  
                            'position':'',
  
                            'content':'Error, disk space do not enough! Nothing done! Total: %d, current: %d, rest:%d, need_size:%d' % (self.user_spacesize, current_size, self.user_spacesize - current_size, filesize - exits_file_size)
  
                        }
  
                        self.request.send(json.dumps(msg).encode('utf-8'))
  
                else:
  
                    pass
  

  
        else:
  
            if os.path.isfile(file_path):  ####如果文件已经存在,先删除,再计算磁盘空间大小
  
                os.remove(file_path)
  
            current_size = self.du()  ####调用du查看用户磁盘空间大小,但是du命令的最后会发送一个结果信息给client,会和前面和后面的信息粘包,需要注意
  
            self.request.recv(1024)  ####接收客户端ack信号,防止粘包,代号:P01
  
            print(self.user_spacesize, current_size, filesize)
  
            if self.user_spacesize >= current_size + filesize:
  
                self.request.send(b'begin')  ####发送开始传输信号
  
                fk = open(file_path, 'wb')
  
                while filesize > receive_size:
  
                    if filesize - receive_size > 1024:
  
                        size = 1024
  
                    else:
  
                        size = filesize - receive_size
  
                    data = self.request.recv(size)
  
                    fk.write(data)
  
                    receive_size += len(data)
  
                    # print(receive_size,len(data))   ####打印每次接收的数据
  
                    # ProcessBar(receive_size, filesize)  ####服务端进度条,不需要可以注释掉
  

  
                fk.close()
  
                receive_filemd5 = os.popen('md5sum %s' % file_path).read().split()[0]
  
                print('\r\n', file_path, 'md5:', receive_filemd5, '原文件md5:', filemd5)
  
                if receive_filemd5 == filemd5:
  
                    self.request.send(b'file received successfully!')
  
                else:
  
                    self.request.send(b'Error, file received have problems!')
  
            else:
  
                self.request.send(
  
                    b'Error, disk space do not enough! Nothing done! Total: %d, current: %d, rest:%d, filesize:%d' % (
  
                    self.user_spacesize, current_size, self.user_spacesize - current_size, filesize))
  

  
    def newget(self, *args):  ####发送给客户端文件,具有断点续传功能
  
        # print('get receive the cmd',args[0])
  
        filename = args[0]['filename']
  
        remote_local_filesize = args[0]['filesize']
  
        print(filename)
  
        # self.request.send(b'server have been ready to send')  ####发送ACK
  
        file_path = os.path.join(self.position, filename)
  
        if os.path.isfile(file_path):
  
            filesize = os.path.getsize(file_path)
  
            ####直接调用系统命令取得MD5值,如果使用hashlib,需要写open打开文件-》read读取文件(可能文件大会很耗时)-》m.update计算三部,代码量更多,效率也低
  
            filemd5 = os.popen('md5sum %s' % file_path).read().split()[0]
  
            msg = {
  
                'action': 'newget',
  
                'filename': filename,
  
                'filesize': filesize,
  
                'filemd5': filemd5,
  
                'override': 'True'
  
            }
  
            print(msg)
  
            self.request.send(json.dumps(msg).encode('utf-8'))
  

  
            '''接下来发送文件给客户端'''
  
            self.request.recv(1024)  ####接收ACK信号,下一步发送文件
  
            fk = open(file_path, 'rb')
  
            fk.seek(remote_local_filesize,0)
  
            send_size = remote_local_filesize
  
            for line in fk:
  
                send_size += len(line)
  
                self.request.send(line)
  
                # ProcessBar(send_size, filesize)     ####服务端进度条,不需要可以注释掉
  
            else:
  
                print('文件传输完毕')
  
                fk.close()
  

  
        else:
  
            print(file_path, '文件未找到')
  
            self.request.send(json.dumps('Filenotfound').encode('utf-8'))
  

  
    def pwd(self, *args):
  
        current_position = self.position
  
        result = current_position.replace(file_dir, '')  ####截断目录信息,使用户只能看到自己的家目录信息
  
        self.request.send(json.dumps(result).encode('utf-8'))
  

  
    def ls(self, *args):  ####列出当前目录下的所有文件信息,类型,字节数,生成时间。
  
        result = ['%-20s%-7s%-10s%-23s' % ('filename', 'type', 'bytes', 'creationtime')]  ####信息标题
  
        for f in os.listdir(self.position):
  
            type = 'unknown'
  
            f_abspath = os.path.join(self.position, f)  ####给出文件的绝对路径,不然程序会找不到文件
  
            if os.path.isdir(f_abspath):
  
                type = 'd'
  
            elif os.path.isfile(f_abspath):
  
                type = 'f'
  
            result.append('%-20s%-7s%-10s%-23s' % (
  
            f, type, os.path.getsize(f_abspath), TimeStampToTime(os.path.getctime(f_abspath))))
  
        self.request.send(json.dumps(result).encode('utf-8'))
  

  
    def du(self, *args):
  
        '''统计纯文件和目录占用空间大小,结果小于在OS上使用du -s查询,因为有一些(例如'.','..')隐藏文件未包含在内'''
  
        totalsize = 0
  
        if os.path.isdir(self.position):
  
            dirsize, filesize = 0, 0
  
            for root, dirs, files in os.walk(self.position):
  
                for d_item in dirs:  ####计算目录占用空间,Linux中每个目录占用4096bytes,实际上也可以按这个值来相加
  
                    if d_item != '':
  
                        dirsize += os.path.getsize(os.path.join(root, d_item))
  
                for f_item in files:  ####计算文件占用空间
  
                    if f_item != '':
  
                        filesize += os.path.getsize(os.path.join(root, f_item))
  
            totalsize = dirsize + filesize
  
            result = 'current directory total sizes: %d' % totalsize
  
        else:
  
            result = 'Error,%s is not path ,or path does not exist!' % self.position
  
        self.request.send(json.dumps(result).encode('utf-8'))
  
        return totalsize
  

  
    def cd(self, *args):
  
        print(*args)
  
        user_homedir = os.path.join(file_dir, self.username)
  
        cmd_dict = args[0]
  
        error_tag = False
  
        '''判断目录信息'''
  
        if cmd_dict['dir'] == '':
  
            self.position = user_homedir
  
        elif cmd_dict['dir'] == '.' or cmd_dict['dir'] == '/' or '//' in cmd_dict['dir']:  ####'.','/','//','///+'匹配
  
            pass
  
        elif cmd_dict['dir'] == '..':
  
            if user_homedir != self.position and user_homedir in self.position:  ####当前目录不是家目录,并且当前目录是家目录下的子目录
  
                self.position = os.path.dirname(self.position)
  
        elif '.' not in cmd_dict['dir'] and os.path.isdir(
  
                os.path.join(self.position, cmd_dict['dir'])):  ####'.' not in cmd_dict['dir'] 防止../..输入
  
            self.position = os.path.join(self.position, cmd_dict['dir'])
  
        else:
  
            error_tag = True
  
        '''发送结果'''
  
        if error_tag:
  
            result = 'Error,%s is not path here, or path does not exist!' % cmd_dict['dir']
  
            self.request.send(json.dumps(result).encode('utf-8'))
  
        else:
  
            self.pwd()
  

  
    def mv(self,*args):
  
        print(*args)
  
        try:
  
            objectname = args[0]['objectname']
  
            dstname = args[0]['dstname']
  
            abs_objectname = os.path.join(self.position,objectname)
  
            abs_dstname = os.path.join(self.position, dstname)
  
            print(abs_objectname,abs_dstname,os.path.isfile(abs_objectname),os.path.isdir(abs_objectname),os.path.isdir(abs_dstname))
  
            result = ''
  
            if os.path.isfile(abs_objectname):
  
                if os.path.isdir(abs_dstname) or not os.path.exists(abs_dstname):
  
                    shutil.move(abs_objectname, abs_dstname)
  
                    print('moving success')
  
                    result = 'moving success'
  

  
                elif os.path.isfile(abs_dstname):
  
                    print('moving cancel, file has been exits')
  
                    result = 'moving cancel, file has been exits'
  

  
            elif os.path.isdir(abs_objectname):
  
                if os.path.isdir(abs_dstname) or not os.path.exists(abs_dstname):
  
                    shutil.move(abs_objectname, abs_dstname)
  
                    print('moving success')
  
                    result = 'moving success'
  

  
                elif os.path.isfile(abs_dstname):
  
                    print('moving cancel, %s is file' % dstname)
  
                    result = 'moving cancel, %s is file' % dstname
  

  
            else:
  
                print('nothing done')
  
                result = 'nothing done'
  
            self.request.send(json.dumps(result).encode('utf-8'))
  

  
        except Exception as e:
  
            print(e)
  
            result = 'moving fail,' + e
  
            self.request.send(json.dumps(result).encode('utf-8'))
  

  
    def mkdir(self, *args):  ####创建目录
  
        try:
  
            dirname = args[0]['dirname']
  
            if dirname.isalnum():  ####判断文件是否只有数字和字母
  
                if os.path.exists(os.path.join(self.position, dirname)):
  
                    result = '%s have existed' % dirname
  
                else:
  
                    os.mkdir(os.path.join(self.position, dirname))
  
                    result = '%s created succes' % dirname
  
            else:
  
                result = 'Illegal character %s, dirname can only by string and num here.' % dirname
  
        except TypeError:
  
            result = 'please input dirname'
  
        self.request.send(json.dumps(result).encode('utf-8'))
  

  
    def rm(self, *args):  ####删除文件
  
        filename = args[0]['filename']
  
        confirm = args[0]['confirm']
  
        file_abspath = os.path.join(self.position, filename)
  
        if os.path.isfile(file_abspath):
  
            if confirm == True:
  
                os.remove(file_abspath)
  
                result = '%s have been delete.' % filename
  
            else:
  
                result = 'Not file deleted'
  
        elif os.path.isdir(file_abspath):
  
            result = '%s is a dir, plsese using rmdir' % filename
  
        else:
  
            result = 'File %s not exist!' % filename
  
        self.request.send(json.dumps(result).encode('utf-8'))
  

  
    def rmdir(self, *args):  ###删除目录
  
        dirname = args[0]['dirname']
  
        confirm = args[0]['confirm']
  
        file_abspath = os.path.join(self.position, dirname)
  
        if '.' in dirname or '/' in dirname:  ####不能跨目录删除
  
            result = 'should not rmdir %s this way' % dirname
  
        elif os.path.isdir(file_abspath):
  
            if confirm == True:
  
                shutil.rmtree(file_abspath)
  
                result = '%s have been delete.' % dirname
  
            else:
  
                result = 'Not file deleted'
  
        elif os.path.isfile(file_abspath):
  
            result = '%s is a file, not directory deleted' % dirname
  
        else:
  
            result = 'directory %s not exist!' % dirname
  
        self.request.send(json.dumps(result).encode('utf-8'))
  

  
    def auth(self):
  
        self.data = json.loads(self.request.recv(1024).decode('utf-8'))
  
        print(self.data)
  
        recv_username = self.data['username']
  
        recv_passwd = self.data['passwd']
  
        query_result = useropr.query_user(recv_username)
  
        print(query_result)
  
        if query_result == None:
  
            self.request.send(b'user does not exits')
  
        elif query_result['content']['passwd'] == recv_passwd:
  
            self.request.send(b'ok')
  
            return query_result  ####返回查询结果
  
        elif query_result['content']['passwd'] != recv_passwd:
  
            self.request.send(b'password error')
  
        else:
  
            self.request.send(b'unknown error')
  

  
    def handle(self):  ####处理类,调用以上方法
  
        # self.position = file_dir
  
        # print(self.position)
  
        auth_tag = False
  
        while auth_tag != True:
  
            auth_result = self.auth()  ####用户认证,如果通过,返回用户名,不通过为None
  
            print('the authentication result is:', auth_result)
  
            if auth_result != None:
  
                self.username = auth_result['content']['username']
  
                self.user_spacesize = auth_result['content']['spacesize']
  
                auth_tag = True
  
        print(self.username, self.user_spacesize)
  
        user_homedir = os.path.join(file_dir, self.username)
  
        if os.path.isdir(user_homedir):
  
            self.position = user_homedir  ####定锚,用户家目录
  
            print(self.position)
  
            while True:
  
                print('当前连接:', self.client_address)
  
                self.data = self.request.recv(1024).strip()
  
                print(self.data)
  
                logging.info(self.client_address)
  
                if len(self.data) == 0:
  
                    print('客户端断开连接')
  
                    break  ####检查发送来的命令是否为空
  
                cmd_dict = json.loads(self.data.decode('utf-8'))
  
                action = cmd_dict['action']
  
                logging.info(cmd_dict)
  
                if hasattr(self, action):
  
                    func = getattr(self, action)
  
                    func(cmd_dict)
  
                else:
  
                    print('未支持指令:', action)
  
                logging.info('current directory:%s' % self.position)
  

  

  
if __name__ == '__main__':
  
    ip, port = '0.0.0.0', 9999
  
    server = socketserver.ThreadingTCPServer((ip, port), MyTCPHandler)
  
    server.serve_forever()

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-549592-1-1.html 上篇帖子: python学习——安装部署 下篇帖子: python 九九乘法待完善
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表