• module032简单ftp


    需求


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

    目录结构


    ftp_client
        ├ bin   # 执行文件目录
        |   └ ftp_client.py     # ftp客户端执行程序 
        ├ conf  # 配置文件目录
        |   └ setting.py        # 配置文件。目前主要内容为服务端地址
        ├ core  # 程序核心代码位置
        |   └ main.py           # 主逻辑交互程序    
        └ ftpdownload   # 下载文件存储目录
        
    ftp_server
        ├ bin   # 执行文件目录
        |   └ ftp_server.py     # ftp服务端执行程序 
        ├ conf  # 配置文件目录
        |   └ setting.py        # 配置文件。目前主要内容为服务端地址
        ├ core  # 程序核心代码位置
        |   ├ main.py           # 主逻辑交互程序
        |   └ init_user.py      # 用来 初始化用户/创建用户
        ├ log   # 日志文件存储目录
        ├ userinfo      # 用户信息数据库
        └ userstorage   # 用户空间
    

    代码


    FTP 服务端

    1 import os,sys
    2 
    3 BasePath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    4 sys.path.insert(0,BasePath)
    5 
    6 from core import main
    7 main.main()
    ftp_server.py
    server_addr = {
        'ip':'127.0.0.1',
        'port':12345
    }
    setting.py
     1 import hashlib,pickle,os
     2 
     3 BasePath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
     4 userinfo_dir = os.path.join(BasePath,'userinfo')
     5 userstorage_dir = os.path.join(BasePath,'userstorage')
     6 
     7 print('\nYou can create some users for testing here...')
     8 
     9 while 1:
    10     name = input('Name: ').strip()
    11     pwd = input('Password: ').strip()
    12 
    13     m_name = hashlib.md5()
    14     m_name.update(name.encode())
    15     name_md5 = m_name.hexdigest()
    16 
    17     m_pwd = hashlib.md5()
    18     m_pwd.update(pwd.encode())
    19     pwd_md5 = m_pwd.hexdigest()
    20 
    21     root = os.path.join(userstorage_dir,name)
    22 
    23     while 1:
    24         quota = input('quota: ').strip()
    25         if quota.isdigit() or quota == '':
    26             quota = int(quota) if quota else 1024000000
    27             break
    28         else:
    29             continue
    30 
    31     user_info = {
    32         'name':name,
    33         'pwd':pwd_md5,
    34         'root':root,
    35         'quota':quota,
    36         'space_size':0,  # 记录已用空间大小,暂未使用
    37         'files_md5':{},  # 记录已上传文件的md5
    38         'put_progress':{}  # 记录未上传完毕文件的进度
    39     }
    40 
    41     user_info_path = os.path.join(userinfo_dir,name)
    42     with open(user_info_path,'wb') as f:
    43         pickle.dump(user_info,f)
    44 
    45     user_storage_path = os.path.join(userstorage_dir,name)
    46     if not os.path.isdir(user_storage_path):
    47         os.mkdir(user_storage_path)
    48 
    49     print('''
    50 User %s has be created!
    51 %s's database file is \033[1;33m%s\033[0m
    52 %s's storage_directory is \033[1;33m%s\033[0m
    53 %s's disk quota is %d Bytes
    54 '''%(name,name,user_info_path,name,user_storage_path,name,quota))
    55 
    56     continue_flag = input('Please press \'\033[1;31mq\033[0m\' to quit or other key to create another user').strip()
    57 
    58     if continue_flag == 'q':
    59         break
    init_user.py
      1 #! /usr/bin/env python3
      2 # -*- encoding:utf-8 -*-
      3 # Author:Jailly
      4 
      5 import socketserver
      6 import os
      7 import sys
      8 import json
      9 import pickle
     10 import re
     11 import subprocess
     12 import locale
     13 import hashlib
     14 import logging
     15 from logging import handlers
     16 
     17 BasePath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
     18 sys.path.insert(0,BasePath)
     19 
     20 from conf import setting
     21 
     22 userinfo_dir = os.path.join(BasePath,'userinfo')
     23 userstorage_dir = os.path.join(BasePath,'userstorage')
     24 sys_encode = locale.getdefaultlocale()[1]
     25 
     26 logger = logging.getLogger()
     27 logger.setLevel(10)
     28 
     29 log_path = os.path.join(os.path.join(BasePath,'log'),'ftpserver.log')
     30 tfh = handlers.TimedRotatingFileHandler(log_path,when='midnight',backupCount=10,encoding='utf-8')
     31 tfh.setLevel(20)
     32 formatter = logging.Formatter('%(levelname)s:%(message)s.[%(asctime)s]',datefmt=r'%m/%d/%Y %H:%M:%S')
     33 
     34 tfh.setFormatter(formatter)
     35 logger.addHandler(tfh)
     36 
     37 
     38 class MyTCPHandler(socketserver.BaseRequestHandler):
     39     ''' handle the request from client'''
     40 
     41     def __get_user_info(self,name):
     42         '''
     43         get user's information from the file related
     44         :param name: user name
     45         :return: user information
     46         '''
     47 
     48         user_info_file_path = os.path.join(userinfo_dir, name)
     49 
     50         with open(user_info_file_path,'rb') as f:
     51             user_info = pickle.load(f)
     52 
     53         return user_info
     54 
     55 
     56     def __write_user_info(self,user_info):
     57 
     58         user_info_file_path = os.path.join(userinfo_dir, self.user_name)
     59 
     60         with open(user_info_file_path,'wb') as f:
     61             pickle.dump(user_info,f)
     62 
     63 
     64     def authenticate(self):
     65         '''
     66         handle the authentication and return the result code 
     67         :param:
     68         :return: authentication result code:'0' means failure,'1' means sucess
     69         '''
     70 
     71         while 1:
     72 
     73             auth_json = self.request.recv(8192).decode('utf-8')
     74             auth_info = json.loads(auth_json)
     75 
     76             recv_name = auth_info['name']
     77             recv_pwd_md5 = auth_info['pwd']
     78 
     79             if recv_name in os.listdir(userinfo_dir):
     80                 user_info = self.__get_user_info(recv_name)
     81 
     82                 if recv_pwd_md5 == user_info['pwd']:
     83                     self.request.send(b'0')
     84                     logger.info('User %s logins succesfully,client adress is %s'%(recv_name,str(self.client_address)))
     85                     return user_info
     86                 else:
     87                     self.request.send(b'2')
     88 
     89             else:
     90                 self.request.send(b'1')
     91 
     92 
     93     def __replace_path(self,match):
     94         ''' It can only be called by re.sub() in method 'replace_args' '''
     95         if match.group().startswith(os.sep):
     96             return os.path.join(self.user_root,match.group())
     97         else:
     98             return os.path.join(self.user_current_path,match.group())
     99 
    100     def __replace_args(self,cmd,args_input):
    101         '''
    102         Replace file path in arguments inputed.
    103         It can only be used in method that handle the command with file_path but 'cd'
    104         :return: real_instructions: instructions that has been replaced
    105         '''
    106 
    107         real_args = re.sub(r'((?<=\s)|^)([^-].*)', self.__replace_path, args_input) \
    108             if re.search(r'((?<=\s)|^)([^-].*)', args_input) else ' '.join([args_input, self.user_current_path])
    109         real_instructions = ' '.join([cmd, real_args])
    110 
    111         return real_instructions
    112 
    113 
    114     # def __replace_res_con(self,res_con):
    115     #     '''
    116     #     replace the absolute path of user root on windows
    117     #     :param res_con: result of cmd
    118     #     :return:
    119     #     '''
    120     #
    121     #     res_con = res_con.replace(self.user_root, '')
    122     #     con_list = re.split(r'%s\s*'%os.linesep,res_con)
    123     #     new_con_list = []
    124     #     for i in range(len(con_list)):
    125     #         if i>0:
    126     #             temp_line = ''.join([con_list[i-1],con_list[i]])
    127     #             if re.search(self.user_root,temp_line):
    128     #                 pass
    129 
    130 
    131     def cmd_base(self,*args):
    132         '''
    133         execute command that its arguments contains path, and send result to client
    134         :param: args[1]: cmd
    135         :param: args[2]; args_input
    136         :return:
    137         '''
    138 
    139         instructions = self.__replace_args(args[1], args[2])
    140 
    141         if os.name == 'posix':
    142             res = subprocess.Popen(instructions,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    143         elif os.name == 'nt':
    144             instructions_list = [r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe','-ExecutionPolicy','RemoteSigned']
    145             instructions_list.extend(instructions.split())
    146             # print(instructions_list)
    147             res = subprocess.Popen(instructions_list,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    148         else:
    149             # 待验证
    150             res = subprocess.Popen(instructions, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    151 
    152         out, err = res.communicate()
    153         res_con = err.decode(sys_encode) if res.wait() else out.decode(sys_encode)
    154         # windows系统powershell的某些命令输出中包含用户存储空间的绝对路径,需替换 或 删除相关字段!!!
    155         res_con = re.split(r'(?:\r\n){3}',res_con,maxsplit=1)[1] if len(re.split(r'(\r\n){3}',res_con,maxsplit=1)) > 1 \
    156             else res_con
    157         # print(res_con)
    158 
    159         # 发送json格式可以避免命令执行成功但无返回,导致的发送为空的情况;且兼具了扩展性
    160         res_cmd = {
    161             'res_con':res_con
    162         }
    163 
    164         res_json = json.dumps(res_cmd)
    165         # print(res_json)
    166         # 发送命令结果
    167         self.send_result(res_json.encode())
    168 
    169 
    170     def send_result(self,res_json):
    171         '''
    172         send the result of command to client
    173         :param cmd_json: result of command,it's a byte-like object
    174         :return:
    175         '''
    176 
    177         len_res_json = len(res_json)
    178         self.request.send(str(len_res_json).encode())
    179         self.request.recv(8192)  # block to avoid packet splicing(s:2)
    180         self.request.sendall(res_json)
    181 
    182 
    183     def cmd_ls(self,*args):
    184         self.cmd_base(*args)
    185 
    186 
    187     def cmd_pwd(self,*args):
    188         '''
    189         execute 'pwd' command and send the result to client
    190         :param: args[1]: cmd
    191         :param: args[2]; args_input
    192         :return:
    193         '''
    194 
    195         res_con = self.user_current_path.replace(self.user_root,'')
    196         res_con = res_con if res_con else os.sep
    197         res_cmd = {'res_con':res_con}
    198         res_json = json.dumps(res_cmd)
    199         self.send_result(res_json.encode())
    200 
    201 
    202     def cmd_cd(self,*args):
    203         '''
    204         execute 'cd' command(set 'self.user_current_path' actually) and send result to client
    205         :param: args[1]: cmd
    206         :param: args[2]; args_input
    207         :return:
    208         '''
    209 
    210         file_path_input = re.search(r'((?<=\s)|^)([^-].*)',args[2]).group()  # 仅适用于以 ‘-’ 做参数前缀的情况
    211         last_current_path = self.user_current_path
    212         if file_path_input.startswith(os.sep):
    213             file_path_input = file_path_input.replace(os.sep,'',1)
    214             self.user_current_path = os.path.join(self.user_root,file_path_input)
    215         elif file_path_input == '.':
    216             pass
    217         elif file_path_input == '..':
    218             self.user_current_path = os.path.dirname(self.user_current_path)
    219         else:
    220             self.user_current_path = os.path.join(self.user_current_path,file_path_input)
    221 
    222         if os.path.isdir(self.user_current_path):
    223             if self.user_current_path.startswith(self.user_root):
    224                 res_cmd = {'res_con': 0}  # 0 means 'Execute command successfully'
    225             else:
    226                 self.user_current_path = self.user_root
    227                 res_cmd = {'res_con': 2} # 2 means 'You do not have permission to access the directory'
    228 
    229         else:
    230             self.user_current_path = last_current_path
    231             res_cmd = {'res_con': 1}  # 1 means 'Not such directory'
    232 
    233         res_json = json.dumps(res_cmd)
    234 
    235         self.send_result(res_json.encode())
    236 
    237 
    238     def cmd_mkdir(self,*args):
    239         self.cmd_base(*args)
    240 
    241 
    242     def cmd_rmdir(self,*args):
    243         self.cmd_base(*args)
    244 
    245 
    246     def cmd_rm(self,*args):
    247         '''
    248         execute 'rm' command , and send result to client
    249         :param: args[1]: cmd
    250         :param: args[2]; args_input
    251         :return:
    252         '''
    253         self.cmd_base(*args)
    254 
    255         file_path_input = re.search(r'((?<=\s)|^)([^-].*)',args[2]).group()
    256         user_info = self.__get_user_info(self.user_name)
    257 
    258         # 同时删除其md5记录,上传进度记录
    259         if file_path_input in user_info['files_md5']:
    260             del user_info['files_md5'][file_path_input]
    261 
    262         if file_path_input in user_info['put_progress']:
    263             del user_info['files_md5'][file_path_input]
    264 
    265 
    266     def __get_dir_size(self,dir):
    267         '''
    268         obtain the target directory's size
    269         :param dir:target directory's absolute path
    270         :return:target directory's size
    271         '''
    272 
    273         walks = os.walk(dir)
    274         size = 0
    275 
    276         for walk in walks:
    277             for each_file_relative_path in walk[2]:
    278                 each_file_absolute_path = os.path.join(walk[0],each_file_relative_path)
    279                 size += os.stat(each_file_absolute_path).st_size
    280 
    281         return size
    282 
    283 
    284     def __rename_file(self,path,times=1):
    285         '''
    286         rename file in the form of 'file name + (n)' recursively until its name is different from all files in current directory
    287         :param path:the file's path
    288         :param times:the times of renaming file
    289         :return:
    290         '''
    291 
    292         new_path = path+'(%d)'%times
    293         if os.path.isfile(new_path):
    294             times += 1
    295             return self.__rename_file(path,times)
    296         else:
    297             return new_path
    298 
    299 
    300     def cmd_put(self,*args):
    301         '''
    302         receive the file that the client uploads
    303         :param args[2]:the path on client of file that was been putted
    304         :return:
    305         '''
    306 
    307         self.request.send(b'0')  # cooperate to finish client's block action (c:1)
    308         filesize = int(self.request.recv(1024).decode('utf-8'))
    309         filename = args[2]
    310 
    311         # 计算空间是否充足时,要考虑未上传完毕的文件,断点续传时还需再上传多少大小,而不是简单地将现有空间用量 加 待上传文件大小
    312         user_info = self.__get_user_info(self.user_name)
    313         # print(user_info)
    314         file_path = os.path.join(self.user_current_path, filename)
    315         has_uploaded_size = user_info['put_progress'][file_path] if file_path in user_info['put_progress'] else 0
    316         need_increase_size = filesize - has_uploaded_size
    317 
    318         # 磁盘配额的两种方案:①每次上传,由服务端实时计算可用空间;②上传文件后,将文件大小加到用户当前磁盘空间上,并将新数值记录为当前用户磁盘空间,
    319         # 记录到用户配置文件中,每次上传前读取配置文件中的当前磁盘空间大小,计算用户空间是否充足。这里采用方案①
    320         user_current_space_size = self.__get_dir_size(self.user_root)
    321         user_expeted_space_size = user_current_space_size + need_increase_size
    322 
    323         # 告知客户端磁盘空间是否充足:1:不足;2:充足
    324         if user_expeted_space_size < self.user_info['quota']:
    325             self.request.send(b'0')  # tell client if the space left is enough
    326 
    327             if os.path.isfile(file_path):
    328                 file_path = self.__rename_file(file_path)
    329 
    330             put_progress = user_info['put_progress'][file_path] if file_path in user_info['put_progress'] else 0
    331 
    332             self.request.recv(1024) # block to avoid packet splicing(s:3)
    333             self.request.send(str(put_progress).encode())
    334 
    335             m = hashlib.md5()
    336             try:
    337                 with open(''.join([file_path,'.uploading']), 'a+b') as f:
    338                     f.seek(0,0)  # a模式下,指针默认在文件末尾,需先将指针定位到文件头部
    339                     m.update(f.read())
    340                     while put_progress < (filesize - 8192):
    341                         accepting_data = self.request.recv(8192)
    342                         m.update(accepting_data)
    343                         put_progress += len(accepting_data)
    344                         f.write(accepting_data)
    345 
    346                     while put_progress < filesize:
    347                         buffersize = filesize - put_progress
    348                         accepting_data = self.request.recv(buffersize)
    349                         m.update(accepting_data)
    350                         put_progress += len(accepting_data)
    351                         f.write(accepting_data)
    352 
    353             except Exception as e:
    354                 print(e)
    355                 print('\033[1;31mUploading from %s was been interrupted\033[0m' % (str(self.client_address)))
    356 
    357                 self.break_flag = 1 # 退出循环,否则IO缓冲区中未接收的数据会又发被handle()中本应接收指令的recv所接收
    358 
    359                 logger.warning('Uploading the file \'%s\' by user %s interrupted'%(file_path,self.user_name))
    360 
    361             else:
    362                 # 上传完成后去掉文件名中的'.uploading'
    363                 os.rename(''.join([file_path,'.uploading']),file_path)
    364 
    365                 md5_from_client = self.request.recv(1024).decode('utf-8')
    366                 # print('md5_from_client: ',md5_from_client)
    367                 # print('md5_from_server: ',m.hexdigest())
    368                 # 告知客户端完整性验证是否成功:b'0':成功;b'1':失败
    369                 self.request.send(b'0' if md5_from_client == m.hexdigest() else b'1')
    370 
    371                 user_info['files_md5'][file_path]  = m.hexdigest()
    372                 self.__write_user_info(user_info)
    373 
    374                 logger.info('User %s uploads the file \'%s\''%(self.user_name,file_path))
    375 
    376             finally:
    377                 user_info = self.__get_user_info(self.user_name)
    378                 # 进度与文件大小相等,则删除上传进度的记录,否则记录上传进度
    379                 if put_progress != filesize:
    380                     user_info['put_progress'][file_path] = put_progress
    381                 else:
    382                     if file_path in user_info['put_progress']:
    383                         del user_info['put_progress'][file_path]
    384 
    385                 self.__write_user_info(user_info)
    386 
    387         else:
    388             self.request.send(b'1')
    389 
    390 
    391     def cmd_get(self,*args):
    392         '''
    393         send the file that the client downloads
    394         :param args[2]:the path on client of file that was been putted
    395         :return:
    396         '''
    397 
    398         filename = args[2]
    399         if filename.startswith(os.sep):
    400             filename = filename.replace(os.sep,'',1)
    401             file_path = os.path.join(self.user_root,filename)
    402         else:
    403             file_path = os.path.join(self.user_current_path, filename)
    404 
    405         if os.path.isfile(file_path):
    406             self.request.send(b'0')  # indicate if the file exist:b'0' means yes,b'1' means no
    407 
    408             get_progress = int(self.request.recv(1024).decode('utf-8'))
    409             # print('get_progress: ',get_progress)
    410             filesize = os.stat(file_path).st_size
    411             self.request.send(str(filesize).encode())
    412 
    413             try:
    414                 with open(file_path,'br') as f:
    415                     f.seek(get_progress,0)
    416                     for line in f:
    417                         self.request.send(line)
    418             except Exception as e:
    419                 print(e)
    420                 print('Connection with %s is closed'%str(self.client_address))
    421                 self.break_flag = 1
    422 
    423                 logger.warning('Dloading the file \'%s\' by user %s interrupted'%(file_path,self.user_name))
    424 
    425             else:
    426                 user_info = self.__get_user_info(self.user_name)
    427                 file_md5 = user_info['files_md5'][file_path] if file_path in user_info['files_md5'] else '0'
    428                 print(user_info)
    429                 self.request.send(file_md5.encode())
    430 
    431                 logger.info('User %s downloads the file \'%s\''%(self.user_name,file_path))
    432 
    433             finally:
    434                 pass
    435 
    436         else:
    437             self.request.send(b'1')
    438 
    439 
    440     def cmd_exit(self,*args):
    441         self.break_flag = 1
    442         self.request.close()
    443 
    444 
    445     def handle(self):
    446         '''method for handling request'''
    447 
    448         try:
    449             self.break_flag = 0
    450 
    451             self.user_info = self.authenticate()
    452 
    453             self.user_name = self.user_info['name']
    454             # self.user_info_file_path = os.path.join(userinfo_dir, self.user_name)  # 验证时也会调用,那时还不存在self.user_name
    455             self.user_root = os.path.join(userstorage_dir,self.user_name)
    456             self.user_current_path = self.user_root
    457 
    458             while 1:
    459                 instructions = self.request.recv(1024)
    460                 # print('instructions_byets:',instructions)
    461                 instructions = instructions.decode('utf-8')
    462                 # print('instructions_str:',instructions)
    463 
    464                 cmd = instructions.split(maxsplit = 1)[0]
    465                 # print(cmd)
    466                 args_input = instructions.split(maxsplit = 1)[1] if len(instructions.split()) > 1 else ''
    467                 # print(args_input)
    468 
    469                 if hasattr(self,''.join(['cmd_',cmd])):
    470                     # print('cmd found!')
    471                     func = getattr(self,''.join(['cmd_',cmd]))
    472                     func(self,cmd,args_input)
    473 
    474                     if self.break_flag == 1:
    475                         break
    476 
    477                 else:
    478                     print('%s:command not found'%cmd)
    479 
    480         except ConnectionResetError:
    481             print('\033[1;31mThe connection with %s is closed\033[0m'%(str(self.client_address)))
    482 
    483             logger.info('The connection with %s is closed'%(str(self.client_address)))
    484 
    485         except Exception as e:
    486             print(e)
    487             print('Connection with %s is closed'%str(self.client_address))
    488 
    489             logger.error('Detected an error occurred: %s'%e)
    490 
    491 
    492 def main():
    493     ip = setting.server_addr['ip']
    494     port = setting.server_addr['port']
    495     myserver = socketserver.ThreadingTCPServer((ip,port),MyTCPHandler)
    496 
    497     myserver.serve_forever()
    498 
    499 
    500 if __name__ == "__main__":
    501     main()
    main.py

    FTP 客户端

    1 import os,sys
    2 
    3 BasePath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    4 sys.path.insert(0,BasePath)
    5 
    6 from core import main
    7 main.main()
    ftp_client.py
    1 server_addr = {
    2     'ip':'127.0.0.1',
    3     'port':12345
    4 }
    setting.py
      1 #! /usr/bin/env python3
      2 # -*- encoding:utf-8 -*-
      3 # Author:Jailly
      4 
      5 import socket,os,sys,hashlib,json,time,pickle
      6 
      7 BasePath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
      8 sys.path.insert(0,BasePath)
      9 
     10 download_path = os.path.join(BasePath,'ftpdownload')
     11 conf_path = os.path.join(BasePath,'conf')
     12 
     13 from conf import setting
     14 
     15 class FtpClient(object):
     16     ''' handle client's socket creating , connecting with server and interactions'''
     17 
     18     def __init__(self):
     19         self.socket = socket.socket()
     20 
     21 
     22     def help(self):
     23         '''show command that can be used by ftp-client'''
     24 
     25         msg = '''You can use the following command:
     26 \033[1;31mls\033[0m [opt] [file]|[directory]       
     27 \033[1;31mpwd\033[0m
     28 \033[1;31mcd\033[0m [opt] [directory]
     29 \033[1;31mmkdir\033[0m [opt] [directory]
     30 \033[1;31mrmdir\033[0m [opt] [directory]
     31 \033[1;31mrm\033[0m [opt] [directory]
     32 \033[1;31mput\033[0m [file]
     33 \033[1;31mget\033[0m [file]
     34 exit
     35         '''
     36         print(msg)
     37 
     38 
     39     def connect(self,ip,port):
     40         '''
     41         connet with ftp-server
     42         :param: ip: ftp server's ip
     43         :param: port: ftp server's port
     44         '''
     45 
     46         self.socket.connect((ip,port))
     47 
     48 
     49     def get_auth_result(self):
     50         '''
     51         send user's anthentication information and receive authentication result from server
     52         :param:
     53         :return: auth_flag: authentication result code:'0' means failure,'1' means success
     54         :return: name_md5: md5 value of name inputed
     55         :return: name: name inputed
     56         '''
     57 
     58         name = input('user:').strip()
     59         pwd = input('password:').strip()
     60 
     61         m_pwd = hashlib.md5()
     62         m_pwd.update(pwd.encode())
     63         pwd_md5 = m_pwd.hexdigest()
     64 
     65         auth_info = {
     66             'name':name,
     67             'pwd':pwd_md5
     68         }
     69 
     70         auth_json = json.dumps(auth_info)
     71 
     72         self.socket.send(auth_json.encode('utf-8'))
     73 
     74         auth_flag = int(self.socket.recv(1024).decode('utf-8'))
     75         return auth_flag,name
     76 
     77 
     78     def authenticate(self):
     79         '''
     80         just for authentication
     81         :return: 
     82         '''
     83 
     84         auth_times = {}
     85         while 1:
     86             auth_flag,name = self.get_auth_result()
     87 
     88             if auth_flag:
     89                 if auth_flag == 1:
     90                     print('\033[1;31mUser does not exists!\033[0m')
     91                 else:
     92                     auth_times.setdefault(name,1)
     93                     auth_times[name] += 1
     94 
     95                     if auth_times[name] == 3:
     96                         print('\033[1;31mInput wrong password of user \'%s\' more than 3 times, \
     97 The connection will be disconnected in 2 seconds\033[0m'%name,end='')
     98 
     99                         for i in range(3):
    100                             sys.stdout.write('.')
    101                             sys.stdout.flush()
    102                             time.sleep(0.5)
    103 
    104                         self.socket.close()
    105                         exit()
    106                     else:
    107                         print('\033[1;31mIncorrect password!\033[0m')
    108 
    109             else:
    110                 self.user_name = name
    111                 self.local_user_info_path = os.path.join(conf_path,self.user_name)
    112                 print('\033[1;33mHi,%s!\nWelcome to Jailly\'s Ftpserver\033[0m'%name)
    113                 break
    114 
    115 
    116     def cmd_base(self,*args,only_get_result = False):
    117         '''
    118         receive return of command from ftp_server and show it
    119         :param: args[1]:instructions
    120         :param: only_get_result:False -> just return res_con; True -> print res_con
    121         :return:
    122         '''
    123 
    124         # cmd = args[0].split(maxsplit=1)[0]
    125         # args_input = args[0].split(maxsplit=1)[1] if len(args[0].split()) > 1 else ''
    126 
    127         self.socket.send(args[1].encode('utf-8'))
    128 
    129         len_res_json = int(self.socket.recv(1024).decode('utf-8'))
    130         # print(len_res_json)
    131         self.socket.send(b'0')  # cooperate to finish server's block action(s:2)
    132 
    133         accepted_len = 0
    134         res_json = b''
    135         while accepted_len != len_res_json:
    136             accepting_data = self.socket.recv(8192)
    137             accepted_len += len(accepting_data)
    138             res_json += accepting_data
    139 
    140         res_cmd = json.loads(res_json.decode('utf-8'))
    141         res_con = res_cmd['res_con']
    142 
    143         if only_get_result:
    144             return res_con
    145 
    146         print(res_con)
    147 
    148 
    149     def cmd_ls(self,*args):
    150         self.cmd_base(*args)
    151 
    152 
    153     def cmd_pwd(self,*args):
    154         self.cmd_base(*args)
    155 
    156 
    157     def cmd_cd(self,*args):
    158         res_con = self.cmd_base(*args,only_get_result=True)
    159 
    160         if res_con == 1:
    161             print('\033[1;31mNot such directory\033[0m')
    162         elif res_con == 2:
    163             print('\033[1;31mYou do not have permission to access this directory\033[0m')
    164 
    165 
    166     def cmd_mkdir(self,*args):
    167         self.cmd_base(*args)
    168 
    169 
    170     def cmd_rmdir(self,*args):
    171         self.cmd_base(*args)
    172 
    173 
    174     def cmd_rm(self,*args):
    175         self.cmd_base(*args)
    176 
    177 
    178     def cmd_put(self,*args):
    179         '''
    180         upload the file specified
    181         :param args[1]: instructions
    182         :return:
    183         进度条,断点续传,配额,完整性验证
    184         '''
    185 
    186         file_path = args[1].split(maxsplit=1)[1] if len(args[1].split()) > 1 else ''
    187         filename = os.path.basename(file_path)
    188         if os.path.isfile(file_path):
    189 
    190             self.socket.send((' '.join(['put',filename])).encode('utf-8'))
    191             self.socket.recv(1024)  # 阻塞,以避免"发送指令"与"发送文件长度"2次之间的粘包(c:1)
    192 
    193             filesize = os.stat(file_path).st_size
    194             self.socket.send(str(filesize).encode())
    195 
    196             # 接收服务端发来的确认。enough为1:用户空间不足;enough为0:用户空间充足
    197             enough = self.socket.recv(1024)
    198             if enough == b'1':
    199                 print('\033[1;31mYour available space is not enough,nothing to do\033[0m')
    200                 return
    201 
    202             else:
    203                 self.socket.send(b'0')  # block to avoid packet splicing(s:3)
    204                 sent_size = int(self.socket.recv(1024).decode('utf-8'))
    205                 percentage_last_sent_file = sent_size*100//filesize
    206                 m = hashlib.md5()# md5 object for the file putted
    207                 print('Putting \'%s\':'%filename,end=' ')
    208                 send_start_time = time.time()
    209 
    210                 try:
    211                     with open(file_path,'rb') as f:
    212                         m.update(f.read(sent_size))
    213                         sys.stdout.write('#'*percentage_last_sent_file)
    214                         sys.stdout.flush()
    215 
    216                         for line in f:
    217                             self.socket.send(line)
    218                             m.update(line)
    219 
    220                             # 打印进度条
    221                             sent_size += len(line)
    222                             percentage_sent_file = sent_size*100//filesize
    223                             num_bar_unit = percentage_sent_file - percentage_last_sent_file
    224 
    225                             if num_bar_unit:
    226                                 sys.stdout.write('#'*num_bar_unit)
    227                                 sys.stdout.flush()
    228                                 percentage_last_sent_file = percentage_sent_file
    229 
    230                 except Exception as e:
    231                     print(e)
    232                     print('Transfer interrupted')
    233                 else:
    234                     self.socket.send(m.hexdigest().encode('utf-8'))
    235                     integrity_confirm = self.socket.recv(1024)
    236 
    237                     send_end_time = time.time()
    238                     upload_speed = filesize/1000/(send_end_time-send_start_time)
    239 
    240                     print('\nTransfer completed.\nFile integrity authentication %s\nUpload speed:%.1d k/s'
    241                           %('succeeded' if integrity_confirm == b'0' else 'failed',upload_speed))
    242 
    243 
    244         else:
    245             print('File \'\033[1;31m%s\033[0m\' does not exists'%filename)
    246 
    247 
    248     def __rename_file(self, path, times=1):
    249         '''
    250         rename file in the form of 'file name + (n)' recursively until its name is different from all files in current directory
    251         :param path:the file's path
    252         :param times:the times of renaming file
    253         :return:
    254         '''
    255 
    256         new_path = path + '(%d)' % times
    257         if os.path.isfile(new_path):
    258             times += 1
    259             return self.__rename_file(path, times)
    260         else:
    261             return new_path
    262 
    263 
    264     def cmd_get(self,*args):
    265         '''
    266         download the file specified
    267         :param args[1]: instructions
    268         :return:
    269         进度条,断点续传,配额,完整性验证
    270         '''
    271 
    272         self.socket.send(args[1].encode())
    273         filename = os.path.basename(args[1].split(maxsplit=1)[1] if len(args[1].split()) > 1 else '')
    274 
    275         exist_flag = self.socket.recv(1024)
    276         if exist_flag == b'0':
    277             file_path = os.path.join(download_path,filename)
    278             if os.path.isfile(file_path):
    279                 file_path = self.__rename_file(file_path)
    280 
    281             temp_file_path = ''.join([file_path,'.downloading'])
    282 
    283             get_progress = os.stat(temp_file_path).st_size if os.path.isfile(temp_file_path) else 0
    284             # print('get_progress: ',get_progress)
    285             self.socket.send(str(get_progress).encode())
    286 
    287             filesize = int(self.socket.recv(1024).decode('utf-8'))
    288 
    289             last_sent_file_size = get_progress
    290             print('Getting \'%s\': '%filename,end='')
    291             start_time = time.time()
    292 
    293             m = hashlib.md5()
    294             try:
    295                 with open(temp_file_path, 'a+b') as f:
    296                     f.seek(0,0)
    297                     m.update(f.read())
    298                     print('#'*(get_progress*100//filesize),end='')
    299 
    300                     while get_progress < filesize:
    301                         if get_progress >= filesize -8192:
    302                             buffersize = filesize - get_progress
    303                         else:
    304                             buffersize = 8192
    305 
    306                         accepting_data = self.socket.recv(buffersize)
    307 
    308                         m.update(accepting_data)
    309                         get_progress += len(accepting_data)
    310                         f.write(accepting_data)
    311 
    312                         # 打印进度条
    313                         num_bar_unit = (get_progress-last_sent_file_size)*100//filesize
    314                         if num_bar_unit:
    315                             sys.stdout.write('#'*num_bar_unit)
    316                             sys.stdout.flush()
    317                             last_sent_file_size = get_progress
    318 
    319             except Exception as e:
    320                 print(e)
    321                 print('Transfer interrupted')
    322 
    323                 self.socket.close()
    324             else:
    325                 os.rename(''.join([file_path,'.downloading']),file_path)
    326 
    327                 end_time = time.time()
    328                 download_speed = filesize/1024/(end_time-start_time)
    329                 # print(1)
    330                 md5_from_server = self.socket.recv(1024).decode('utf-8')
    331                 # print(2)
    332                 print('\nTransfer completed.\nFile integrity authentication %s\nUpload speed:%.1d k/s'
    333                           %('succeeded' if md5_from_server == m.hexdigest() else 'failed',download_speed))
    334 
    335         else:
    336             print('File \'\033[1;31m%s\033[0m\' does not exists'%filename)
    337 
    338 
    339     def cmd_exit(self,*args):
    340         self.socket.send(b'exit')
    341         self.break_flag = 1
    342         self.socket.close()
    343 
    344 
    345     def interactive(self):
    346         '''handle all interactive actions here'''
    347 
    348         try:
    349             self.break_flag = 0
    350 
    351             self.authenticate()
    352 
    353             while 1:
    354                 instructions = input('>> ').strip()
    355 
    356                 if instructions == '':
    357                     print('Input can not be empty!')
    358                     continue
    359 
    360                 cmd = instructions.split(maxsplit=1)[0]
    361 
    362                 if hasattr(self,''.join(['cmd_',cmd])):
    363                     func = getattr(self,''.join(['cmd_',cmd]))
    364                     func(self,instructions)
    365 
    366                     if self.break_flag:
    367                         break
    368 
    369                 else:
    370                     print('\033[1;31m%s:command not found\033[0m'%cmd)
    371                     self.help()
    372 
    373         except ConnectionResetError as e:
    374             print(e)
    375             print('Connection has interrupted')
    376         except Exception as e:
    377             print(e)
    378             print('\033[1;31mUnknown error,please check if your program operates properly\033[0m')
    379 
    380 def main():
    381     client = FtpClient()  # 创建套接字
    382 
    383     server_ip = setting.server_addr['ip']
    384     server_port = setting.server_addr['port']
    385 
    386     client.connect(server_ip,server_port)  # 连接服务器
    387 
    388     client.interactive()  # 与服务器交互
    389 
    390 
    391 if __name__ == '__main__':
    392     main()
    main.py
  • 相关阅读:
    NVMe固态硬盘工具箱使用说明
    (原创)Python文件与文件系统系列(1)—— file 对象
    Linux系统排查4——网络篇
    Python内置类型——list
    (原)数据结构——线索二叉树
    Python匿名函数——lambda表达式
    Python生成器
    Python内置类型——dict
    Python内置类型——set
    解决Django-1.8.2应用部署到Apache后无法显示admin应用的CSS
  • 原文地址:https://www.cnblogs.com/jailly/p/7159233.html
Copyright © 2020-2023  润新知