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()

三 分任务开发:

四 测试:

五 上线:

原文地址:https://www.cnblogs.com/2722127842qq-123/p/13669835.html