高级FTP
1. 用户加密认证
2. 多用户同时登陆
3. 每个用户有自己的家目录且只能访问自己的家目录
4. 对用户进行磁盘配额、不同用户配额可不同
5. 用户可以登陆server后,可切换目录
6. 查看当前目录下文件
7. 上传下载文件,保证文件一致性
8. 传输过程中现实进度条
9. 支持断点续传
6. **测试:**
----------
用户登陆
1.登录
2.注册
(输入编号,任意键退出:)1
请输入你的用户名(q返回):a
请输入你的密码:a
登陆成功!
(help查看命令)help
ls 显示当前目录
cd ../.. 切换目录
get filename 下载文件
put filename 上传文件
mkdir dirname 创建目录
qouta 查看剩余可用磁盘配额
----------
7. **备注:**
1.程序运行在client/bin/startclient.py和server/bin/startserver.py,请先运行server端,在运行client端
2.模拟的本地目录为client/db目录,下面有测试文件abcd
3.模拟的服务器用户家目录为db/home/$name目录
4.测试账号用户名密码为a,a,测试目录为a,下面有测试文件abc
server端主文件为:
1 #!/usr/bin/env python 2 # coding=utf-8 3 #Author:yang 4 import socketserver,hashlib 5 import json,os,sys 6 from conf import settings 7 8 class MyTCPHandler(socketserver.BaseRequestHandler): 9 10 user = None 11 home_dir = None 12 auth_dir = None 13 total_space = None 14 free_space = None 15 16 def handle(self): 17 while True: 18 try: 19 self.data = self.request.recv(1024).strip() 20 print("{} wrote:".format(self.client_address[0])) 21 print(self.data) 22 cmd_dic = json.loads(self.data.decode()) 23 action = cmd_dic["action"] 24 if hasattr(self,action): 25 func = getattr(self,action) 26 func(cmd_dic) 27 28 except ConnectionResetError as e: 29 print("err",e) 30 break 31 32 def auth(self,*args): 33 #验证登录 34 cmd_dic = args[0] 35 username = cmd_dic['username'] 36 password = cmd_dic['password'] 37 auth = os.path.join(settings.USER_AUTH,username) 38 if os.path.exists(auth): 39 with open(auth, 'r') as f: 40 user_dict = json.load(f) 41 print(user_dict) 42 if user_dict['password'] == password: 43 self.user = username 44 self.home_dir = os.path.join(settings.USER_HOME,username) 45 self.current_dir = self.home_dir 46 self.total_space = user_dict['quota'] 47 48 ret = [] 49 size = 0 50 for root, dirs, files in os.walk(self.home_dir): 51 for files_path in files: 52 ret.append(os.path.join(root, files_path)) 53 for i in ret: 54 if os.path.isfile(i): 55 size += os.path.getsize(i) 56 print(size) 57 self.free_space = int(int(self.total_space) - size) 58 if not os.path.exists(self.home_dir): 59 os.mkdir(self.home_dir) 60 self.request.sendall(bytes("yes", encoding='utf8')) 61 else: 62 self.request.sendall(bytes("no", encoding='utf8')) 63 else: 64 self.request.sendall(bytes("reg", encoding='utf8')) 65 66 def reg(self,*args): 67 #注册 68 cmd_dic = args[0] 69 username = cmd_dic['username'] 70 password = cmd_dic['password'] 71 quota = cmd_dic['quota'] 72 auth = os.path.join(settings.USER_AUTH, username) 73 if os.path.exists(auth): 74 self.request.sendall(bytes("no", encoding='utf8')) 75 else: 76 self.user = username 77 self.home_dir = os.path.join(settings.USER_HOME, username) 78 self.current_dir = self.home_dir 79 self.total_space = quota 80 os.mkdir(self.home_dir) 81 data = { 82 "username": username, 83 "password": password, 84 "quota": quota 85 } 86 with open(auth, 'w') as f: 87 json.dump(data,f) 88 self.request.sendall('yes'.encode('utf-8')) 89 90 def ls(self,*args): 91 #查看目录 92 msg = os.popen('dir'+ ' '+self.current_dir).read() 93 self.request.sendall(msg.encode('utf-8')) 94 95 def mkdir(self,*args): 96 #创建目录 97 cmd_dic = args[0] 98 dir = cmd_dic['filename'] 99 dirname = os.path.join(self.current_dir,dir) 100 if not os.path.isdir(dirname): 101 os.popen('mkdir' + ' ' + dirname) 102 self.request.sendall('yes'.encode('utf-8')) 103 else: 104 self.request.sendall('no'.encode('utf-8')) 105 106 def cd(self,*args): 107 #切换目录 108 cmd_dic = args[0] 109 dir = cmd_dic['filename'] 110 if dir == ' 'or dir == '..': 111 self.current_dir = self.home_dir 112 self.request.sendall('yes'.encode('utf-8')) 113 else: 114 dirname = os.path.join(self.current_dir, dir) 115 if os.path.isdir(dirname): 116 self.current_dir = dirname 117 self.request.sendall('yes'.encode('utf-8')) 118 119 else: 120 self.request.sendall('no'.encode('utf-8')) 121 122 def put(self,*args): 123 '''接收客户端文件''' 124 cmd_dic = args[0] 125 file = cmd_dic["filename"] 126 filesize = cmd_dic["size"] 127 filename = os.path.join(self.current_dir, file) 128 file_swap = filename + '.swap' 129 if not os.path.isfile(file_swap): 130 self.request.send('normal'.encode("utf-8")) 131 if filesize > self.free_space: 132 self.request.sendall('no'.encode('utf-8')) 133 else: 134 self.free_space = self.free_space - filesize 135 f = open(file_swap , "wb") 136 m = hashlib.md5() 137 self.request.send("ok".encode('utf-8')) 138 received_size = 0 139 while received_size < filesize: 140 data = self.request.recv(4096) 141 f.write(data) 142 m.update(data) 143 received_size += len(data) 144 percent = int(received_size / filesize * 100) 145 tag = "#" * int(percent / 2) 146 sys.stdout.write("[%s/%s]%s>%s\r" % (filesize, received_size, tag, percent)) 147 if percent == 100: 148 print('\n') 149 else: 150 new_file_md5 = m.hexdigest() 151 server_file_md5 = self.request.recv(4096) 152 if new_file_md5 == server_file_md5.decode(): 153 f.close() 154 os.rename(file_swap,filename) 155 print('文件下载完毕!') 156 else: 157 print("文件不一致!") 158 f.close() 159 else: 160 self.request.send('swap'.encode("utf-8")) 161 swap_file_size = 0 162 with open(file_swap,'r') as f: 163 m = hashlib.md5() 164 for line in f: 165 m.update(line) 166 swap_file_size += len(line) 167 swap_file_md5 = m.hexdigest() 168 file_data = { 169 'swap_size':swap_file_size, 170 'swap_md5': swap_file_md5 171 } 172 self.request.sendall(json.dumps(file_data).encode('utf-8')) 173 if self.request.recv(4096).decode() == 'yes': 174 # 断点续传开始 175 if filesize > self.free_space: 176 self.request.sendall('no'.encode('utf-8')) 177 else: 178 self.free_space = self.free_space - filesize 179 received_size = swap_file_size 180 while received_size < filesize: 181 f = open(file_swap, "ab") 182 data = self.request.recv(4096) 183 f.write(data) 184 received_size += len(data) 185 percent = int(received_size / filesize * 100) 186 tag = "#" * int(percent / 2) 187 sys.stdout.write("[%s/%s]%s>%s\r" % (filesize, received_size, tag, percent)) 188 if percent == 100: 189 print('\n') 190 else: 191 with open(file_swap, 'r') as f: 192 m = hashlib.md5() 193 for line in f: 194 m.update(line) 195 new_file_md5 = m.hexdigest() 196 server_file_md5 = self.request.recv(4096) 197 if new_file_md5 == server_file_md5.decode(): 198 f.close() 199 print('文件下载完毕!') 200 os.rename(file_swap, filename) 201 else: 202 print("文件不一致!") 203 f.close() 204 else: 205 os.remove(file_swap) 206 207 208 209 210 211 212 213 def get(self,*args): 214 '''发送给客户端文件''' 215 cmd_dic = args[0] 216 file = cmd_dic["filename"] 217 filename = os.path.join(self.current_dir,file) 218 server_response = self.request.recv(4096) 219 if server_response.decode() == 'normal': 220 if os.path.isfile(filename): 221 filesize = os.stat(filename).st_size 222 self.request.send(str(filesize).encode('utf-8')) 223 # 防止粘包,等服务器确认 224 print('filesize') 225 server_response = self.request.recv(4096) 226 print(server_response.decode()) 227 f = open(filename, "rb") 228 m = hashlib.md5() 229 for line in f: 230 m.update(line) 231 self.request.send(line) 232 else: 233 print("接收成功") 234 self.request.send(m.hexdigest().encode('utf-8')) 235 f.close() 236 else: 237 self.request.sendall('no'.encode('utf-8')) 238 if server_response.decode() == 'swap': 239 #断点续传 240 if server_response.decode() == 'swap': 241 file_data = json.loads(self.request.recv(4096).decode()) 242 243 with open(filename, "rb") as f: 244 received_size = 0 245 m = hashlib.md5() 246 for line in f: 247 m.update(line) 248 received_size += len(line) 249 if received_size == file_data['swap_size']: 250 if m.hexdigest() == file_data['swap_md5']: 251 self.request.send('yes'.encode('utf-8')) 252 break 253 else: 254 print('文件已经损坏,删除文件') 255 self.request.send('no'.encode('utf-8')) 256 break 257 server_response = self.request.recv(4096) 258 if server_response.decode() == 'no': 259 print('文件大小超出限额') 260 else: 261 f = open(filename, "rb") 262 f.seek(file_data['swap_size']) 263 received_size = file_data['swap_size'] 264 for line in f: 265 self.request.send(line) 266 received_size += len(line) 267 percent = int(received_size / filesize * 100) 268 tag = "#" * int(percent / 2) 269 sys.stdout.write("[%s/%s]%s>%s\r" % (filesize, received_size, tag, percent)) 270 if percent == 100: 271 print('\n') 272 else: 273 print('传输被中断') 274 else: 275 print("发送成功") 276 with open(filename, 'rb') as f: 277 m = hashlib.md5() 278 for line in f: 279 m.update(line) 280 self.request.send(m.hexdigest().encode('utf-8')) 281 f.close() 282 283 def free(self,*args): 284 self.request.sendall(str(self.free_space).encode('utf-8')) 285 286 287 288 def run(): 289 server = socketserver.ThreadingTCPServer((settings.HOST, settings.PORT), MyTCPHandler) 290 server.serve_forever()
server端配置文件为:
1 #!/usr/bin/env python 2 # coding=utf-8 3 #Author:yang 4 import os 5 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 6 BASE_DIR_DB = os.path.join(BASE_DIR,'db') 7 USER_HOME = os.path.join(BASE_DIR_DB,'home') 8 USER_AUTH = os.path.join(BASE_DIR_DB,'auth') 9 CLIENT_HOME = os.path.join(BASE_DIR_DB,'client') 10 11 HOST = 'localhost' 12 PORT = 9998
client端主文件为:
1 #!/usr/bin/env python 2 # coding=utf-8 3 #Author:yang 4 5 import socket 6 import json 7 import os,hashlib,sys 8 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 9 10 class FtpClient(object): 11 #client类 12 13 14 def __init__(self): 15 self.client = socket.socket() 16 self.dir = os.path.join(BASE_DIR,'db') 17 18 def help(self): 19 msg = ''' 20 ls 显示当前目录 21 cd ../.. 切换目录 22 get filename 下载文件 23 put filename 上传文件 24 mkdir dirname 创建目录 25 qouta 查看剩余可用磁盘配额 26 ''' 27 print(msg) 28 def connect(self,ip,port): 29 self.client.connect((ip, port)) 30 31 def interactive(self): 32 #交互界面 33 #self.authenticate() 34 while True: 35 cmd = input(">>(help查看命令)").strip() 36 if len(cmd) ==0:continue 37 cmd_str = cmd.split()[0] 38 if hasattr(self,"cmd_%s" % cmd_str): 39 func = getattr(self,"cmd_%s" % cmd_str) 40 func(cmd) 41 else: 42 self.help() 43 44 def cmd_qouta(self,*args): 45 #查询配额 46 msg_dic = { 47 "action": "free" 48 } 49 self.client.sendall(json.dumps(msg_dic).encode("utf-8")) 50 msg = self.client.recv(4096) 51 print(msg.decode()) 52 53 def cmd_ls(self,*args): 54 #查看当前目录下文件 55 msg_dic = { 56 "action": "ls" 57 } 58 self.client.sendall(json.dumps(msg_dic).encode("utf-8")) 59 msg = self.client.recv(4096) 60 print(msg.decode()) 61 62 def cmd_mkdir(self,*args): 63 #创建目录 64 cmd_split = args[0].split() 65 msg_dic = { 66 "action": "mkdir", 67 'filename':cmd_split[1] 68 } 69 self.client.sendall(json.dumps(msg_dic).encode("utf-8")) 70 msg = self.client.recv(4096) 71 if msg.decode() == 'yes': 72 print('创建成功') 73 else: 74 print('目录已经存在') 75 76 def cmd_cd(self, *args): 77 #切换目录 78 cmd_split = args[0].split() 79 msg_dic = { 80 "action": "cd", 81 'filename': cmd_split[1] 82 } 83 self.client.sendall(json.dumps(msg_dic).encode("utf-8")) 84 msg = self.client.recv(4096) 85 if msg.decode() == 'yes': 86 print('切换成功') 87 else: 88 print('目录不存在') 89 90 def cmd_login(self): 91 #登录 92 while True: 93 name = input("请输入你的用户名(q返回):") 94 if name.lower().strip() == 'q': 95 print('返回上一层成功!') 96 break 97 else: 98 password = input("请输入你的密码:") 99 m = hashlib.md5() 100 m.update(password.encode('utf-8')) 101 psw = m.hexdigest() 102 msg_dic = { 103 "action": "auth", 104 "username": name, 105 "password": psw, 106 } 107 self.client.sendall(json.dumps(msg_dic).encode("utf-8")) 108 msg = self.client.recv(4096) 109 if msg.decode() == 'yes': 110 print("登陆成功!") 111 self.interactive() 112 elif msg.decode() == 'no': 113 print("密码错误!") 114 elif msg.decode() == 'reg': 115 print('用户不存在,请注册!') 116 else: 117 print("未知错误!") 118 119 def cmd_reg(self): 120 #注册 121 while True: 122 name = input("请输入你需要注册的用户名(q返回):") 123 if name.lower().strip() == 'q': 124 print('返回上一层成功!') 125 break 126 else: 127 password = input("请输入你要注册的密码:") 128 quota = input("请输入你要申请的磁盘配额():") 129 m = hashlib.md5() 130 m.update(password.encode('utf-8')) 131 psw = m.hexdigest() 132 msg_dic = { 133 "action": "reg", 134 "username": name, 135 "password": psw, 136 "quota":quota 137 } 138 self.client.sendall(json.dumps(msg_dic).encode("utf-8")) 139 msg = self.client.recv(4096) 140 if msg.decode() == 'yes': 141 print("注册成功!") 142 self.interactive() 143 elif msg.decode() == 'no': 144 print("用户名已经存在!") 145 else: 146 print("未知错误!") 147 148 def cmd_put(self,*args): 149 #上传文件 150 cmd_split = args[0].split() 151 if len(cmd_split) >1: 152 file = cmd_split[1] 153 filename = os.path.join(self.dir,cmd_split[1]) 154 if os.path.isfile(filename): 155 filesize = os.stat(filename).st_size 156 msg_dic = { 157 "action": "put", 158 "filename":file, 159 "size": filesize, 160 } 161 self.client.send( json.dumps(msg_dic).encode("utf-8") ) 162 print("send",json.dumps(msg_dic).encode("utf-8") ) 163 server_response = self.client.recv(4096) 164 if server_response.decode() == 'normal': 165 server_response = self.client.recv(4096) 166 if server_response.decode() == 'no': 167 print('文件大小超出限额') 168 else: 169 f = open(filename,"rb") 170 m = hashlib.md5() 171 received_size = 0 172 for line in f: 173 m.update(line) 174 self.client.send(line) 175 len(line) 176 received_size += len(line) 177 percent = int(received_size / filesize * 100) 178 tag = "#" * int(percent / 2) 179 sys.stdout.write("[%s/%s]%s>%s\r" % (filesize, received_size, tag, percent)) 180 if percent == 100: 181 print('\n') 182 else: 183 print("发送成功") 184 self.client.send(m.hexdigest().encode('utf-8')) 185 f.close() 186 #断点续传 187 if server_response.decode() == 'swap': 188 file_data = json.loads(self.client.recv(4096).decode()) 189 190 with open(filename, "rb") as f: 191 received_size = 0 192 m = hashlib.md5() 193 for line in f: 194 m.update(line) 195 received_size += len(line) 196 if received_size == file_data['swap_size']: 197 if m.hexdigest() == file_data['swap_md5']: 198 self.client.send('yes'.encode('utf-8')) 199 break 200 else: 201 print('文件已经损坏,删除文件') 202 self.client.send('no'.encode('utf-8')) 203 break 204 server_response = self.client.recv(4096) 205 if server_response.decode() == 'no': 206 print('文件大小超出限额') 207 else: 208 f = open(filename, "rb") 209 f.seek(file_data['swap_size']) 210 received_size = file_data['swap_size'] 211 for line in f: 212 self.client.send(line) 213 received_size += len(line) 214 percent = int(received_size / filesize * 100) 215 tag = "#" * int(percent / 2) 216 sys.stdout.write("[%s/%s]%s>%s\r" % (filesize, received_size, tag, percent)) 217 if percent == 100: 218 print('\n') 219 else: 220 print("发送成功") 221 with open(filename, 'rb') as f: 222 m = hashlib.md5() 223 for line in f: 224 m.update(line) 225 self.client.send(m.hexdigest().encode('utf-8')) 226 f.close() 227 else: 228 print(file,"文件不存在") 229 230 def cmd_get(self,*args): 231 #下载文件 232 cmd_split = args[0].split() 233 msg_dic = { 234 "action": "get", 235 "filename": cmd_split[1], 236 } 237 238 self.client.send(json.dumps(msg_dic).encode("utf-8")) 239 filename = os.path.join(self.dir, cmd_split[1]) 240 file_swap = filename + '.swap' 241 if not os.path.isfile(file_swap): 242 self.client.send('normal'.encode("utf-8")) 243 filesize = self.client.recv(4096) 244 if filesize.isdigit() == False: 245 print('文件不存在') 246 else: 247 filesize = int(filesize) 248 f = open(file_swap, "wb") 249 m = hashlib.md5() 250 self.client.send("ok".encode('utf-8')) 251 received_size = 0 252 while received_size < filesize: 253 data = self.client.recv(4096) 254 f.write(data) 255 m.update(data) 256 received_size += len(data) 257 percent = int(received_size / filesize * 100) 258 tag = "#" * int(percent / 2) 259 sys.stdout.write("[%s/%s]%s>%s\r" % (filesize, received_size, tag, percent)) 260 if percent == 100: 261 print('\n') 262 else: 263 new_file_md5 = m.hexdigest() 264 server_file_md5 = self.client.recv(4096) 265 if new_file_md5 == server_file_md5.decode(): 266 f.close() 267 os.rename(file_swap,filename) 268 print('文件下载完毕!') 269 else: 270 print("文件不一致!") 271 f.close() 272 else: 273 #断点续传 274 self.client.send('swap'.encode("utf-8")) 275 swap_file_size = 0 276 with open(file_swap, 'rb') as f: 277 m = hashlib.md5() 278 for line in f: 279 m.update(line) 280 swap_file_size += len(line) 281 swap_file_md5 = m.hexdigest() 282 file_data = { 283 'swap_size': swap_file_size, 284 'swap_md5': swap_file_md5 285 } 286 self.client.sendall(json.dumps(file_data).encode('utf-8')) 287 if self.client.recv(4096).decode() == 'yes': 288 # 断点续传开始 289 filesize = int(self.client.recv(4096).decode()) 290 received_size = swap_file_size 291 while received_size < filesize: 292 f = open(file_swap, "ab") 293 data = self.client.recv(4096) 294 f.write(data) 295 received_size += len(data) 296 percent = int(received_size / filesize * 100) 297 tag = "#" * int(percent / 2) 298 sys.stdout.write("[%s/%s]%s>%s\r" % (filesize, received_size, tag, percent)) 299 if percent == 100: 300 print('\n') 301 else: 302 with open(file_swap, 'r') as f: 303 m = hashlib.md5() 304 for line in f: 305 m.update(line) 306 new_file_md5 = m.hexdigest() 307 server_file_md5 = self.client.recv(4096) 308 if new_file_md5 == server_file_md5.decode(): 309 os.rename(file_swap, filename) 310 print('文件下载完毕!') 311 else: 312 print("文件不一致!") 313 f.close() 314 else: 315 os.remove(file_swap) 316 print('文件损坏') 317 318 def run(): 319 ftp = FtpClient() 320 #实例化 321 ftp.connect("localhost",9998) 322 #链接server端 323 while True: 324 menu = ''' 325 1.登录 326 2.注册 327 ''' 328 print(menu) 329 choice = input('>>(输入编号,任意键退出:)') 330 if choice == '1': 331 ftp.cmd_login() 332 if choice == '2': 333 ftp.cmd_reg() 334 else: 335 exit('退出成功!')
浙公网安备 33010602011771号