天成1 发表于 2018-8-10 11:03:11

python 多用户在线的FTP程序

#!/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
  
      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()
  
                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)
  
      filename = args['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()
  
            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
  
      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()
  
                            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()
  
                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)
  
      filename = args['filename']
  
      remote_local_filesize = args['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()
  
            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
  
      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['objectname']
  
            dstname = args['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['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['filename']
  
      confirm = args['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['dirname']
  
      confirm = args['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]
查看完整版本: python 多用户在线的FTP程序