zoukankan      html  css  js  c++  java
  • python【项目】:基于socket的FTP服务器

    • 功能要求

    1. 用户加密认证

    2. 服务端采用 SocketServer实现,支持多客户端连接

    3. 每个用户有自己的家目录且只能访问自己的家目录

    4. 对用户进行磁盘配额、不同用户配额可不同

    5. 用户可以登陆server后,可切换目录

    6. 能查看当前目录下文件

    7. 上传下载文件,保证文件一致性

    8. 传输过程中实现进度条展示

    9.用户可在自己家目录进行创建目录、文件、删除目录及文件

    10.服务端可实现增加用户、删除用户

    11.支持上传时断点续传

    • 应用知识点 

    a) 类的应用

    b) 函数的使用

    c) 多进程

    d) 反射

    e)  socket、socketserver、hashlib、configparser、logging

    f)  文件的读写

    • 开发环境
    1. python 3.6.1
    2. PyCharm 2016.2.3
    • 目录结构

    FTPClient

           |--bin              (主接口目录)

          |--ftpclient.py    (客户端主程序接口文件)

          |--config           (配置文件目录)

          |--code.py       (状态码文件)

          |--settings.py    (配置文件)

          |--template.py   (模板文件)

          |--download        (下载存放目录)

          |--lib              (模块目录)

          |--client.py      (客户端各类接口封装)

          |--common.py   (公共接口)

          |--logs            (日志目录)

          |--ftpclient.log  (日志文件)

      |--clientRun.py     (主执行程序)

     

    FTPServer

           |--bin               (主接口目录)

          |--ftpserver.py      (服务端socket接口文件)

            |--main.py        (主程序接口文件)

          |--config            (配置目录)

          |--settings.py     (配置文件)

          |--template.py    (模板文件)

      |--database          (数据保存目录)

       |--user.ini        (用户信息文件)

      |--dbhelper          (数据目录)

         |--dbapi.py       (数据操作接口)

      |--lib               (模块目录)

         |--user.py        (用户类文件用来实例化对象)

          |--server.py      (服务端模块,各类所有命令方法)

          |--common.py    (公共模块文件)

          |--logs

          |--ftpserver.log   (日志文件)

       |--upload           (上传文件存放的目录)

       |--serverRun.py     (主执行程序)

    • 模块功能系统图

    1、思维导图

    2、功能接口关系图

    客户端:

    服务端:

    • 关键代码段

    1、服务端

     1 #!/usr/bin/env python
     2 #coding=utf-8
     3 __author__ = 'yinjia'
     4 
     5 
     6 import socketserver,os,sys
     7 sys.path.append(os.path.dirname(os.path.dirname(__file__)))
     8 from config import settings,template
     9 from lib import common,server
    10 
    11 
    12 
    13 logger = common.Logger('ftpserver').getlog()
    14 
    15 class MyServer(socketserver.BaseRequestHandler):
    16 
    17     def handle(self):
    18         try:
    19             client_socket = self.request
    20             client_addr = self.client_address
    21             logger.info("client {0} connected".format(client_addr))
    22             #发送成功标识给客户端
    23             client_socket.sendall(bytes("OK",encoding='utf-8'))
    24             client_user = None
    25 
    26             while True:
    27                 #获取客户端命令
    28                 ret_client_data = str(client_socket.recv(1024),encoding='utf-8')
    29 
    30                 #判断客户端是否退出
    31                 if ret_client_data == b'':
    32                     logger.info("client {0} is exit".format(client_addr))
    33                     client_socket.close()
    34 
    35                 #取出客户端命令
    36                 cmd = ret_client_data.split("|")[0]
    37 
    38                 logger.info("client {0} send command {1}".format(client_addr,cmd))
    39                 #判断是否登录认证状态
    40                 if cmd == 'auth':
    41                     client_user = server.client_auth(client_socket, ret_client_data)
    42                 else:
    43                    try:
    44                         #通过反射寻找模块的命令
    45                         if hasattr(server,cmd):
    46                             func = getattr(server,cmd)
    47                             func(client_socket, client_user, ret_client_data)
    48                         else:
    49                             logger.error("command {0} not found".format(cmd))
    50                    except Exception as e:
    51                        logger.error(e)
    52                        client_socket.close()
    53 
    54         except Exception as e:
    55             logger.error(e)
    56 
    57 def process():
    58     """
    59     启动服务
    60     :return:
    61     """
    62     server = socketserver.ThreadingTCPServer((settings.FTP_SERVER_IP,settings.FTP_SERVER_PORT),MyServer)
    63     server.serve_forever()
    ftpserver
     1 #!/usr/bin/env python
     2 #coding=utf-8
     3 __author__ = 'yinjia'
     4 
     5 
     6 import os,sys,configparser
     7 sys.path.append(os.path.dirname(os.path.dirname(__file__)))
     8 from config import settings
     9 
    10 def readall_sections():
    11     """
    12     读取user.ini文件所有的用户名
    13     :return: 返回所有的用户名列表
    14     """
    15     con = configparser.ConfigParser()
    16     con.read(settings.USER_INI, encoding='utf-8')
    17     result = con.sections()
    18     return result
    19 
    20 def GetValue(key,value):
    21     """
    22     获取user.ini文件键名值
    23     :param key: 键名
    24     :param value: 键值
    25     :return:
    26     """
    27     con = configparser.ConfigParser()
    28     con.read(settings.USER_INI, encoding='utf-8')
    29     result = con.get(key,value)
    30     return result
    31 
    32 def CheckSections(sections_name):
    33     """
    34     检查sections项名是否存在
    35     :param sections_name: 用户名
    36     :return:
    37     """
    38     con = configparser.ConfigParser()
    39     con.read(settings.USER_INI, encoding='utf-8')
    40     result = con.has_section(sections_name)
    41     return result
    42 
    43 def AddOption(sections_name, **args):
    44     """
    45     添加用户信息
    46     :param sections_name:用户名
    47     :param args: 字典格式:('test3',password='aa',totalspace='bb',userspace='cc')
    48     :return:
    49     """
    50     con = configparser.ConfigParser()
    51     with open(settings.USER_INI,'a+',encoding='utf-8') as f:
    52         con.add_section(sections_name)
    53         for key in args:
    54             con.set(sections_name, key, args[key])
    55         con.write(f)
    56 
    57 def DelSections(sections_name):
    58     """
    59     删除用户信息
    60     :param sections_name:
    61     :return:
    62     """
    63     con = configparser.ConfigParser()
    64     con.read(settings.USER_INI, encoding='utf-8')
    65     with open(settings.USER_INI,'w') as f:
    66         con.remove_section(sections_name)
    67         con.write(f)
    68 
    69 def ModifyOption(sections_name, **args):
    70     """
    71     修改磁盘配额空间
    72     :param sections_name: 用户名
    73     :param args:用户字典信息
    74     :return:
    75     """
    76     con = configparser.ConfigParser()
    77     con.read(settings.USER_INI, encoding='utf-8')
    78     for key in args:
    79         con.set(sections_name, key, args[key])
    80     with open(settings.USER_INI, 'w', encoding='utf-8') as f:
    81         con.write(f)
    82 
    83 def load_info(sections_name):
    84     """
    85     加载用户信息
    86     :param sections_name: 用户名
    87     :return: 返回字典用户信息
    88     """
    89     con = configparser.ConfigParser()
    90     con.read(settings.USER_INI, encoding='utf-8')
    91     user_dict = {}
    92     for i, j in con.items(sections_name):
    93         user_dict[i] = j
    94     return user_dict
    dbapi
      1 #!/usr/bin/env python
      2 #coding=utf-8
      3 __author__ = 'yinjia'
      4 
      5 
      6 import os,sys,time
      7 sys.path.append(os.path.dirname(os.path.dirname(__file__)))
      8 from lib.common import Logger
      9 from lib.user import Users
     10 from lib import common
     11 
     12 
     13 logger = Logger('serverr').getlog()
     14 
     15 
     16 
     17 def client_auth(client_socket,args):
     18 
     19     """
     20     客户端认证
     21     :param client_socket: 客户端socket对象
     22     :param args: 用户发送过来的数据 ex: "auth|test|a7470858e79c282bc2f6adfd831b132672dfd1224c1e78cbf5bcd057"
     23     :return: success:认证成功;user_error:用户名不存在;fail:认证失败
     24     """
     25     recv_data_list = args.split("|")
     26     username = recv_data_list[1]
     27     passwd = recv_data_list[2]
     28     client_user = Users(username)
     29     #判断用户名是否存在
     30     if client_user.check_user():
     31         msg = client_user.load_user_info()
     32         password,totalspace,userspace = msg.strip().split("|")
     33         user_info = "{0}|{1}".format(totalspace, userspace)
     34         #判断密码是否正确
     35         if password == passwd:
     36             auth_status = "success"
     37         else:
     38             auth_status = "fail"
     39     else:
     40         auth_status = "user_error"
     41 
     42     #将认证状态发送给客户端
     43     client_socket.sendall(bytes(auth_status,encoding='utf-8'))
     44     if auth_status == "success":
     45         # 认证成功将用户空间消息发给客户端
     46         client_socket.sendall(bytes(user_info, encoding='utf-8'))
     47     return client_user
     48 
     49 
     50 def cd(client_socket,client_user,ret_data):
     51     """
     52     切换目录路径
     53     :param client_socket: 客户端socket对象
     54     :param client_user: 客户端用户对象
     55     :param ret_data: 接收客户命令消息体  例如:cd|..或cd|test或cd|/test/aa/bb
     56     :return:
     57     """
     58     #获取命令行消息体
     59     cd_folder = ret_data.split("|")[1]
     60     try:
     61         #判断是否当前根目录
     62         if cd_folder == "..":
     63             if client_user.userpath == client_user.homepath:
     64                 sed_msg = "0|{0}".format(os.path.basename(client_user.userpath))
     65             else:
     66                 #返回上一级目录
     67                 client_user.userpath = os.path.dirname(client_user.userpath)
     68                 sed_msg = "1|{0}".format(os.path.basename(client_user.userpath))
     69         elif cd_folder == "." or cd_folder == "":
     70             sed_msg = "3|{0}".format(cd_folder)
     71         else:
     72             #组合路径目录
     73             tmp_path = os.path.join(client_user.userpath, cd_folder)
     74             if os.path.isdir(tmp_path):
     75                 client_user.userpath = tmp_path
     76                 sed_msg = "1|{0}".format(os.path.basename(client_user.userpath))
     77             else:
     78                 # 不是文件夹
     79                 sed_msg = "2|{0}".format(cd_folder)
     80         # 开始发送结果
     81         client_socket.sendall(bytes(sed_msg,encoding='utf-8'))
     82     except Exception as e:
     83         logger.error(e)
     84 
     85 def put(client_socket,client_user,ret_data):
     86     """
     87     上传文件
     88     :param client_socket:
     89     :param client_user:
     90     :param ret_data:
     91     :return:
     92     """
     93     # 初始化上传文件的基本信息
     94     filename = ret_data.split("|")[1]
     95     filesize = int(ret_data.split("|")[2])
     96     filemd5 = ret_data.split("|")[3]
     97     put_folder = client_user.userpath
     98     check_filename = os.path.isfile(os.path.join(put_folder,filename))
     99     save_path = os.path.join(put_folder, filename)
    100     fmd5 = common.md5sum(save_path)
    101     #不存在文件名,正常传输
    102     if not check_filename:
    103         client_socket.sendall(bytes("ok",encoding='utf-8'))
    104         # 全新的文件的话,更新用户使用空间大小
    105         client_user.update_quota(filesize)
    106         # 已经接收的文件大小
    107         has_recv = 0
    108         with open(save_path,'wb') as f:
    109             while True:
    110                 # 如果文件总大小等于已经接收的文件大小,则退出
    111                 if filesize == has_recv:
    112                     break
    113                 data = client_socket.recv(1024)
    114                 f.write(data)
    115                 has_recv += len(data)
    116     else:
    117         #存在文件名条件,做判断分析是否存在断点
    118         if fmd5 == filemd5:
    119             client_socket.sendall(bytes("full", encoding='utf-8'))
    120             # 已经接收的文件大小
    121             has_recv = 0
    122             with open(save_path, 'wb') as f:
    123                 while True:
    124                     # 如果文件总大小等于已经接收的文件大小,则退出
    125                     if filesize == has_recv:
    126                         break
    127                     data = client_socket.recv(1024)
    128                     f.write(data)
    129                     has_recv += len(data)
    130         else:
    131             #存在断点文件,发起请求续签标志
    132             recv_size = os.stat(save_path).st_size
    133             ready_status = "{0}|{1}".format("continue", str(recv_size))
    134             client_socket.sendall(bytes(ready_status, encoding='utf-8'))
    135             # 已经接收的文件大小
    136             has_recv = 0
    137             with open(save_path, 'wb') as f:
    138                 while True:
    139                     # 如果文件总大小等于已经接收的文件大小,则退出
    140                     if filesize == has_recv:
    141                         break
    142                     data = client_socket.recv(1024)
    143                     f.write(data)
    144                     has_recv += len(data)
    145 
    146 def get(client_socket,client_user,ret_data):
    147     """
    148     下载文件
    149     :param client_socket:
    150     :param client_user:
    151     :param ret_data:
    152     :return:
    153     """
    154     # 获取文件名
    155     filename = ret_data.split("|")[1]
    156     # 文件存在吗
    157     file = os.path.join(client_user.userpath, filename)
    158     if os.path.exists(file):
    159         # 先告诉客户端文件存在标识
    160         client_socket.send(bytes("1", 'utf8'))
    161         # 得到客户端回应
    162         client_socket.recv(1024)
    163         # 发送文件的基本信息 "filesize|file_name|file_md5"
    164         filesize = os.stat(file).st_size
    165         file_md5 = common.md5sum(file)
    166         sent_data = "{fsize}|{fname}|{fmd5}".format(fsize=str(filesize),
    167                                                     fname=filename,
    168                                                     fmd5=file_md5)
    169         client_socket.sendall(bytes(sent_data, 'utf8'))
    170 
    171         # 客户端收到ready
    172         if str(client_socket.recv(1024), 'utf-8') == "ready":
    173             # 开始发送数据了
    174             with open(file, 'rb') as f:
    175                 new_size = 0
    176                 for line in f:
    177                     client_socket.sendall(line)
    178                     new_size += len(line)
    179                     if new_size >= filesize:
    180                         break
    181     else:
    182         # 文件不存在
    183         client_socket.send(bytes("0", 'utf8'))
    184 
    185 
    186 def mk(client_socket,client_user,ret_data):
    187     """
    188     创建目录
    189     :param client_socket: 客户端socket对象
    190     :param client_user: 客户端用户对象
    191     :param ret_data: 接收客户命令消息体  例如:mk|test
    192     :return:
    193     """
    194     mk_folder = ret_data.split("|")[1]
    195     if mk_folder:
    196         try:
    197             folder_path = os.path.join(client_user.homepath, mk_folder)
    198             os.makedirs(folder_path)
    199             client_socket.sendall(bytes("5000",encoding='utf-8'))
    200         except Exception as e:
    201             client_socket.sendall(bytes("5001",encoding='utf-8'))
    202             logger.error("create directory failure: %s" % e)
    203     else:
    204         client_socket.sendall(bytes("5002", encoding='utf-8'))
    205 
    206 def delete(client_socket,client_user,ret_data):
    207     """
    208     删除目录或文件
    209     :param client_socket:客户端socket对象
    210     :param client_user:客户端用户对象
    211     :param ret_data:接收消息体:样本格式:delete|/test/aa
    212     :return:
    213     """
    214     del_folder = ret_data.split("|")[1]
    215     if del_folder:
    216         try:
    217             #判断文件名是否存在
    218             folder_path = os.path.join(client_user.homepath, del_folder)
    219             filesize = os.stat(folder_path).st_size
    220             if os.path.isfile(folder_path):
    221                 os.remove(folder_path)
    222                 client_user.update_down_quota(filesize)
    223                 sent_data = "{staus}|{fsize}".format(staus="6001",
    224                                                     fsize=str(filesize)
    225                                                     )
    226 
    227                 client_socket.sendall(bytes(sent_data, encoding='utf-8'))
    228             #判断目录是否存在
    229             elif os.path.isdir(folder_path):
    230                 os.removedirs(folder_path)
    231                 client_socket.sendall(bytes("6000", encoding='utf-8'))
    232             else:
    233                 #目录或文件名不存在情况,删除失败
    234                 client_socket.sendall(bytes("6002", encoding='utf-8'))
    235         except Exception as e:
    236                 #当前路径目录下不是空目录,不能删除
    237                 client_socket.sendall(bytes("6004", encoding='utf-8'))
    238                 logger.error("Delete directory or filename failure: %s" % e)
    239     else:
    240         #命令行后是空白目录
    241         client_socket.sendall(bytes("6003", encoding='utf-8'))
    242 
    243 
    244 def ls(client_socket,client_user,ret_data):
    245     """
    246     显示当前文件目录及文件名
    247     :param client_socket: 客户端socket对象
    248     :param client_user: 客户端用户对象
    249     :param ret_data: 接收消息体样本格式:ls|
    250     :return:
    251     """
    252     check_folder = client_user.userpath
    253     #获取用户目录下的文件目录或文件名列表
    254     file_list = os.listdir(check_folder)
    255     #目录下的文件个数
    256     file_count = len(file_list)
    257     if file_count > 0:
    258         return_list = "{filecount}|".format(filecount=file_count)
    259         for rootpath in file_list:
    260             file = os.path.join(check_folder,rootpath)
    261             stat = os.stat(file)
    262             create_time = time.strftime('%Y:%m-%d %X', time.localtime(stat.st_mtime))
    263             file_size = stat.st_size
    264             if os.path.isfile(file):
    265                 return_list += "{ctime}        {fsize}    {fname}
    ".format(ctime=create_time,
    266                                                                             fsize=str(file_size).rjust(10, " "),
    267                                                                             fname=rootpath)
    268             if os.path.isdir(file):
    269                 return_list += "{ctime}  <DIR> {fsize}    {fname}
    ".format(ctime=create_time,
    270                                                                             fsize=str(file_size).rjust(10, " "),
    271                                                                             fname=rootpath)
    272     else:
    273         return_list = "0|"
    274 
    275     try:
    276         # 开始发送信息到客户端
    277         # 1 先把结果串的大小发过去
    278         str_len = len(return_list.encode("utf-8"))
    279         client_socket.sendall(bytes(str(str_len), encoding='utf-8'))
    280         # 2 接收客户端 read 标识,防止粘包
    281         read_stat = client_socket.recv(1024).decode()
    282         if read_stat == "ready":
    283             client_socket.sendall(bytes(return_list, encoding='utf-8'))
    284         else:
    285             logger.error("client send show command,send 'ready' status fail")
    286     except Exception as e:
    287         logger.error(e)
    server
      1 #!/usr/bin/env python
      2 #coding=utf-8
      3 __author__ = 'yinjia'
      4 
      5 
      6 import os,sys
      7 sys.path.append(os.path.dirname(os.path.dirname(__file__)))
      8 from config import settings
      9 from dbhelper import dbapi
     10 from lib.common import Logger
     11 
     12 logger = Logger('user').getlog()
     13 
     14 """
     15 服务端用户信息类
     16 """
     17 
     18 class Users(object):
     19     def __init__(self,username):
     20         self.username = username
     21         self.password = ""
     22         self.totalspace = 0
     23         self.userspace = 0
     24         self.homepath = os.path.join(settings.USER_HOME_FOLDER, self.username)
     25         self.userpath = self.homepath
     26 
     27     def create_user(self):
     28         """
     29         创建用户
     30         :return: True:创建用户成功; False: 创建用户失败
     31         """
     32         args = dict(password=str(self.password), totalspace=str(self.totalspace), userspace=str(self.userspace))
     33         dbapi.AddOption(self.username, **args)
     34         self.__create_folder()
     35 
     36     def del_user(self):
     37         """
     38         删除用户
     39         :return: True;删除用户成功;False: 删除用户失败
     40         """
     41         dbapi.DelSections(self.username)
     42         self.__del_folder()
     43 
     44     def check_user(self):
     45         """
     46         判断用户是否存在
     47         :return:
     48         """
     49         if dbapi.CheckSections(self.username):
     50             return True
     51         return False
     52 
     53 
     54     def load_user_info(self):
     55         """
     56         加载用户信息,赋值给属性
     57         :return:
     58         """
     59         user_info = dbapi.load_info(self.username)
     60         self.password = user_info["password"]
     61         self.totalspace = int(user_info["totalspace"])
     62         self.userspace = int(user_info["userspace"])
     63         msg = "{0}|{1}|{2}".format(self.password, self.totalspace, self.userspace)
     64         return msg
     65 
     66     def __create_folder(self):
     67         """
     68         创建用户的目录
     69         :return:
     70         """
     71         os.mkdir(self.homepath)
     72 
     73     def __del_folder(self):
     74         """
     75         删除用户目录
     76         :return:
     77         """
     78         os.removedirs(self.homepath)
     79 
     80 
     81     def update_quota(self,filesize):
     82         """
     83         更新用户磁盘配额数据
     84         :param filesize: 上传文件大小
     85         :return: True: 更新磁盘配额成功;False:更新磁盘配额失败
     86         """
     87         if dbapi.CheckSections(self.username):
     88             self.userspace += filesize
     89             args = dict(userspace=str(self.userspace))
     90             dbapi.ModifyOption(self.username, **args)
     91             return True
     92         return False
     93 
     94     def update_down_quota(self,filesize):
     95         """
     96         用户删除文件情况,自动减少对应文件大小并更新用户磁盘配额空间
     97         :param filesize:
     98         :return:
     99         """
    100         if dbapi.CheckSections(self.username):
    101             self.userspace -= filesize
    102             args = dict(userspace=str(self.userspace))
    103             dbapi.ModifyOption(self.username, **args)
    104             return True
    105         return False
    user

    2、客户端

     1 #!/usr/bin/env python
     2 #coding=utf-8
     3 __author__ = 'yinjia'
     4 
     5 import os,sys
     6 sys.path.append(os.path.dirname(os.path.dirname(__file__)))
     7 from config import settings,template,code
     8 from lib import common
     9 from lib.client import client
    10 
    11 logger = common.Logger('ftpclient').getlog()
    12 
    13 
    14 def run():
    15     common.message(template.START_MENU,"INFO")
    16     common.message("正在连接服务器 {0}:{1}......".format(settings.FTP_SERVER_IP,settings.FTP_SERVER_PORT),"INFO")
    17 
    18     #创建对象
    19     client_obj = client(settings.FTP_SERVER_IP,settings.FTP_SERVER_PORT)
    20     #连接服务器,返回结果
    21     conn_result = client_obj.connect()
    22     if conn_result == code.CONN_SUCC:
    23         common.message("连接成功!", "INFO")
    24         #客户端登录
    25         login_result = client_obj.login()
    26         if login_result:
    27             exit_flag = False
    28             while not exit_flag:
    29                 show_menu = template.LOGINED_MENU.format(client_obj.username,
    30                                                          str(int(client_obj.totalspace / 1024 / 1024)),
    31                                                          str(int(client_obj.userspace / 1024 / 1024)))
    32                 common.message(show_menu,"INFO")
    33                 inp_command = common.input_command("[请输入命令]:")
    34                 if inp_command == "quit":
    35                     exit_flag = True
    36                 else:
    37                     #获取命令
    38                     func = inp_command.split("|")[0]
    39                     try:
    40                         if hasattr(client, func):
    41                             #从模块寻找到函数
    42                             target_func = getattr(client, func)
    43                             #执行函数
    44                             target_func(client_obj, inp_command)
    45                         else:
    46                             common.message("Client {0} 未找到".format(inp_command), "ERROR")
    47                     except Exception as e:
    48                         logger.error(e)
    49     else:
    50         common.message("连接失败!", "ERROR")
    ftpclient
      1 #!/usr/bin/env python
      2 #coding=utf-8
      3 __author__ = 'yinjia'
      4 
      5 
      6 import os,sys,socket
      7 sys.path.append(os.path.dirname(os.path.dirname(__file__)))
      8 from config import settings
      9 from config import code
     10 from lib import common
     11 
     12 logger = common.Logger('client').getlog()
     13 
     14 class client(object):
     15     def __init__(self,server_addr, server_port):
     16         self.username =""
     17         self.totalspace = 0
     18         self.userspace = 0
     19         self.client = socket.socket()
     20         self.__server = (server_addr, server_port)
     21 
     22     def connect(self):
     23         """
     24         客户端连接验证
     25         :return: 连接成功返回1000;连接失败返回1001
     26         """
     27         try:
     28             self.client.connect(self.__server)
     29             ret_bytes = self.client.recv(1024)
     30             #接收服务端消息
     31             ret_str = str(ret_bytes, encoding='utf-8')
     32             if ret_str == "OK":
     33                 return code.CONN_SUCC
     34             else:
     35                 return code.CONN_FAIL
     36         except Exception as e:
     37             logger.error(e)
     38 
     39     def check_auth(self,user, passwd):
     40         """
     41         客户端状态发送给服务端验证,并返回结果
     42         :param user: 用户名
     43         :param passwd: 密码
     44         :return:
     45         """
     46         sendmsg = "{cmd}|{user}|{passwd}".format(cmd="auth",
     47                                                  user=user,
     48                                                  passwd=passwd)
     49         self.client.sendall(bytes(sendmsg,encoding='utf-8'))
     50         ret_bytes = self.client.recv(1024)
     51         #获取服务端返回的认证状态信息
     52         auth_info = str(ret_bytes, encoding='utf-8')
     53         if auth_info == "success":
     54             self.username = user
     55             #获取服务端返回用户空间信息
     56             user_info = str(self.client.recv(1024),encoding='utf-8')
     57             self.totalspace = int(user_info.split("|")[0])
     58             self.userspace = int(user_info.split("|")[1])
     59             return code.AUTH_SUCC
     60         if auth_info == "user_error":
     61             return code.AUTH_USER_ERROR
     62         if auth_info == "fail":
     63             return code.AUTH_FAIL
     64 
     65     def login(self):
     66         while True:
     67             username = str(input("请输入用户名:")).strip()
     68             password = str(input("请输入密码:")).strip()
     69             #对密码进行md5加密
     70             password = common.md5(password)
     71             #登录认证
     72             auth_status = self.check_auth(username,password)
     73             if auth_status == code.AUTH_SUCC:
     74                 common.message(">>>>>>>登录成功","INFO")
     75                 return True
     76             elif auth_status == code.AUTH_USER_ERROR:
     77                 common.message(">>>>>>>用户名不存在","ERROR")
     78                 return False
     79             else:
     80                 common.message(">>>>>>>用户名或密码错误!","ERROR")
     81                 return False
     82 
     83     def mk(self,command):
     84         """
     85         创建目录
     86         :param command: 发送命令消息格式;mk|test或mk|/test/yj
     87         :return:
     88         """
     89         #发送命令消息给服务端
     90         self.client.sendall(bytes(command,encoding='utf-8'))
     91         #接收服务端发来的回应消息
     92         mk_msg = str(self.client.recv(1024), encoding='utf-8')
     93         mk_msg = int(mk_msg)
     94         if mk_msg == code.FILE_MK_SUCC:
     95             common.message(">>>>>>>创建目录成功","INFO")
     96         elif mk_msg == code.FILE_MK_FAIL:
     97             common.message(">>>>>>>创建目录失败","ERROR")
     98         else:
     99             common.message(">>>>>>>请输入文件夹名","ERROR")
    100 
    101     def delete(self,command):
    102         """
    103         删除目录或文件名
    104         :param command: delete|PycharmProjects/untitled/project/FTPv1/FTPServer/upload/admin/test/aa
    105         :return:
    106         """
    107         # 发送命令消息给服务端
    108         self.client.sendall(bytes(command, encoding='utf-8'))
    109         # 接收服务端发来的回应消息
    110         del_msg = str(self.client.recv(1024), encoding='utf-8')
    111         reve_status = int(del_msg.split("|")[0])
    112         reve_delfilename_fsize = int(del_msg.split("|")[1])
    113 
    114         if del_msg == code.FOLDER_DEL_SUCC:
    115             common.message(">>>>>>>删除目录成功","INFO")
    116         elif reve_status == code.FILE_DEL_SUCC:
    117             #更新用户空间配额大小
    118             self.userspace -= reve_delfilename_fsize
    119             common.message(">>>>>>>删除文件名成功","INFO")
    120         elif reve_status == code.FILE_DEL_FAIL:
    121             common.message(">>>>>>>删除目录或文件名失败","ERROR")
    122         elif reve_status == code.FILE_DEL_EMPTY:
    123             common.message(">>>>>>>当前目录下不是空目录,无法删除!","ERROR")
    124         else:
    125             common.message(">>>>>>>命令行请输入需要删除的路径目录或文件名!","ERROR")
    126 
    127     def cd(self,command):
    128         """
    129         切换目录路径
    130         :param command: cd|.. 或cd|foldername
    131         :return: 返回状态信息
    132         """
    133         # 发送命令消息给服务端
    134         self.client.sendall(bytes(command, encoding='utf-8'))
    135         # 接收服务端发来的回应消息
    136         cd_msg = str(self.client.recv(1024), encoding='utf-8')
    137         result_status,result_folder = cd_msg.split("|")
    138         if result_status == "0":
    139             result_value = "当前是根目录"
    140         elif result_status == "1":
    141             result_value = "目录已切换到:{0}".format(result_folder)
    142         elif result_status == "2":
    143             result_value = "切换失败, {0} 不是一个目录".format(result_folder)
    144         elif result_status == "3":
    145             result_value = "命令无效:{0}".format(result_folder)
    146         common.message(result_value,"INFO")
    147 
    148     def ls(self,*args):
    149         """
    150         显示客户端的文件列表详细信息
    151         :param args:
    152         :return: 返回文件列表
    153         """
    154         try:
    155             # 发送命令到服务端
    156             self.client.send(bytes("ls|", encoding='utf-8'))
    157             # 接收服务端发送结果的大小
    158             total_data_len = self.client.recv(1024).decode()
    159             # 收到了并发送一个ready标识给服务端
    160             self.client.send(bytes("ready", 'utf-8'))
    161 
    162             # 开始接收数据
    163             total_size = int(total_data_len)  # 文件总大小
    164             has_recv = 0  # 已经接收的文件大小
    165             exec_result = bytes("", 'utf8')
    166             while True:
    167                 # 如果文件总大小等于已经接收的文件大小,则退出
    168                 if total_size == has_recv:
    169                     break
    170                 data = self.client.recv(1024)
    171                 has_recv += len(data)
    172                 exec_result += data
    173             # 获取结果中文件及文件夹的数量
    174             return_result = str(exec_result, 'utf-8')
    175             file_count = int(return_result.split("|")[0])
    176             if file_count == 0:
    177                 return_result = "目前无上传记录"
    178             else:
    179                 return_result = return_result.split("|")[1]
    180             common.message(return_result,"INFO")
    181         except Exception as e:
    182             logger.error("client ls:{0}".format(e))
    183 
    184     def put(self,command):
    185         """
    186         上传文件
    187         :param command: put|folderfile
    188         :return:
    189         """
    190         file_name = command.split("|")[1]
    191         if os.path.isfile(file_name):
    192             filename = os.path.basename(file_name)
    193             fsize = os.stat(file_name).st_size
    194             fmd5 = common.md5sum(file_name)
    195 
    196             # 将基本信息发给服务端
    197             file_msg = "{cmd}|{file}|{filesize}|{filemd5}".format(cmd='put',
    198                                                                   file=filename,
    199                                                                   filesize=fsize,
    200                                                                   filemd5=fmd5)
    201             self.client.send(bytes(file_msg, encoding='utf8'))
    202             logger.info("send file info: {0}".format(file_msg))
    203             #接收来自服务端数据
    204             put_msg = str(self.client.recv(1024),encoding='utf-8')
    205             try:
    206                 #正常上传文件
    207                 if put_msg == "ok":
    208                     #判断是否超过用户空间配额
    209                     if self.userspace + fsize > self.totalspace:
    210                         common.message("用户磁盘空间不足,无法上传文件,请联系管理员!","ERROR")
    211                     else:
    212                         self.userspace += fsize
    213                         new_size = 0
    214                         with open(file_name,'rb') as f:
    215                             for line in f:
    216                                 self.client.sendall(line)
    217                                 new_size += len(line)
    218                                 # 打印上传进度条
    219                                 common.progress_bar(new_size,fsize)
    220                                 if new_size >= fsize:
    221                                     break
    222                 #断点续传文件
    223                 if put_msg.split("|")[0] == "continue":
    224                     send_size = int(put_msg.split("|")[1])
    225                     common.message("服务端存在此文件,但未上传完,开始断点续传......","INFO")
    226                     new_size = 0
    227                     with open(file_name,'rb') as f:
    228                         #用seek来进行文件指针的偏移,实现断点续传的功能
    229                         f.seek(send_size)
    230                         while fsize - send_size > 1024:
    231                             revedata = f.read(1024)
    232                             self.client.sendall(revedata)
    233                             new_size += len(revedata)
    234                             #打印上传进度条
    235                             common.progress_bar(new_size, fsize)
    236                         else:
    237                             revedata = f.read(fsize - send_size)
    238                             self.client.sendall(revedata)
    239                             # 打印上传进度条
    240                             common.progress_bar(new_size, fsize)
    241 
    242                 #不存在断点文件情况,询问是否覆盖掉原文件
    243                 if put_msg == "full":
    244                     inp_msg = common.message("服务端存在完整文件,是否覆盖掉原文件[输入y或n]:","INFO")
    245                     inp = str(input(inp_msg)).strip().lower()
    246                     if inp == "y":
    247                         with open(file_name, 'rb') as f:
    248                             new_size = 0
    249                             for line in f:
    250                                 self.client.sendall(line)
    251                                 new_size += len(line)
    252                                 #打印上传进度条
    253                                 common.progress_bar(new_size, fsize)
    254                                 if new_size >= fsize:
    255                                     break
    256                     elif inp == "n":
    257                         sys.exit()
    258                     else:
    259                        common.message("无效命令", "ERROR")
    260                 logger.info("upload file<{0}> successful".format(file_name))
    261                 common.message("文件上传成功", "INFO")
    262             except Exception as e:
    263                 logger.error("文件上传失败:{0}".format(e))
    264                 common.message("文件上传失败!", "ERROR")
    265         else:
    266             common.message("文件不存在!", "ERROR")
    267 
    268     def get(self,command):
    269         """
    270         下载文件
    271         :param command:
    272         :return:
    273         """
    274         return_result = ""
    275         # 发送基本信息到服务端 (command,username,file)
    276         self.client.send(bytes(command, encoding='utf-8'))
    277         # 先接收到命令是否正确标识,1 文件存在, 0 文件不存在
    278         ack_by_server = self.client.recv(1024)
    279         try:
    280             # 文件名错误,当前路径下找不到
    281             if str(ack_by_server, encoding='utf-8') == "0":
    282                 return_result = "
    当前目录下未找到指定的文件,请到存在目录下执行get操作!"
    283             else:
    284                 # 给服务端回应收到,防止粘包
    285                 self.client.send(bytes("ok", 'utf8'))
    286 
    287                 # 文件存在,开始接收文件基本信息(大小,文件名)
    288                 file_info = self.client.recv(1024).decode()
    289                 file_size = int(file_info.split("|")[0])
    290                 file_name = file_info.split("|")[1]
    291                 file_md5 = file_info.split("|")[2]
    292 
    293                 # 2 发送 ready 标识,准备开始接收文件
    294                 self.client.send(bytes("ready", 'utf8'))
    295 
    296                 # 3 开始接收数据了
    297                 has_recv = 0
    298                 with open(os.path.join(settings.DOWNLOAD_FILE_PATH, file_name), 'wb') as f:
    299                     while True:
    300                         # 如果文件总大小等于已经接收的文件大小,则退出
    301                         if file_size == has_recv:
    302                             break
    303                         data = self.client.recv(1024)
    304                         f.write(data)
    305                         has_recv += len(data)
    306                         # 打印下载进度条
    307                         common.progress_bar(has_recv, file_size)
    308                 return_result = "
    文件下载成功"
    309                 logger.info("download file<{0}> from server successful".format(file_name))
    310                 # md5文件验证
    311                 check_md5 = common.md5sum(os.path.join(settings.DOWNLOAD_FILE_PATH, file_name))
    312                 if check_md5 == file_md5:
    313                     logger.info("md5 check for file<{0}> succ!".format(file_name))
    314                     return_result += ", MD5 验证成功! "
    315                 else:
    316                     return_result += ", MD5 验证文件不匹配! "
    317             common.message(return_result,"INFO")
    318         except Exception as e:
    319             logger.error(e)
    client
      1 #!/usr/bin/env python
      2 #coding=utf-8
      3 __author__ = 'yinjia'
      4 
      5 
      6 import os,sys,logging,hashlib,time
      7 sys.path.append(os.path.dirname(os.path.dirname(__file__)))
      8 from config import settings
      9 
     10 class Logger(object):
     11     """
     12     日志记录,写入指定日志文件
     13     """
     14     def __init__(self,logger):
     15 
     16         create_time = time.strftime('%Y-%m-%d %H:%M:%S')
     17         format = '[%(name)s]:[%(asctime)s] [%(filename)s|%(funcName)s] [line:%(lineno)d] %(levelname)-8s: %(message)s'
     18 
     19         # 创建一个logger
     20         self.logger = logging.getLogger(logger)
     21         self.logger.setLevel(logging.INFO)
     22 
     23         # 创建一个handler,用于写入日志文件
     24         fp = logging.FileHandler(settings.LOGS)
     25 
     26         # 定义handler的输出格式formatter
     27         fpmatter = logging.Formatter(format)
     28         fp.setFormatter(fpmatter)
     29 
     30         # 给logging添加handler
     31         self.logger.addHandler(fp)
     32 
     33     def getlog(self):
     34         return self.logger
     35 
     36 
     37 def md5(arg):
     38     """
     39     密码进行md5加密
     40     :param arg: 用户的密码
     41     :return: 返回进行加密后的密码
     42     """
     43     result = hashlib.md5()
     44     result.update(arg.encode())
     45     return result.hexdigest()
     46 
     47 
     48 def md5sum(filename):
     49     """
     50     用于获取文件的md5值
     51     :param filename: 文件名
     52     :return: MD5码
     53     """
     54     if not os.path.isfile(filename):  # 如果校验md5的文件不是文件,返回空
     55         return False
     56     myhash = hashlib.md5()
     57     f = open(filename, 'rb')
     58     while True:
     59         b = f.read(1024)
     60         if not b:
     61             break
     62         myhash.update(b)
     63     f.close()
     64     return myhash.hexdigest()
     65 
     66 def message(msg,type):
     67     """
     68     根据不同的消息类型,打印出消息内容以不同的颜色显示
     69     :param msg: 消息内容
     70     :param type: 消息类型
     71     :return: 返回格式化后的消息内容
     72     """
     73     if type == "CRITICAL":
     74         show_msg = "
    33[1;33m{0}33[0m
    ".format(msg)
     75     elif type == "ERROR":
     76         show_msg = "
    33[1;31m{0}33[0m
    ".format(msg)
     77     elif type == "WARNING":
     78         show_msg = "
    33[1;32m{0}33[0m
    ".format(msg)
     79     elif type == "INFO":
     80         show_msg = "
    33[1;36m{0}33[0m
    ".format(msg)
     81     else:
     82         show_msg = "
    {0}
    ".format(msg)
     83     print(show_msg)
     84 
     85 def progress_bar(cache, totalsize):
     86     """
     87     打印进度条
     88     :param cache: 缓存字节大小
     89     :param totalsize: 文件总共字节
     90     :return:
     91     """
     92     ret = cache / totalsize
     93     num = int(ret * 100)
     94     view = '
    %d%% |%s' % (num, num * "*")
     95     sys.stdout.write(view)
     96     sys.stdout.flush()
     97 
     98 def input_command(msg):
     99     flag = False
    100     while not flag:
    101         command_list = ["put","get","ls","cd","mk","delete","quit"]
    102         command_inp = input(msg).strip()
    103         if command_inp == "ls":
    104             return_command = "{0}|".format(command_inp)
    105             flag = True
    106         elif command_inp == "quit":
    107             return_command = command_inp
    108             flag = True
    109         else:
    110             if command_inp.count("|") != 1:
    111                 message("输入命令不合法!","ERROR")
    112             else:
    113                 #获取命令
    114                 cmd,args = command_inp.strip().lower().split("|")
    115                 if cmd not in command_list:
    116                     message("输入命令不合法!", "ERROR")
    117                 else:
    118                     return_command = "{0}|{1}".format(cmd, args)
    119                     flag = True
    120     return return_command
    common
    •  部分效果展示图

  • 相关阅读:
    五大常用算法
    排序八 基数排序
    动态规划:从新手到专家(一)
    Linux Shell 文本处理工具集锦
    sleep与信号唤醒的问题 & 内核对信号的处理方式 & udelay
    《利用python进行数据分析》读书笔记--第九章 数据聚合与分组运算(一)
    《利用python进行数据分析》读书笔记--第八章 绘图和可视化
    《利用python进行数据分析》读书笔记--第七章 数据规整化:清理、转换、合并、重塑(三)
    《利用python进行数据分析》读书笔记--第七章 数据规整化:清理、转换、合并、重塑(二)
    k8s集群通过nginx-ingress做tcpudp 4层网络转发
  • 原文地址:https://www.cnblogs.com/yinjia/p/8683173.html
Copyright © 2011-2022 走看看