• FTP项目


    FTP目录

    服务端

    conf

      settings.py

     1 # _*_ coding: utf-8 _*_
     2 #服务端配置信息
     3 
     4 import os
     5 import sys
     6 import socket
     7 
     8 BASE_DIR = os.path.dirname(os.path.dirname(
     9     os.path.abspath(__file__)))
    10 sys.path.append(BASE_DIR)
    11 
    12 ACCOUNTS_FILE = os.path.join(BASE_DIR, 'conf', 'accounts.ini')
    13 
    14 address_family = socket.AF_INET
    15 socket_type = socket.SOCK_STREAM
    16 
    17 # 绑定的IP地址:
    18 BIND_HOST = '127.0.0.1'
    19 
    20 # 绑定的端口:
    21 BIND_PORT = 8080
    22 
    23 ip_port = (BIND_HOST, BIND_PORT)
    24 # server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    25 # server.bind(('127.0.0.1',8080))
    26 
    27 coding = 'utf-8'
    28 
    29 listen_count = 8
    30 
    31 max_recv_bytes = 1024
    32 
    33 allow_reuser_address = False
    34 
    35 # server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    36 # server.bind(('127.0.0.1',8080))
    37 # server.listen(8)
    38 # b=server.recv(1024)
    39 # allow_reouser_address=False
    settings

    core

      ftp_server.py

      1 # _*_ coding: utf-8 _*_
      2 #服务端ftp
      3 
      4 import socket
      5 import struct
      6 import json
      7 import os
      8 import pickle
      9 import subprocess
     10 import hashlib
     11 
     12 from conf import settings
     13 from core.user_handler import UserHandle
     14 
     15 
     16 class FTPServer():
     17 
     18     def __init__(self, server_address, bind_and_listen=True):
     19         self.server_address = server_address
     20         self.socket = socket.socket(settings.address_family, settings.socket_type)
     21         if bind_and_listen:
     22             try:
     23                 self.server_bind()
     24                 self.server_listen()
     25             except Exception:
     26                 self.server_close()
     27 
     28     def server_bind(self):
     29         allow_reuse_address = False
     30         if allow_reuse_address:
     31             self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
     32         self.socket.bind(self.server_address)
     33 
     34     def server_listen(self):
     35         self.socket.listen(settings.listen_count)
     36 
     37     def server_close(self):
     38         self.socket.close()
     39 
     40     def server_accept(self):
     41         return self.socket.accept()
     42 
     43     def conn_close(self, conn):
     44         conn.close()
     45 
     46     def getfile_md5(self):
     47         '''获取文件的md5'''
     48         return hashlib.md5(self.readfile()).hexdigest()
     49 
     50     def readfile(self):
     51         '''读取文件,得到文件内容的bytes类型'''
     52         with open(self.file_path, 'rb') as f:
     53             filedata = f.read()
     54         return filedata
     55 
     56     def send_filedata(self, exist_file_size=0):
     57         """下载时,将文件打开,send(data)"""
     58         with open(self.file_path, 'rb') as f:
     59             f.seek(exist_file_size)
     60             while True:
     61                 data = f.read(1024)
     62                 if data:
     63                     self.conn.send(data)
     64                 else:
     65                     break
     66 
     67     def get(self, cmds):
     68         '''
     69        下载,首先查看文件是否存在,然后上传文件的报头大小,上传文件,以读的方式发开文件
     70        找到下载的文件
     71     发送 header_size
     72     发送 header_bytes file_size
     73     读文件 rb 发送 send(line)
     74     若文件不存在,发送0 client提示:文件不存在
     75        :param cmds:
     76        :return:
     77                '''
     78         if len(cmds) > 1:
     79             filename = cmds[1]
     80             self.file_path = os.path.join(os.getcwd(), filename)
     81             if os.path.isfile(self.file_path):
     82                 file_size = os.path.getsize(self.file_path)
     83                 obj = self.conn.recv(4)
     84                 exist_file_size = struct.unpack('i', obj)[0]
     85                 header = {
     86                     'filename': filename,
     87                     'filemd5': self.getfile_md5(),
     88                     'file_size': file_size
     89                 }
     90                 header_bytes = pickle.dumps(header)
     91                 self.conn.send(struct.pack('i', len(header_bytes)))
     92                 self.conn.send(header_bytes)
     93                 if exist_file_size:  # 表示之前被下载过 一部分
     94                     if exist_file_size != file_size:
     95                         self.send_filedata(exist_file_size)
     96                     else:
     97                         print('33[31;1mbreakpoint and file size are the same33[0m')
     98                 else:  # 文件第一次下载
     99                     self.send_filedata()
    100             else:
    101                 print('33[31;1merror33[0m')
    102                 self.conn.send(struct.pack('i', 0))
    103 
    104         else:
    105             print("33[31;1muser does not enter file name33[0m")
    106 
    107     def recursion_file(self, dir):
    108         """递归查询用户目录下的所有文件,算出文件的大小"""
    109         res = os.listdir(dir)
    110         for i in res:
    111             path = os.path.join(dir, i)
    112             if os.path.isdir(path):
    113                 self.recursion_file(path)
    114             elif os.path.isfile(path):
    115                 self.home_bytes_size += os.path.getsize(path)
    116 
    117     def current_home_size(self):
    118         """得到当前用户目录的大小,字节/M"""
    119         self.home_bytes_size = 0
    120         self.recursion_file(self.homedir_path)
    121         home_m_size = round(self.home_bytes_size / 1024 / 1024, 1)
    122 
    123     def put(self, cmds):
    124         """从client上传文件到server当前工作目录下
    125         """
    126         if len(cmds) > 1:
    127             obj = self.conn.recv(4)
    128             state_size = struct.unpack('i', obj)[0]
    129             if state_size == 0:
    130                 print("33[31;1mfile does not exist!33[0m")
    131             else:
    132                 # 算出了home下已被占用的大小self.home_bytes_size
    133                 self.current_home_size()
    134                 header_bytes = self.conn.recv(struct.unpack('i', self.conn.recv(4))[0])
    135                 header_dic = pickle.loads(header_bytes)
    136                 filename = header_dic.get('filename')
    137                 file_size = header_dic.get('file_size')
    138                 file_md5 = header_dic.get('file_md5')
    139                 self.file_path = os.path.join(os.getcwd(), filename)
    140                 if os.path.exists(self.file_path):
    141                     self.conn.send(struct.pack('i', 1))
    142                     has_size = os.path.getsize(self.file_path)
    143                     if has_size == file_size:
    144                         print("33[31;1mfile already does exist!33[0m")
    145                         self.conn.send(struct.pack('i', 0))
    146                     else:
    147                         print('33[31;1mLast file not finished,this time continue33[0m')
    148                         self.conn.send(struct.pack('i', 1))
    149                         if self.home_bytes_size + int(file_size - has_size) > self.quota_bytes:
    150                             print('33[31;1mSorry exceeding user quotas33[0m')
    151                             self.conn.send(struct.pack('i', 0))
    152                         else:
    153                             self.conn.send(struct.pack('i', 1))
    154                             self.conn.send(struct.pack('i', has_size))
    155                             with open(self.file_path, 'ab') as f:
    156                                 f.seek(has_size)
    157                                 self.write_file(f, has_size, file_size)
    158                             self.verification_filemd5(file_md5)
    159                 else:
    160                     self.conn.send(struct.pack('i', 0))
    161                     print('33[31;1mSorry file does not exist now first put33[0m')
    162                     if self.home_bytes_size + int(file_size) > self.quota_bytes:
    163                         print('33[31;1mSorry exceeding user quotas33[0m')
    164                         self.conn.send(struct.pack('i', 0))
    165                     else:
    166                         self.conn.send(struct.pack('i', 1))
    167                         with open(self.file_path, 'wb') as f:
    168                             recv_size = 0
    169                             self.write_file(f, recv_size, file_size)
    170                         self.verification_filemd5(file_md5)
    171 
    172         else:
    173             print("33[31;1muser does not enter file name33[0m")
    174 
    175     def write_file(self, f, recv_size, file_size):
    176         '''上传文件时,将文件内容写入到文件中'''
    177         while recv_size < file_size:
    178             res = self.conn.recv(settings.max_recv_bytes)
    179             f.write(res)
    180             recv_size += len(res)
    181             self.conn.send(struct.pack('i', recv_size))  # 为了进度条的显示
    182 
    183     def verification_filemd5(self, filemd5):
    184         # 判断文件内容的md5
    185         if self.getfile_md5() == filemd5:
    186             print('33[31;1mCongratulations download success33[0m')
    187             self.conn.send(struct.pack('i', 1))
    188         else:
    189             print('33[31;1mSorry download failed33[0m')
    190             self.conn.send(struct.pack('i', 0))
    191 
    192     def ls(self, cmds):
    193         '''查看当前工作目录下,先返回文件列表的大小,在返回查询的结果'''
    194         print("33[34;1mview current working directory33[0m")
    195         subpro_obj = subprocess.Popen('dir', shell=True,
    196                                       stdout=subprocess.PIPE,
    197                                       stderr=subprocess.PIPE)
    198         stdout = subpro_obj.stdout.read()
    199         stderr = subpro_obj.stderr.read()
    200         self.conn.send(struct.pack('i', len(stdout + stderr)))
    201         self.conn.send(stdout)
    202         self.conn.send(stderr)
    203 
    204     def mkdir(self, cmds):
    205         '''增加目录
    206         在当前目录下,增加目录
    207         1.查看目录名是否已经存在
    208         2.增加目录成功,返回 1
    209         2.增加目录失败,返回 0'''
    210         print("33[34;1madd working directory33[0m")
    211         if len(cmds) > 1:
    212             mkdir_path = os.path.join(os.getcwd(), cmds[1])
    213             if not os.path.exists(mkdir_path):
    214                 os.mkdir(mkdir_path)
    215                 print('33[31;1mCongratulations add directory success33[0m')
    216                 self.conn.send(struct.pack('i', 1))
    217             else:
    218                 print("33[31;1muser directory already does exist33[0m")
    219                 self.conn.send(struct.pack('i', 0))
    220         else:
    221             print("33[31;1muser does not enter file name33[0m")
    222 
    223     def cd(self, cmds):
    224         '''切换目录
    225         1.查看是否是目录名
    226         2.拿到当前目录,拿到目标目录,
    227         3.判断homedir是否在目标目录内,防止用户越过自己的home目录 eg: ../../....
    228         4.切换成功,返回 1
    229         5.切换失败,返回 0'''
    230         print("33[34;1mSwitch working directory33[0m")
    231         if len(cmds) > 1:
    232             dir_path = os.path.join(os.getcwd(), cmds[1])
    233             if os.path.isdir(dir_path):
    234                 # os.getcwd 获取当前工作目录
    235                 previous_path = os.getcwd()
    236                 # os.chdir改变当前脚本目录
    237                 os.chdir(dir_path)
    238                 target_dir = os.getcwd()
    239                 if self.homedir_path in target_dir:
    240                     print('33[31;1mCongratulations switch directory success33[0m')
    241                     self.conn.send(struct.pack('i', 1))
    242                 else:
    243                     print('33[31;1mSorry switch directory failed33[0m')
    244                     # 切换失败后,返回到之前的目录下
    245                     os.chdir(previous_path)
    246                     self.conn.send(struct.pack('i', 0))
    247             else:
    248                 print('33[31;1mSorry switch directory failed,the directory is not current directory33[0m')
    249                 self.conn.send(struct.pack('i', 0))
    250         else:
    251             print("33[31;1muser does not enter file name33[0m")
    252 
    253     def remove(self, cmds):
    254         """删除指定的文件,或者空文件夹
    255                1.删除成功,返回 1
    256                2.删除失败,返回 0
    257                """
    258         print("33[34;1mRemove working directory33[0m")
    259         if len(cmds) > 1:
    260             file_name = cmds[1]
    261             file_path = os.path.join(os.getcwd(), file_name)
    262             if os.path.isfile(file_path):
    263                 os.remove(file_path)
    264                 self.conn.send(struct.pack('i', 1))
    265             elif os.path.isdir(file_path):  # 删除空目录
    266                 if not len(os.listdir(file_path)):
    267                     os.removedirs(file_path)
    268                     print('33[31;1mCongratulations remove success33[0m')
    269                     self.conn.send(struct.pack('i', 1))
    270                 else:
    271                     print('33[31;1mSorry remove directory failed33[0m')
    272                     self.conn.send(struct.pack('i', 0))
    273             else:
    274                 print('33[31;1mSorry remove directory failed33[0m')
    275                 self.conn.send(struct.pack('i', 0))
    276         else:
    277             print("33[31;1muser does not enter file name33[0m")
    278 
    279     def get_recv(self):
    280         '''从client端接收发来的数据'''
    281         return pickle.loads(self.conn.recv(settings.max_recv_bytes))
    282 
    283     def handle_data(self):
    284         '''处理接收到的数据,主要是将密码转化为md5的形式'''
    285         user_dic = self.get_recv()
    286         username = user_dic['username']
    287         password = user_dic['password']
    288         md5_obj = hashlib.md5()
    289         md5_obj.update(password)
    290         check_password = md5_obj.hexdigest()
    291 
    292     def auth(self):
    293         '''
    294         处理用户的认证请求
    295         1,根据username读取accounts.ini文件,然后查看用户是否存在
    296         2,将程序运行的目录从bin.user_auth修改到用户home/username方便之后查询
    297         3,把客户端返回用户的详细信息
    298         :return:
    299         '''
    300         while True:
    301             user_dic = self.get_recv()
    302             username = user_dic['username']
    303             password = user_dic['password']
    304             md5_obj = hashlib.md5(password.encode('utf-8'))
    305             check_password = md5_obj.hexdigest()
    306             user_handle = UserHandle(username)
    307             # 判断用户是否存在 返回列表,
    308             user_data = user_handle.judge_user()
    309             if user_data:
    310                 if user_data[0][1] == check_password:
    311                     self.conn.send(struct.pack('i', 1))  # 登录成功返回 1
    312                     self.homedir_path = os.path.join(settings.BASE_DIR, 'home', username)
    313                     # 将程序运行的目录名修改到 用户home目录下
    314                     os.chdir(self.homedir_path)
    315                     # 将用户配额的大小从M 改到字节
    316                     self.quota_bytes = int(user_data[2][1]) * 1024 * 1024
    317                     user_info_dic = {
    318                         'username': username,
    319                         'homedir': user_data[1][1],
    320                         'quota': user_data[2][1]
    321                     }
    322                     # 用户的详细信息发送到客户端
    323                     self.conn.send(pickle.dumps(user_info_dic))
    324                     return True
    325                 else:
    326                     self.conn.send(struct.pack('i', 0))  # 登录失败返回 0
    327             else:
    328                 self.conn.send(struct.pack('i', 0))  # 登录失败返回 0
    329 
    330     def server_link(self):
    331         print("33[31;1mwaiting client .....33[0m")
    332         while True:  # 链接循环
    333             self.conn, self.client_addr = self.server_accept()
    334             while True:  # 通信循环
    335                 try:
    336                     self.server_handle()
    337                 except Exception:
    338                     break
    339             self.conn_close(self.conn)
    340 
    341     def server_handle(self):
    342         '''处理与用户的交互指令'''
    343         if self.auth():
    344             print("33[32;1m-------user authentication successfully-------33[0m")
    345             res = self.conn.recv(settings.max_recv_bytes)
    346             # 解析命令,提取相应的参数
    347             cmds = res.decode(settings.coding).split()
    348             if hasattr(self, cmds[0]):
    349                 func = getattr(self, cmds[0])
    350                 func(cmds)
    ftp_server

      main.py

     1 # _*_ coding: utf-8 _*_
     2 
     3 
     4 from core.user_handler import UserHandle
     5 from core.ftp_server import FTPServer
     6 from conf import settings
     7 
     8 
     9 class Manager():
    10     '''
    11     主程序,包括启动server,创建用户,退出
    12     :return:
    13     '''
    14 
    15     def start_ftp(self):
    16         '''启动server端'''
    17         server = FTPServer(settings.ip_port)
    18         server.server_link()
    19         server.close()
    20 
    21     def create_user(self):
    22         '''创建用户,执行创建用户的类'''
    23         username = input("33[32;1mplease input your username>>>33[0m").strip()
    24         UserHandle(username).add_user()
    25 
    26     def logout(self):
    27         '''
    28         退出登陆
    29         :return:
    30         '''
    31         print("33[32;1m-------Looking forward to your next login-------33[0m")
    32         exit()
    33 
    34     def interactive(self):
    35         '''交互函数'''
    36         msg = '''33[32;1m
    37                        1   启动ftp服务端
    38                        2   创建用户
    39                        3   退出
    40                33[0m'''
    41         menu_dic = {
    42             "1": 'start_ftp',
    43             "2": 'create_user',
    44             "3": 'logout',
    45         }
    46         exit_flag = False
    47         while not exit_flag:
    48             print(msg)
    49             user_choice = input("Please input a command>>>").strip()
    50             if user_choice in menu_dic:
    51                 getattr(self, menu_dic[user_choice])()
    52             else:
    53                 print("33[31;1myou choice doesn't exist33[0m")
    main

      user_handler.py

     1 # _*_ coding: utf-8 _*_
     2 
     3 
     4 import configparser
     5 import hashlib
     6 import os
     7 
     8 from conf import settings
     9 
    10 
    11 class UserHandle():
    12     '''
    13     创建用户名称,密码
    14     如果用户存在,则返回,如果用户不存在,则注册成功
    15     '''
    16 
    17     def __init__(self, username):
    18         self.username = username
    19         self.config = configparser.ConfigParser()
    20         self.config.read(settings.ACCOUNTS_FILE)
    21 
    22     @property
    23     def password(self):
    24         '''生成用户的默认密码 '''
    25         password_inp = input("33[32;1mplease input your password>>>33[0m").strip()
    26         md5_obj = hashlib.md5()
    27         md5_obj.update(password_inp.encode())
    28         md5_password = md5_obj.hexdigest()
    29         return md5_password
    30 
    31     @property
    32     def disk_quota(self):
    33         '''生成每个用户的磁盘配额'''
    34         quota = input('33[32;1mplease input Disk quotas>>>:33[0m').strip()
    35         if quota.isdigit():
    36             return quota
    37         else:
    38             exit('33[31;1mdisk quotas must be integer33[0m')
    39 
    40     def add_user(self):
    41         """创建用户,存到accounts.ini"""
    42         if not self.config.has_section(self.username):
    43             print('33[31;1mcreating username is :%s 33[0m' % self.username)
    44             self.config.add_section(self.username)
    45             self.config.set(self.username, 'password', self.password)
    46             self.config.set(self.username, 'homedir', 'home/' + self.username)
    47             self.config.set(self.username, 'quota', self.disk_quota)
    48             with open(settings.ACCOUNTS_FILE, 'w') as f:
    49                 self.config.write(f)
    50             os.mkdir(os.path.join(settings.BASE_DIR, 'home', self.username))  # 创建用户的home文件夹
    51             print('33[1;32msuccessfully create userdata33[0m')
    52         else:
    53             print('33[1;31musername already existing33[0m')
    54 
    55     def judge_user(self):
    56         """判断用户是否存在"""
    57         if self.config.has_section(self.username):
    58             return self.config.items(self.username)
    user_handler

    start.py

     1 # _*_ coding: utf-8 _*_
     2 #服务端启动入口
     3 
     4 
     5 import os
     6 import sys
     7 
     8 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
     9 sys.path.append(BASE_DIR)
    10 
    11 from core import ftp_server
    12 from core import main
    13 from conf import settings
    14 
    15 if __name__ == '__main__':
    16     a = main.Manager()
    17     a.interactive()
    server_start

    客户端

    CONF

      settings.py

     1 # _*_ coding: utf-8 _*_
     2 #存放客户端配置信息
     3 
     4 
     5 import os
     6 import sys
     7 import socket
     8 
     9 BASE_DIR = os.path.dirname(os.path.dirname(
    10     os.path.abspath(__file__)))
    11 sys.path.append(BASE_DIR)
    12 
    13 # 下载的文件存放路径
    14 down_filepath = os.path.join(os.path.dirname(
    15     os.path.dirname(os.path.abspath(__file__))), 'download')
    16 
    17 # 上传的文件存放路径
    18 upload_filepath = os.path.join(os.path.dirname(
    19     os.path.dirname(os.path.abspath(__file__))), 'upload')
    20 
    21 # 绑定的IP地址
    22 BIND_HOST = '127.0.0.1'
    23 
    24 # 绑定的端口号
    25 BIND_PORT = 8080
    26 
    27 ip_port = (BIND_HOST, BIND_PORT)
    28 
    29 address_family = socket.AF_INET
    30 
    31 socket_type = socket.SOCK_STREAM
    32 
    33 
    34 coding = 'utf-8'
    35 
    36 listen_count = 8
    37 
    38 max_recv_bytes = 1024
    39 
    40 allow_reuser_address = False
    41 
    42 
    43 # client.listen(8)
    44 # client.recv(1024)
    45 # allow_reuser_address = False
    settings

    CORE

      ftp_client.py

      1 # _*_ coding: utf-8 _*_
      2 #存放客户端视图代码
      3 
      4 import socket
      5 import struct
      6 import json
      7 import os
      8 import sys
      9 import pickle
     10 import hashlib
     11 
     12 from conf import settings
     13 
     14 
     15 class FTPClient:
     16 
     17     def __init__(self, server_address, connect=True):
     18         self.server_address = server_address
     19         self.socket = socket.socket(settings.address_family, settings.socket_type)
     20         if connect:
     21             try:
     22                 self.client_connect()
     23             except Exception:
     24                 self.client_close()
     25 
     26     def client_connect(self):
     27         try:
     28             self.socket.connect(self.server_address)
     29         except Exception as e:
     30             print("33[31;1merror:%s33[0m" % e)
     31             exit("33[31;1m
    The server is not activated 33[0m")
     32 
     33     def client_close(self):
     34         self.socket.close()
     35 
     36     def readfile(self):
     37         '''读取文件'''
     38         with open(self.file_path, 'rb') as f:
     39             filedata = f.read()
     40         return filedata
     41 
     42     def appendfile_content(self, file_path, temp_file_size, file_size):
     43         '''追加文件内容'''
     44         with open(file_path, 'ab') as f:
     45             f.seek(temp_file_size)
     46             get_size = temp_file_size
     47             while get_size < file_size:
     48                 res = self.socket.recv(settings.max_recv_bytes)
     49                 f.write(res)
     50                 get_size += len(res)
     51                 self.progress_bar(1, get_size, file_size)  # 1表示下载
     52 
     53     def getfile_md5(self):
     54         '''对文件内容进行加密,也就是保持文件的一致性'''
     55         md5 = hashlib.md5(self.readfile())
     56         print("md5是:
    ", md5.hexdigest())
     57         return md5.hexdigest()
     58 
     59     def progress_bar(self, num, get_size, file_size):
     60         float_rate = float(get_size) / float(file_size)
     61         rate_num = round(float_rate * 100, 2)
     62         if num == 1:  # 1表示下载
     63             sys.stdout.write('33[31;1m
    finish downloaded perentage:{0}%33[0m'.format(rate_num))
     64         elif num == 2:  # 2表示上传
     65             sys.stdout.write('33[31;1m
    finish uploaded perentage:{0}%33[0m'.format(rate_num))
     66         sys.stdout.flush()
     67 
     68     def recv_file_header(self, header_size):
     69         """接收文件的header, filename file_size file_md5"""
     70         header_types = self.socket.recv(header_size)
     71         header_dic = pickle.loads(header_types)
     72         print(header_dic, type(header_dic))
     73         total_size = header_dic['file_size']
     74         filename = header_dic['filename']
     75         filemd5 = header_dic['filemd5']
     76         return (filename, total_size, filemd5)
     77 
     78     def verification_filemd5(self, filemd5):
     79         # 判断下载下来的文件MD5值和server传过来的MD5值是否一致
     80         if self.getfile_md5() == filemd5:
     81             print('33[31;1mCongratulations download success33[0m')
     82         else:
     83             print('33[31;1mSorry download failed,download again support breakpoint continuation33[0m')
     84 
     85     def write_file(self, f, get_size, file_size):
     86         '''下载文件,将内容写入文件中'''
     87         while get_size < file_size:
     88             res = self.socket.recv(settings.max_recv_bytes)
     89             f.write(res)
     90             get_size += len(res)
     91             self.progress_bar(1, get_size, file_size)  # 1表示下载
     92 
     93     def get(self, cmds):
     94         """从server下载文件到client
     95         """
     96         if len(cmds) > 1:
     97             filename = cmds[1]
     98             self.file_path = os.path.join(settings.down_filepath, filename)
     99             if os.path.isfile(self.file_path):  # 如果文件存在,支持断电续传
    100                 temp_file_size = os.path.getsize(self.file_path)
    101                 self.socket.send(struct.pack('i', temp_file_size))
    102                 header_size = struct.unpack('i', self.socket.recv(4))[0]
    103                 if header_size:
    104                     filename, file_size, filemd5 = self.recv_file_header(header_size)
    105                     if temp_file_size == file_size:
    106                         print('33[34;1mFile already does exist33[0m')
    107                     else:
    108                         print('33[34;1mFile now is breakpoint continuation33[0m')
    109                         self.appendfile_content(self.file_path, temp_file_size)
    110                         self.verification_filemd5(filemd5)
    111                 else:
    112                     print("33[34;1mFile was downloaded before,but now server's file is not exist33[0m")
    113             else:  # 如果文件不存在,则是直接下载
    114                 self.socket.send(struct.pack('i', 0))
    115                 obj = self.socket.recv(1024)
    116                 header_size = struct.unpack('i', obj)[0]
    117                 if header_size == 0:
    118                     print("33[31;1mfile does not exist!33[0m")
    119                 else:
    120                     filename, file_size, filemd5 = self.recv_file_header(header_size)
    121                     download_filepath = os.path.join(settings.down_filepath, filename)
    122                     with open(download_filepath, 'wb') as f:
    123                         get_size = 0
    124                         self.write_file(f, get_size, file_size)
    125                     self.verification_filemd5(filemd5)
    126         else:
    127             print("33[31;1muser does not enter file name33[0m")
    128 
    129     def ls(self, cmds):
    130         '''查看当前工作目录,文件列表'''
    131         print("33[34;1mview current working directory33[0m")
    132         obj = self.socket.recv(4)
    133         dir_size = struct.unpack('i', obj)[0]
    134         recv_size = 0
    135         recv_bytes = b''
    136         while recv_size < dir_size:
    137             temp_bytes = self.socket.recv(settings.max_recv_bytes)
    138             recv_bytes += temp_bytes
    139             recv_size += len(temp_bytes)
    140         print(recv_bytes.decode('gbk'))
    141 
    142     def mkdir(self, cmds):
    143         '''增加目录
    144         1,server返回1 增加成功
    145         2,server返回2 增加失败'''
    146         print("33[34;1madd working directory33[0m")
    147         obj = self.socket.recv(4)
    148         res = struct.unpack('i', obj)[0]
    149         if res:
    150             print('33[31;1mCongratulations add directory success33[0m')
    151         else:
    152             print('33[31;1mSorry add directory failed33[0m')
    153 
    154     def cd(self, cmds):
    155         '''切换目录'''
    156         print("33[34;1mSwitch working directory33[0m")
    157         if len(cmds) > 1:
    158             obj = self.socket.recv(4)
    159             res = struct.unpack('i', obj)[0]
    160             if res:
    161                 print('33[31;1mCongratulations switch directory success33[0m')
    162             else:
    163                 print('33[31;1mSorry switch directory failed33[0m')
    164         else:
    165             print("33[31;1muser does not enter file name33[0m")
    166 
    167     def remove(self, cmds):
    168         '''表示删除文件或空文件夹'''
    169         print("33[34;1mRemove working directory33[0m")
    170         obj = self.socket.recv(4)
    171         res = struct.unpack('i', obj)[0]
    172         if res:
    173             print('33[31;1mCongratulations remove success33[0m')
    174         else:
    175             print('33[31;1mSorry remove directory failed33[0m')
    176 
    177     def open_sendfile(self, file_size, recv_size=0):
    178         '''打开要上传的文件(由于本程序上传文件的原理是先读取本地文件,再写到上传地址的文件)'''
    179 
    180         with open(self.file_path, 'rb') as f:
    181             # send_bytes = b''
    182             # send_size = 0
    183             f.seek(recv_size)
    184             while True:
    185                 data = f.read(1024)
    186                 if data:
    187                     self.socket.send(data)
    188                     obj = self.socket.recv(4)
    189                     recv_size = struct.unpack('i', obj)[0]
    190                     self.progress_bar(2, recv_size, file_size)
    191                 else:
    192                     break
    193         success_state = struct.unpack('i', self.socket.recv(4))[0]
    194         if success_state:
    195             print('33[31;1mCongratulations upload success33[0m')
    196         else:
    197             print('33[31;1mSorry upload directory failed33[0m')
    198 
    199     def put_situation(self, file_size, condition=0):
    200         '''上传的时候有两种情况,文件已经存在,文件不存在'''
    201         quota_state = struct.unpack('i', self.socket.recv(4))[0]
    202         if quota_state:
    203             if condition:
    204                 obj = self.socket.recv(4)
    205                 recv_size = struct.unpack('i', obj)[0]
    206                 self.open_sendfile(file_size, recv_size)
    207             else:
    208                 self.open_sendfile(file_size)
    209         else:
    210             print('33[31;1mSorry exceeding user quotas33[0m')
    211 
    212     def put(self, cmds):
    213         """往server端登录的用户目录下上传文件
    214         """
    215         if len(cmds) > 1:
    216             filename = cmds[1]
    217             self.file_path = os.path.join(settings.upload_filepath, filename)
    218             if os.path.isfile(self.file_path):  # 如果文件存在,支持断电续传
    219                 self.socket.send(struct.pack('i', 1))
    220                 file_size = os.path.getsize(self.file_path)
    221                 header_dic = {
    222                     'filename': os.path.basename(filename),
    223                     'file_md5': self.getfile_md5(),
    224                     'file_size': file_size
    225                 }
    226                 header_bytes = pickle.dumps(header_dic)
    227                 self.socket.send(struct.pack('i', len(header_bytes)))
    228                 self.socket.send(header_bytes)
    229                 state = struct.unpack('i', self.socket.recv(4))[0]
    230                 if state:  # 已经存在
    231                     has_state = struct.unpack('i', self.socket.recv(4))[0]
    232                     if has_state:
    233                         self.put_situation(file_size, 1)
    234                     else:  # 存在的大小 和文件大小一致 不必再传
    235                         print("33[31;1mfile already does exist!33[0m")
    236                 else:  # 第一次传
    237                     self.put_situation(file_size)
    238             else:  # 文件不存在
    239                 print("33[31;1mfile does not exist!33[0m")
    240                 self.socket.send(struct.pack('i', 0))
    241         else:
    242             print("33[31;1muser does not enter file name33[0m")
    243 
    244     def get_recv(self):
    245         '''从client端接受发来的数据'''
    246         return pickle.loads(self.socket.recv(settings.max_recv_bytes))
    247 
    248     def login(self):
    249         '''
    250         登陆函数,当登陆失败超过三次,则退出
    251         用户密码发送到server短
    252         接受server端返回的信息,如果成功返回1,失败返回0
    253         :return: 如果用户账号密码正确,则返回用户数据的字典
    254         '''
    255         retry_count = 0
    256         while retry_count < 3:
    257             username = input('33[34;1mplease input Username:33[0m').strip()
    258             if not username:
    259                 continue
    260             password = input('33[34;1mplease input Password:33[0m').strip()
    261             user_dic = {
    262                 'username': username,
    263                 'password': password
    264             }
    265             # 将用户信息发送到客户端,然后接受客户端的数据
    266             data = pickle.dumps(user_dic)
    267             self.socket.send(pickle.dumps(user_dic))
    268             # 为了防止出现黏包问题,所以先解压报头,读取报头,再读数据
    269             obj = self.socket.recv(4)
    270             res = struct.unpack('i', obj)[0]
    271             # 此处,如果返回的是代码4001,则成功 4002则失败
    272             if res:
    273                 print("33[32;1m-----------------welcome to ftp client-------------------33[0m")
    274                 user_info_dic = self.get_recv()
    275                 recv_username = user_info_dic['username']
    276                 return True
    277             else:
    278                 print("33[31;1mAccount or Passwordoes not correct!33[0m")
    279         retry_count += 1
    280 
    281     def execute(self):
    282         '''
    283         执行,或者实施
    284         :return:
    285         '''
    286         if self.login():
    287             while True:
    288                 try:
    289                     self.help_info()
    290                     inp = input("Please input a command>>>").strip()
    291                     if not inp:
    292                         continue
    293                     self.socket.send(inp.encode(settings.coding))
    294                     cmds = inp.split()
    295                     if hasattr(self, cmds[0]):
    296                         func = getattr(self, cmds[0])
    297                         func(cmds)
    298                         break
    299                     else:
    300                         print('33[31;1mNo such command ,please try again33[0m')
    301                 except Exception as e:  # server关闭了
    302                     print('33[31;1m%s33[0m' % e)
    303                     break
    304 
    305     def help_info(self):
    306         print('''33[34;1m
    307               get + (文件名)    表示下载文件
    308               put + (文件名)    表示上传文件
    309               ls                 表示查询当前目录下的文件列表(只能访问自己的文件列表)
    310               mkdir + (文件名)  表示创建文件夹 
    311               cd + (文件名)     表示切换目录(只能在自己的文件列表中切换)
    312               remove + (文件名) 表示删除文件或空文件夹
    313         33[0m''')
    ftp_client

    START.PY

     1 # _*_ coding: utf-8 _*_
     2 #客户端启动入口代码
     3 
     4 import os
     5 import sys
     6 
     7 BASE_DIR = os.path.dirname(os.path.dirname(
     8     os.path.abspath(__file__)))
     9 sys.path.append(BASE_DIR)
    10 
    11 from core import ftp_client
    12 from conf import settings
    13 
    14 if __name__ == '__main__':
    15     run = ftp_client.FTPClient(settings.ip_port)
    16     run.execute()
    start.py

    README.md

    项目说明书

    作者:wuchangwen

    项目:FTP

    项目需求:

    开发一个支持多用户在线的FTP程序

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

    项目分析:

    1. 对于此项目,最初的想法是写出上传,和下载文件的程序,包括客户端和服务端。
    2. 在此基础上扩展程序,包括提出开始程序到bin里面,配置文件在config里面
    3. 然后把上传文件和下载文件的程序进行断点续传的程序重构
    4. 在此基础上,对文件进行加密
    5. 增加功能,包括设置进度条,增加查看功能,增加目录功能,删除文件功能,切换目录功能等
    6. 然后再设置磁盘分配功能,完善内容
    7. 然后添加用户登陆,包括对用户的密码加密等功能
    8. 写完后检查程序
    

    主要功能:

    1.用户认证功能
    2.用户登录功能
    3.查看目录功能
    4.配置磁盘空间功能
    5.切换功能
    6.查看文件
    7.上传功能
    8.下载功能
    9.设置进度条
    10.断点续存
    

    一 需求分析:

    1.拿到项目,会先在客户那里一起讨论需求,
    商量项目的功能是否能实现,周期与价格,得到一个需求文档。
    
    2.最后在公司内部需要开一次会议,最终得到一个开发文档,
    交给不同岗位的程序员进行开发。
    - Python: 后端,爬虫
    
        - 不同的岗位:
            - UI界面设计:
                - 设计软件的布局,会分局软件的外观切成一张张图片。
            
            - 前端:
                - 拿到UI交给他的图片,然后去搭建网页面。
                - 设计一些页面中,哪些位置需要接收数据,需要进行数据交互。
            
            - 后端:
                - 直接核心的业务逻辑,调度数据库进行数据的增删查改。
            
            - 测试:
                - 会给代码进行全面测试,比如压力测试,界面测试(CF的卡箱子BUG)。
            
            - 运维:
                - 部署项目。
    

    二 程序的架构设计:

    1、程序设计的好处
        1) 思路清晰
        2) 不会出现写一半代码时推翻重写
        3) 方便自己或以后的同事更好维护
    2、三层架构设计的好处
        1) 把每个功能都分层三部分,逻辑清晰
        2) 如果用户更换不同的用户界面或不同,
        的数据储存机制都不会影响接口层的核心
        逻辑代码,扩展性强。
        3) 可以在接口层,准确的记录日志与流水。
    3、程序架构
        服务端
        客户端
    
     4.数据处理
        - 保存数据  save()
        - 查看数据  select()
        - 更新数据  update()
        - 删除数据  delete()
    

    三 分任务开发:

    四 测试:

    五 上线:

  • 相关阅读:
    mysql数据库引擎myisam与innodb
    Java观察者模式的理解
    线程安全的单例模式
    线程相关
    java 线程读写锁
    原子变量AtomicInteger
    接口文档管理,版本管理工具,阿里RAP的windows下部署
    谷歌浏览器报错:跨域问题处理( Access-Control-Allow-Origin)_ 用于本地测试的快捷解决方法
    mysql bin-log日志记录
    阿里RDS中插入emoji 表情插入失败的解决方案
  • 原文地址:https://www.cnblogs.com/2722127842qq-123/p/13669835.html
Copyright © 2020-2023  润新知