原文地址:http://www.itnose.net/detail/6754889.html
高级FTP服务器
1. 用户加密认证
2. 多用户同时登陆
3. 每个用户有自己的家目录且只能访问自己的家目录
4. 对用户进行磁盘配额、不同用户配额可不同
5. 用户可以登陆server后,可切换目录
6. 查看当前目录下文件
7. 上传下载文件,保证文件一致性
8. 传输过程中现实进度条
9.支持断点续传
10.用户操作日志 服务端 启动参数 start
客户端 启动参数 -s localhost -P 9500 程序结构:
seniorFTP/#综合目录
|- - -ftp_client/#客户端程序目录
| |- - -__init__.py
| |- - -bin/#启动目录
| | |- - -__init__.py
| | |- - -client_ftp.py#客户端视图启动
| |
| |- - -cfg/#配置目录
| | |- - -__init__.py
| | |- - -config.py#配置文件
| |
| |- - -down/#下载文件目录
| |
| |- - -putfile/#上传文件目录
| |
| |
| |- - -REDMAE
|- - -ftp_server/#服务端程序目录
| |- - -__init__.py
| |- - -bin/#启动目录
| | |- - -__init__.py
| | |- - -start.py#服务端视图启动
| | |- - -user_reg.py#用户注册启动
| |
| |- - -cfg/#配置目录
| | |- - -__init__.py
| | |- - -config.py#配置文件
| | |- - -userpwd.cfg#用户信息文件
| |
| |- - -core/#文件目录
| | |- - -__init__.py
| | |- - -ftp_server.py#服务端主要逻辑 类
|           |      |- - -logs.py#日志主要逻辑 类
|           |      |- - -main.py#服务端启动主程序
| |
| |- - -home/#用户文件目录
| | |- - -用户/#个人目录
| |
| |- - -log/#日志文件目录
| |
| |- - -REDMAE
| |
|
|- - -REDMAE 先上流程图:

详细代码如下:

|- - -ftp_client/#客户端程序目录
| |- - -__init__.py
| |- - -bin/#启动目录
| | |- - -__init__.py
| | |- - -client_ftp.py#客户端视图启动

  1 #!usr/bin/env python
2 #-*-coding:utf-8-*-
3 # Author calmyan
4 import socket,os,json,getpass,hashlib
5 import os ,sys,optparse
6
7 STATUS_CODE={
8 240:'格式出错,格式:{"action":"get","filename":"filename","size":100}',
9 241:'指令错误',
10 242:'用户密码出错',
11 243:'用户或密码出错',
12 244:'用户密码通过校验',
13 }
14 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量
15 sys.path.append(BASE_DIR)#增加环境变量
16 from cfg import config
17 class FTPClient(object):
18 def __init__(self):
19 paresr=optparse.OptionParser()
20 paresr.add_option('-s','--server',dest='server',help='服务器地址')
21 paresr.add_option('-P','--port',type="int",dest='port',help='服务器端口')
22 paresr.add_option('-u','--username',dest='username',help='用户名')
23 paresr.add_option('-p','--password',dest='password',help='密码')
24 (self.options,self.args)=paresr.parse_args()#返回一个字典与列表的元组
25 self.verify_args(self.options,self.args)#判断参数
26 self.ser_connect()#连接服务端
27 self.cmd_list=config.CMD_LIST
28 self.rat=0#文件断点
29
30 #实例化一个连接端
31 def ser_connect(self):
32 self.c=socket.socket()#实例化一个连接端
33 self.c.connect((self.options.server,self.options.port))#进行连接
34
35 #判断用户与密码是否成对出现
36 def verify_args(self,options,args):
37 if (options.username is None and options.password is None) or (options.username is not None and options.password is not None):#判断用户与密码是否成对出现
38 pass##判断用户与密码单个出现
39 else:
40 exit('出错:请输入用户与密码!')#退出
41 if options.server and options.port:#端口判断
42 if options.port>0 and options.port<65535:
43 return True
44 else:
45 print('端口号:[%s]错误,端口范围:0-65535'%options.port)
46
47 #登陆方法
48 def landing(self):#登陆方法
49 '''用户验证'''
50 if self.options.username is not None:#判断用户名已经输入
51 #print(self.options.username,self.options.password)
52 return self.get_user_pwd(self.options.username,self.options.password)#返回结果
53 else:
54 print('用户登陆'.center(60,'='))
55 ret_count=0#验证次数
56 while ret_count<5:
57 username=input('用户名:').strip()
58 password=getpass.getpass('密码:').strip()
59 if self.get_user_pwd(username,password):
60 return self.get_user_pwd(username,password)#调用远程验证用户 返回结果
61 else:
62 ret_count+=1#次数加一
63 print('认证出错次数[%s]'%ret_count)
64 else:
65 print('密码出错次数过多!')
66 exit()
67
68 #'''用户名与密码检验'''
69 def get_user_pwd(self,username,password):
70 '''用户名与密码检验'''
71 #发送 头文件
72 data={
73 'action':'auth',
74 'username':username,
75 'password':password
76 }
77 self.c.send(json.dumps(data).encode())#发送到服务器
78 response = self.get_response()#得到服务端的回复
79 if response.get('status_code') == 244:
80 print(STATUS_CODE[244])
81 self.user = username#存下用户名
82 self.user_dir=response.get('dir')#目录
83 return True
84 else:
85 print(response.get("status_msg") )
86
87 #服务器回复
88 def get_response(self):#服务器回复
89 '''服务器回复信息'''
90 data=self.c.recv(1024)#接收回复
91 data = json.loads(data.decode())
92 return data
93
94 #指令帮助
95 def help(self):#指令帮助
96 attr='''
97 help 指令帮助
98 ----------------------------------
99 info 个人信息
100 ----------------------------------
101 ls 查看当前目录(linux/windows)
102 ----------------------------------
103 pwd 查看当前路径(linux/windows)
104 ----------------------------------
105 cd 目录 切换目录(linux/windows)
106 ----------------------------------
107 get filename 下载文件
108 ----------------------------------
109 put filename 上传文件
110 ----------------------------------
111 --md5 使用md5 在get/put 后
112 ----------------------------------
113 mkdir name 创建目录(linux/windows)
114 ----------------------------------
115 rmdir name 删除目录(linux/windows)
116 ----------------------------------
117 rm filename 删除文件 (linux/windows)
118 ----------------------------------
119 exit 退出
120 ----------------------------------
121 '''.format()
122 print(attr)
123
124 ##交互
125 def inter(self):#交互
126 if self.landing():#通过用户密码认证
127 print('指令界面'.center(60,'='))
128 self.help()
129 while True:
130 cmd = input('[%s]-->指令>>>:'%self.user_dir).strip()
131 if len(cmd)==0:continue#输入空跳过
132 if cmd=='exit':exit()#退出指令
133 cmd_str=cmd.split()#用空格分割 取命令到列表
134 #print(cmd_str)
135 #print(len(cmd_str))
136 if len(cmd_str)==1 and cmd_str[0] in self.cmd_list:#如果是单个命令 并且在命令列表中
137 #if len(cmd_str)==1:#如果是单个命令 并且在命令列表中
138 if cmd_str[0]==config.HELP:
139 self.help()
140 continue
141 func=getattr(self,'cmd_compr')#调用此方法
142 ret=func(cmd_str)
143 if ret:
144 continue
145 else:
146 pass
147 elif len(cmd_str)>1:
148 if hasattr(self,'cmd_%s'%cmd_str[0]):#判断类中是否有此方法
149 func=getattr(self,'cmd_%s'%cmd_str[0])#调用此方法
150 func(cmd_str)#执行
151 continue
152 else:
153 print('指令出错!')
154 self.help()#
155
156 #'''是否要md5'''
157 def cmd_md5_(self,cmd_list):
158 '''是否要md5'''
159 if '--md5' in cmd_list:
160 return True
161
162 #进度条
163 def show_pr(self,total):#进度条
164 received_size = 0 #发送的大小
165 current_percent = 0 #
166 while received_size < total:
167 if int((received_size / total) * 100 ) > current_percent :
168 print("#",end="",flush=True)#进度显示
169 current_percent = int((received_size / total) * 100 )
170 new_size = yield #断点跳转 传入的大小
171 received_size += new_size
172
173 #单个命令
174 def cmd_compr(self,cmd_str,**kwargs):
175 mag_dict={
176 "action":"compr",
177 'actionname':cmd_str[0]
178 }
179 self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送数据
180 cmd_res_attr=self.get_response()#得到服务器的回复
181 if type(cmd_res_attr) is not int:#如果不int 类型
182 if cmd_res_attr["status_code"] ==241:#命令不对
183 print(cmd_res_attr['status_msg'])
184 return
185 if cmd_res_attr["status_code"] ==240:#命令不对
186 print(cmd_res_attr['status_msg'])
187 return
188 size_l=0#收数据当前大小
189 self.c.send('准备好接收了,可以发了'.encode('utf-8'))
190 receive_data= ''.encode()
191 while size_l< cmd_res_attr:
192 data=self.c.recv(1024)#开始接收数据
193 size_l+=len(data)#加上
194 receive_data += data
195 else:
196 receive_data=receive_data.decode()
197 try:
198 receive_data=eval(receive_data)#转为列表 或字典
199 except Exception as e:
200 pass
201 if type(receive_data) is dict:#如果是字典
202 for i in receive_data:
203 print(i,receive_data[i])
204 return 1
205 if type(receive_data) is list:#如果是列表
206 for i in receive_data:
207 print(i)
208 return 1
209 print(receive_data)
210 return 1
211
212 #切换目录
213 def cmd_cd(self,cmd_list,**kwargs):
214 '''切换目录'''
215 mag_dict={
216 "action":"cd",
217 'actionname':cmd_list[1]
218 }
219 self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送数据
220 msg_l=self.c.recv(1024)#接收数据 消息
221 data=json.loads(msg_l.decode())
222 if data["status_code"] ==251:#目录不可切换
223 print(data['status_msg'])
224 return
225 elif data["status_code"] ==252:#目录可以换
226 print(data['status_msg'])
227 self.c.send(b'1')#发送到服务器,表示可以了
228 data=self.c.recv(1024)
229 print(data.decode())
230 user_dir=data.decode()
231 print(user_dir)
232 self.user_dir=user_dir
233 return
234 elif data["status_code"] ==256:#目录不存在
235 print(data['status_msg'])
236 return
237
238 #删除文件
239 def cmd_rm(self,cmd_list,**kwargs):
240 mag_dict={
241 "action":"rm",
242 'filename':cmd_list[1]
243 }
244 self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送文件信息
245 data=self.get_response()#得到服务器的回复
246 if data["status_code"] ==245:#文件不存在
247 print(data['status_msg'])
248 #print('删除前空间:',data['剩余空间'])
249 return
250 elif data["status_code"] ==254:#文件删除完成
251 print(data['status_msg'])
252 print('删除前空间:',data['剩余空间'])
253 pass
254 self.c.send(b'1')#发送到服务器,表示可以
255 data=self.get_response()#得到服务器的回复
256 if data["status_code"] ==255:#文件删除完成
257 print(data['status_msg'])
258 print('删除后空间:',data['剩余空间'])
259 return
260
261 #创建目录
262 def cmd_mkdir(self,cmd_list,**kwargs):
263 mag_dict={
264 "action":"mkdir",
265 'filename':cmd_list[1]
266 }
267 self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送文件信息
268 data=self.get_response()#得到服务器的回复
269 if data["status_code"] ==257:#目录已经存在
270 print(data['status_msg'])
271 return
272 elif data["status_code"] ==256:#目录创建中
273 print(data['目录'])
274 pass
275 self.c.send(b'1')#发送到服务器,表示可以
276 data=self.get_response()#得到服务器的回复
277 if data["status_code"] ==258:#目录创建中完成
278 print(data['status_msg'])
279 return
280 pass
281
282 #删除目录
283 def cmd_rmdir(self,cmd_list,**kwargs):
284 mag_dict={
285 "action":"rmdir",
286 'filename':cmd_list[1]
287 }
288 self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送文件信息
289 data=self.get_response()#得到服务器的回复
290 if data["status_code"] ==256:#目录不存在
291 print(data['status_msg'])
292 return
293 elif data["status_code"] ==260:#目录不为空
294 print(data['status_msg'])
295 print(data['目录'])
296 return
297 elif data["status_code"] ==257:#目录删除中
298 print(data['目录'])
299 pass
300 self.c.send(b'1')#发送到服务器,表示可以
301 data=self.get_response()#得到服务器的回复
302 if data["status_code"] ==259:#目录删除完成
303 print(data['status_msg'])
304 return
305 pass
306
307 #上传方法
308 def cmd_put(self,cmd_list,**kwargs):#上传方法
309 if len(cmd_list) > 1:
310 filename=cmd_list[1]#取文件名
311 filename_dir=config.PUT_DIR+filename#拼接文件名路径
312
313 if os.path.isfile(filename_dir):#是否是一个文件
314 filesize=os.stat(filename_dir).st_size#获取文件大小
315 #执行行为 名字,大小,是否
316 mag_dict={
317 "action":"put",
318 'filename':filename,
319 'size':filesize,
320 'overridden':True,
321 'md5':False
322 }
323 if self.cmd_md5_(cmd_list):#判断是否进行MD5
324 mag_dict['md5'] = True
325 self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送文件信息
326 data=self.get_response()#得到服务器的回复
327 if data["status_code"] ==250:#磁盘空间不足
328 print(data['status_msg'])
329 print(mag_dict['size'])
330 return
331 if data["status_code"] ==249:#磁盘空间足够
332 print(data['status_msg'])
333 print('剩余空间',data['剩余空间'])
334 self.c.send(b'1')#发送到服务器,表示可以上传文件了
335 data=self.get_response()#得到服务器的回复
336 if data["status_code"] ==230:#断点续传
337 print(data['status_msg'])
338 print(data['文件大小'])
339 self.rat=data['文件大小']#文件指针位置
340 pass
341 elif data["status_code"] ==231:#非断点续传
342 print(data['status_msg'])
343 self.rat=0#文件指针位置
344 pass
345 f=open(filename_dir,'rb')#打开文件
346 f.seek(self.rat)#移动到位置
347 print(mag_dict['md5'])
348 self.c.send(b'1')#发送到服务器,表示可以上传文件了
349 if mag_dict['md5']==True:
350 md5_obj = hashlib.md5()#定义MD5
351 progress = self.show_pr(mag_dict['size']) #进度条 传入文件大小
352 progress.__next__()
353 while self.rat<filesize:
354 line=f.read(1024)
355 self.c.send(line)
356 try:
357 progress.send(len(line))#传入当前数据大小
358 except StopIteration as e:
359 print("100%")
360 break
361 md5_obj.update(line)#计算MD5
362
363 else:
364 print(filename,'发送完成!')
365 f.close()
366 md5_val = md5_obj.hexdigest()
367 md5_from_server = self.get_response()#服务端的MD5
368 if md5_from_server['status_code'] == 248:
369 if md5_from_server['md5'] == md5_val:
370 print("%s 文件一致性校验成功!" % filename)
371 return
372 else:
373 progress = self.show_pr(mag_dict['size']) #进度条 传入文件大小
374 progress.__next__()
375 #for line in f:
376 while self.rat<filesize:
377 line=f.read(1024)
378 self.c.send(line)
379 try:
380 progress.send(len(line))#传入当前数据大小
381 except StopIteration as e:
382 print("100%")
383 break
384 #print(line)
385 else:
386 print(filename,'发送完成!')
387 f.close()
388 return
389 else:
390 print(filename,'文件不存在!')
391
392 #下载方法
393 def cmd_get(self,cmd_list,**kwargs):#下载方法
394 #cmd_split= args[0].split()#指令解析
395 # if len(cmd_list) == 1:
396 # print("没有输入文件名.")
397 # return
398 #down_filename = cmd_list[1].split('/')[-1]#文件名
399 down_filename=cmd_list[1]#取文件名
400 file_path='%s/%s'%(config.GET_DIR,down_filename)#拼接文件路径 用户down目录
401 if os.path.isfile(file_path):#文件是否存
402 filesize=os.stat(file_path).st_size#获取文件大小
403 name_down=True
404 else:
405 filesize=0
406 name_down=False
407 mag_dict={
408 "action":"get",
409 'filename':cmd_list[1],
410 'name_down':name_down,
411 'size':filesize
412 }
413 if self.cmd_md5_(cmd_list):#判断是否进行MD5
414 mag_dict['md5'] = True
415 self.c.send(json.dumps(mag_dict).encode())#发送
416 self.c.send(b'1')#发送到服务器,防粘包
417
418 response = self.get_response()#服务器返回文件 的信息
419 if response["status_code"] ==247:#如文件存在
420 if name_down==True and response['file_size']==filesize:
421 print('文件已经下载完成')
422 self.c.send(b'2')
423 return
424 self.c.send(b'1')#发送到服务器,表示可以接收文件了
425 #if name_down:
426 received_size = filesize#当前接收的数据大小
427 #else:
428 #received_size = 0#当前接收的数据大小
429
430 file_obj = open(file_path,"ab")#打开文件
431 if self.cmd_md5_(cmd_list):
432 md5_obj = hashlib.md5()
433 progress = self.show_pr(response['file_size']) #进度条 传入文件大小
434 progress.__next__()
435 while received_size< response['file_size']:
436 if response['file_size'] - received_size>1024:#表示接收不止一次
437 size=1024
438 else:#最后一次
439 size=response['file_size'] - received_size
440 #print('最后一个大小',size)
441 data= self.c.recv(size)#接收数据
442
443 try:
444 progress.send(len(data))#传入当前数据大小
445 except StopIteration as e:
446 print("100%")
447 received_size+=len(data)#接收数据大小累加
448 file_obj.write(data)#写入文件
449 md5_obj.update(data)#进行MD5验证
450 else:
451 print("下载完成".center(60,'-'))
452 file_obj.close()
453 md5_val = md5_obj.hexdigest()#获取MD5
454 #print(md5_val)
455 md5_from_server = self.get_response()#服务端的MD5
456 #print(md5_from_server['md5'])
457 if md5_from_server['status_code'] == 248:
458 if md5_from_server['md5'] == md5_val:
459 print("%s 文件一致性校验成功!" % down_filename)
460 pass
461 else:
462 progress = self.show_pr(response['file_size']) #进度条 传入文件大小
463 progress.__next__()
464 while received_size< response['file_size']:
465 if response['file_size'] - received_size>1024:#表示接收不止一次
466 size=1024
467 else:#最后一次
468 size=response['file_size'] - received_size
469 #print('最后一个大小',size)
470 data= self.c.recv(size)#接收数据
471
472 try:
473 progress.send(len(data))#传入当前数据大小
474 except StopIteration as e:
475 print("100%")
476 received_size+=len(data)#接收数据大小累加
477 file_obj.write(data)#写入文件
478 pass
479
480 else:
481 print("下载完成".center(60,'-'))
482 file_obj.close()
483 pass
484 self.c.send(b'1')#发送到服务器,表示可以接收文件了
485
486 if __name__=='__main__':
487
488 c=FTPClient()
489 c.inter()
|           |- - -cfg/#配置目录
| | |- - -__init__.py
| | |- - -config.py#配置文件
 1 #!usr/bin/env python
2 #-*-coding:utf-8-*-
3 # Author calmyan
4
5 import os ,sys
6 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量
7 sys.path.append(BASE_DIR)#增加环境变量
8 #print(BASE_DIR)
9
10 PUT_DIR=BASE_DIR+'\putfile\\'#定义用户上传目录文件路径变量
11 GET_DIR=BASE_DIR+'\down\\'#定义用户下载目录文件路径变量
12 HELP='help'
13 CMD_LIST=['ls','pwd','info','help']
|- - -ftp_server/#服务端程序目录
| |- - -__init__.py
| |- - -bin/#启动目录
| | |- - -__init__.py
| | |- - -start.py#服务端视图启动
 1 #!usr/bin/env python
2 #-*-coding:utf-8-*-
3 # Author calmyan
4 import socket,os,json
5 import os ,sys
6 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量
7 sys.path.append(BASE_DIR)#增加环境变量
8
9 from core import main
10
11 if __name__ == '__main__':
12
13 main.ArvgHandler()
|           |      |- - -user_reg.py#用户注册启动
 1 #!usr/bin/env python
2 #-*-coding:utf-8-*-
3 # Author calmyan
4
5 import configparser
6 import os ,sys
7 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量
8 sys.path.append(BASE_DIR)#增加环境变量
9 from cfg import config
10 #修改个信息 磁盘大小
11 def set_info(name,pwd,size):
12 config_info=configparser.ConfigParser()#读数据
13 config_info.read(config.AUTH_FILE)#读文件 用户名密码
14 #print(config_info.options(name))
15 config_info[name]={}
16 config_info.set(name,config.PWD,pwd)#密码
17 config_info.set(name,config.QUOTATION,size)#磁盘信息
18 config_info.write(open(config.AUTH_FILE,'w'))#写入文件
19 file_path='%s/%s'%(config.USER_HOME,name)#拼接目录路径
20 os.mkdir(file_path)#创建目录
21 print('创建完成'.center(60,'='))
22 print('用户名:[%s]\n密码:[%s]\n磁盘空间:[%s]'%(name,pwd,size))
23
24 if __name__ == '__main__':
25 name=input('name:')
26 pwd=input('pwd:')
27 size=input('size:')
28 set_info(name,pwd,size)
|           |      |- - -userpwd.cfg#用户信息文件
 1 #!usr/bin/env python
2 #-*-coding:utf-8-*-
3 # Author calmyan
4
5 import os ,sys
6 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量
7 sys.path.append(BASE_DIR)#增加环境变量
8 #HOME_PATH = os.path.join(BASE_DIR, "home")
9
10
11
12 #USER_DIR='%s\\data\\'%BASE_DIR#定义用户数据目录文件路径变量
13 #USER_DIR='%s/data'%BASE_DIR#定义用户数据目录文件路径变量
14 #USER_HOME='%s\\home\\'%BASE_DIR#定义用户家目录文件路径变量
15 USER_HOME='%s/home'%BASE_DIR#定义用户家目录文件路径变量
16 #LOG_DIR='%s\\log\\'%BASE_DIR#日志目录
17 USER_LOG='%s/log/user_log.log'%BASE_DIR#日志登陆文件
18 USER_OPERT='%s/log/user_opert.log'%BASE_DIR#日志操作文件
19
20 LOG_LEVEL='DEBUG'#日志级别
21
22 AUTH_FILE='%s/cfg/userpwd.cfg'%BASE_DIR#用户名密码文件
23 HOST='0.0.0.0'# IP
24 PORT=9500#端口
25 QUOTATION='Quotation'#磁盘空间
26 PWD='PWD'#密码
|           |- - -core/#服务端主要文件目录
| | |- - -__init__.py
| | |- - -ftp_server.py#服务端主要逻辑 类
  1 #!usr/bin/env python
2 #-*-coding:utf-8-*-
3 # Author calmyan
4 import socketserver,os,json,pickle,configparser,time
5 time_format='%Y%m%d%H%M%S'#定义时间格式
6 times=time.strftime(time_format)#定义时间
7
8 STATUS_CODE={
9 230:'文件断点继传',
10 231:'新文件',
11 240:'格式出错,格式:{"action":"get","filename":"filename","size":100}',
12 241:'指令错误',
13 242:'用户名或密码为空',
14 243:'用户或密码出错',
15 244:'用户密码通过校验',
16 245:'文件不存在或不是文件',
17 246:'服务器上该文件不存在',
18 247:'准备发送文件,请接收',
19 248:'md5',
20 249:'准备接收文件,请上传',
21 250:'磁盘空间不够',
22 251:'当前已经为主目录',
23 252:'目录正在切换',
24 253:'正在查看路径',
25 254:'准备删除文件',
26 255:'删除文件完成',
27 256:'目录不存在',
28 257:'目录已经存在',
29 258:'目录创建完成',
30 259:'目录删除完成',
31 260:'目录不是空的',
32 }
33 import os ,sys,hashlib
34 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量
35 sys.path.append(BASE_DIR)#增加环境变量
36 from cfg import config
37 from core.logs import log_log
38 from core.logs import user_opert
39
40
41 class MyTCPHandler (socketserver.BaseRequestHandler):#
42
43 def setup(self):
44 print('监听中。。。')
45 #'''用户名与密码是否为空'''
46 def cmd_auth(self,*args,**kwargs):#用户校验
47 '''用户名与密码是否为空'''
48 data=args[0]#获取 传来的数据
49 if data.get('username') is None or data.get('password') is None:#如果用户名或密码为空
50 self.send_mge(242)#发送错误码
51 name=data.get('username')#用户名
52 pwd=data.get('password')#密码
53 print(name,pwd)
54 user=self.authusername(name,pwd)#用户名与密码的校验 获名用户名
55 if user is None:#用户名不存在
56 self.send_mge(243)
57 else:
58 self.user=name#保存用户名
59 self.home_dir='%s/%s'%(config.USER_HOME,self.user)#拼接 用户home目录路径 用户根目录
60 self.user_home_dir=self.home_dir#当前所在目录
61 # self.user_dir=self.user_home_dir.split('/')[-1]#当前所在目录 相对
62 self.dir_join()#进行目录拼接
63 self.send_mge(244,data={'dir':self.user_dir})#相对 目录
64
65 #目录拼接
66 def dir_join(self,*args,**kwargs):
67 self.user_dir=self.user_home_dir.split(self.home_dir)[-1]+'/'#当前所在目录 相对
68 print(self.user_dir)
69
70 #'''用户名与密码的校验''
71 def authusername(self,name,pwd):
72 '''用户名与密码的校验'''
73 config_info=configparser.ConfigParser()#读数据
74 config_info.read(config.AUTH_FILE)#读文件 用户名密码
75 if name in config_info.sections():#用户名存
76 password=config_info[name]['PWD']
77 if password==pwd:#密码正确
78 print('通过校验!')
79 config_info[name]['USERname']=name#名字的新字段
80 info_str='用户[%s],成功登陆'%name
81 self.log_log.warning(info_str)#记录日志
82 #log_log(info_str)
83 return config_info[name]
84 else:
85 info_str='用户[%s],登陆错误'%name
86 #log_log(info_str)
87 self.log_log.warning(info_str)#记录日志
88 return 0
89
90 #判断文件 是否存在
91 def file_name(self,file_path):
92 if os.path.isfile(file_path):#文件是否存
93 return True
94 else:
95 return False
96
97 #判断目录是否存在
98 def file_dir(self,file_path):
99 if os.path.isdir(file_path):#目录是否存
100 return True
101 else:
102 return False
103
104 #删除文件
105 def cmd_rm(self,*args,**kwargs):
106 cmd_dict=args[0]#获取字典
107 action=cmd_dict["action"]
108 filename =cmd_dict['filename']#文件名
109 file_path='%s/%s'%(self.user_home_dir,filename)#拼接文件路径
110 if not self.file_name(file_path):
111 self.send_mge(245)#文件不存在
112 return
113 else:
114 user_size=self.disk_size()#获取磁盘信息
115 self.send_mge(254,data={'剩余空间':user_size})#准备删除文件
116 file_size=os.path.getsize(file_path)#获取文件大小
117 pass
118 self.request.recv(1) #客户端确认 防粘包
119 os.remove(file_path)
120 new_size=float((float(user_size)+float(file_size))/1024000)#空间大小增加
121 self.set_info(str(new_size))#传入新大小
122 self.send_mge(255,data={'剩余空间':new_size})#删除文件完成
123 info_str=self.log_str('删除文件')#生成日志信息
124 self.user_opert.critical(info_str)#记录日志
125 return
126
127 #创建目录
128 def cmd_mkdir(self,*args,**kwargs):
129 cmd_dict=args[0]#获取字典
130 action=cmd_dict["action"]
131 filename =cmd_dict['filename']#目录名
132 file_path='%s/%s'%(self.user_home_dir,filename)#拼接目录路径
133 if self.file_dir(file_path):
134 self.send_mge(257)#目录已经 存在
135 return
136 else:
137 self.send_mge(256,data={'目录':'创建中...'})#目录创建中
138 self.request.recv(1) #客户端确认 防粘包
139 os.mkdir(file_path)#创建目录
140 self.send_mge(258)#目录完成
141 info_str=self.log_str('创建目录')#生成日志信息
142 self.user_opert.critical(info_str)#记录日志
143 return
144
145 #删除目录
146 def cmd_rmdir(self,*args,**kwargs):
147 cmd_dict=args[0]#获取字典
148 action=cmd_dict["action"]
149 filename =cmd_dict['filename']#目录名
150 file_path='%s/%s'%(self.user_home_dir,filename)#拼接目录路径
151 if not self.file_dir(file_path):
152 self.send_mge(256)#目录不存在
153 return
154 elif os.listdir(file_path):
155 self.send_mge(260,data={'目录':'无法删除'})#目录不是空的
156 return
157 else:
158 self.send_mge(257,data={'目录':'删除中...'})#目录创建中
159 self.request.recv(1) #客户端确认 防粘包
160 os.rmdir(file_path)#删除目录
161 self.send_mge(259)#目录删除完成
162 info_str=self.log_str('删除目录')#生成日志信息
163 self.user_opert.critical(info_str)#记录日志
164 return
165
166 #磁盘空间大小
167 def disk_size(self):
168 attr_list=self.user_info()#调用个人信息
169 put_size=attr_list[1]#取得磁盘信息
170 user_size=float(put_size)*1024000#字节
171 return user_size
172
173 #'''客户端上传文件 '''
174 def cmd_put(self,*args,**kwargs):
175 '''客户端上传文件 '''
176 cmd_dict=args[0]#获取字典
177 filename =cmd_dict['filename']#文件名
178 file_size= cmd_dict['size']#文件大小
179 #user_home_dir='%s/%s'%(config.USER_HOME,self.user)#拼接 用户home目录路径
180 file_path='%s/%s'%(self.user_home_dir,filename)#拼接文件路径
181 user_size=self.disk_size()#取得磁盘信息
182 if float(file_size)>float(user_size):#空间不足
183 self.send_mge(250,data={'剩余空间':user_size})
184 return
185 self.send_mge(249,data={'剩余空间':user_size})#发送一个确认
186 self.request.recv(1) #客户端确认 防粘包
187 if self.file_name(file_path):#判断文件名是否存在,
188 s_file_size=os.path.getsize(file_path)##获取服务器上的文件大小
189 if file_size>s_file_size:#如果服务器上的文件小于要上传的文件进
190 tmp_file_size=os.stat(file_path).st_size#计算临时文件大小
191 reversed_size=tmp_file_size#接收到数据大小
192 self.send_mge(230,data={'文件大小':reversed_size})#发送临时文件大小
193 pass
194 else:# file_size==s_file_size:#如果大小一样
195 file_path=file_path+'_'+times#命名新的文件 名
196 reversed_size=0#接收到数据大小
197 self.send_mge(231)#发送 不是断点文件
198 pass
199 else:
200 reversed_size=0#接收到数据大小
201 self.send_mge(231)#发送 不是断点文件
202 pass
203
204 f=open(file_path,'ab')
205 self.request.recv(1) #客户端确认 防粘包
206 if cmd_dict['md5']:#是否有 md5
207 md5_obj = hashlib.md5() # 进行MD5
208 while reversed_size< int(file_size):#接收小于文件 大小
209 if int(file_size) - reversed_size>1024:#表示接收不止一次
210 size=1024
211 else:#最后一次
212 size=int(file_size) - reversed_size
213 #print('最后一个大小',size)
214 data= self.request.recv(size)#接收数据
215 md5_obj.update(data)
216 reversed_size+=len(data)#接收数据大小累加
217 f.write(data)#写入文件
218 else:
219 f.close()
220 print('[%s]文件上传完毕'.center(60,'-')%filename)
221 md5_val = md5_obj.hexdigest()#得出MD5
222 print(md5_val)
223 self.send_mge(248,{'md5':md5_val})#发送md5给客户端
224 else:
225 while reversed_size< int(file_size):#接收小于文件 大小
226 if int(file_size) - reversed_size>1024:#表示接收不止一次
227 size=1024
228 else:#最后一次
229 size=int(file_size) - reversed_size
230 #print('最后一个大小',size)
231 data= self.request.recv(size)#接收数据
232 reversed_size+=len(data)#接收数据大小累加
233 f.write(data)#写入文件
234 else:
235 print('[%s]文件上传完毕'%filename.center(60,'-'))
236 f.close()
237 new_size=float((float(user_size)-float(file_size))/1024000)#扣除空间大小
238 self.set_info(str(new_size))#传入新大小
239 info_str=self.log_str('文件上传')#生成日志信息
240 self.user_opert.critical(info_str)#记录日志
241 return
242
243 #用户下载文件
244 def cmd_get(self,*args,**kwargs):#用户下载文件
245 ''' 用户下载文件'''
246 data=args[0]
247 print(data)
248 if data.get('filename') is None:#判断文件名不为空
249 self.send_mge(245)
250 return
251
252 self.request.recv(1) #客户端确认 防粘包
253 file_path='%s/%s'%(self.user_home_dir,data.get('filename'))#拼接文件路径 用户文件路径
254 if os.path.isfile(file_path):#判断文件是否存在
255 file_obj=open(file_path,'rb')#打开文件句柄\
256 file_size=os.path.getsize(file_path)#获取文件大小
257 if data['name_down']:
258 send_size=data['size']#已经发送数据大小
259 #self.send_mge(230,data={'文件大小':file_size})#断点续传
260 else:
261 send_size=0
262 #self.send_mge(231)#非断点续传
263 #self.request.recv(1) #客户端确认 防粘包
264 file_obj.seek(send_size)#移动到
265 self.send_mge(247,data={'file_size':file_size})#发送相关信息
266 attr=self.request.recv(1024) #客户端确认 防粘包
267 if attr.decode()=='2':return #如果返回是
268 if data.get('md5'):
269 md5_obj = hashlib.md5()
270 while send_size<file_size:
271 line=file_obj.read(1024)
272 #for line in file_obj:
273 self.request.send(line)
274 md5_obj.update(line)
275 else:
276 file_obj.close()
277 md5_val = md5_obj.hexdigest()
278 self.send_mge(248,{'md5':md5_val})
279 print("发送完毕.")
280 else:
281 while send_size<file_size:
282 line=file_obj.read(1024)
283 #for line in file_obj:
284 self.request.send(line)
285 else:
286 file_obj.close()
287 print("发送完毕.")
288 self.request.recv(1) #客户端确认 防粘包
289 info_str=self.log_str('下载文件')#生成日志信息
290 #user_opert(info_str)#记录日志
291 self.user_opert.critical(info_str)#记录日志
292 return
293
294 #切换目录
295 def cmd_cd(self,cmd_dict,*args,**kwargs):
296 '''切换目录'''
297 cmd_attr=cmd_dict['actionname']#获取命令
298 if cmd_attr=='..' or cmd_attr=='../..':
299 if (self.home_dir)==self.user_home_dir:
300 self.send_mge(251)
301 return
302 elif cmd_attr=='../..':
303 self.send_mge(252)#可以切换到上级目录
304 self.user_home_dir=self.home_dir#绝对目录 = home
305 self.user_dir='/'
306 clinet_ack=self.request.recv(1024)#为了去粘包
307 self.request.send(self.user_dir.encode())#返回相对目录
308 return
309 else:
310 self.send_mge(252)#可以切换到上级目录
311 print(self.user_home_dir)#绝对目录
312 print(os.path.dirname(self.user_home_dir))#父级目录
313 self.user_home_dir=os.path.dirname(self.user_home_dir)#父级目录
314 self.dir_join()#目录拼接切换
315 clinet_ack=self.request.recv(1024)#为了去粘包
316 self.request.send(self.user_dir.encode())#返回相对目录
317 return
318
319 elif os.path.isdir(self.user_home_dir+'/'+cmd_attr):#如果目录存在
320 self.send_mge(252)
321 self.user_home_dir=self.user_home_dir+'/'+cmd_attr#目录拼接
322 self.dir_join()#相对目录拼接切换
323 clinet_ack=self.request.recv(1024)#为了去粘包
324 print(clinet_ack.decode())
325 self.request.send(self.user_dir.encode())
326 return
327 else:
328 self.send_mge(256)#目录不存在
329 return
330
331 #查看目录路径 CD
332 def cmd_pwd(self,cmd_dict):
333 self.request.send(str(len(self.user_dir.encode('utf-8'))).encode('utf-8'))#发送大小
334 clinet_ack=self.request.recv(1024)#为了去粘包
335 self.request.send(self.user_dir.encode())#发送相对路径
336 info_str=self.log_str('查看目录路径')#生成日志信息
337 #logger.warning
338 self.user_opert.critical(info_str)#记录日志
339 return
340
341 #修改个信息 磁盘大小
342 def set_info(self,new_size):
343 config_info=configparser.ConfigParser()#读数据
344 config_info.read(config.AUTH_FILE)#读文件 用户名密码
345 print(config_info.options(self.user))
346 config_info.set(self.user,config.QUOTATION,new_size)
347 config_info.write(open(config.AUTH_FILE,'w'))
348
349 #读取个人信息
350 def user_info(self):
351 config_info=configparser.ConfigParser()#读数据
352 config_info.read(config.AUTH_FILE)#读文件 用户名密码
353 print(config_info.options(self.user))
354 pwds=config_info[self.user][config.PWD]#密码
355 Quotation=config_info[self.user][config.QUOTATION]#磁盘配额 剩余
356 user_info={}
357 user_info['用户名']=self.user
358 user_info['密码']=pwds
359 user_info['剩余磁盘配额']=Quotation
360 return user_info,Quotation
361
362 #查看用户信息
363 def cmd_info(self,*args,**kwargs):
364 attr=self.user_info()
365 info_dict=attr[0]
366 self.request.send(str(len(json.dumps(info_dict))).encode('utf-8'))#
367 clinet_ack=self.request.recv(1024)#为了去粘包
368 self.request.send(json.dumps(info_dict).encode('utf-8'))#发送指令
369 info_str=self.log_str('查看用户信息')#生成日志信息
370 self.user_opert.critical(info_str)#记录日志
371 return
372
373 #日志信息生成
374 def log_str(self,msg,**kwargs):
375 info_str='用户[%s]进行了[%s]操作'%(self.user,msg)
376 return info_str
377
378
379 #目录查看
380 def cmd_ls(self,*args,**kwargs):
381 data=os.listdir(self.user_home_dir)#查看目录文件
382 print(data)
383 datas=json.dumps(data)#转成json格式
384 self.request.send(str(len(datas.encode('utf-8'))).encode('utf-8'))#发送大小
385 clinet_ack=self.request.recv(1024)#为了去粘包
386 self.request.send(datas.encode('utf-8'))#发送指令
387 info_str=self.log_str('目录查看')#生成日志信息
388 self.user_opert.critical(info_str)#记录日志
389 return
390 ##单个命令
391 def cmd_compr(self,cmd_dict,**kwargs):
392 attr=cmd_dict['actionname']#赋于变量
393 if hasattr(self,'cmd_%s'%attr):#是否存在
394 func=getattr(self,'cmd_%s'%attr)#调用
395 func(cmd_dict)
396 return
397 else:
398 print('没有相关命令!')
399 self.send_mge(241)
400 return
401
402 #'''发送信息码给客户端'''
403 def send_mge(self,status_code,data=None):
404 '''发送信息码给客户端'''
405 mge={'status_code':status_code,'status_msg':STATUS_CODE[status_code]}#消息
406 if data:#不为空
407 mge.update(data)#提示码进行更新
408 print(mge)
409 self.request.send(json.dumps(mge).encode())#发送给客户端
410
411 #重写handle方法
412 def handle(self):#重写handle方法
413 while True:
414 #try:
415 self.data=self.request.recv(1024).strip()#接收数据
416 print('ip:{}'.format(self.client_address[0]))#连接的ip
417 print(self.data)
418 self.log_log=log_log()#登陆日志
419 self.user_opert=user_opert()#操作日志
420 if not self.data:
421 print("[%s]客户端断开了!."%self.user)
422 info_str='用户[%s],退出'%self.user
423
424 break
425 cmd_dict=json.loads(self.data.decode())#接收 数据
426 if cmd_dict.get('action') is not None:#判断数据格式正确
427 action=cmd_dict['action']#文件 头
428 if hasattr(self,'cmd_%s'%action):#是否存在
429 func=getattr(self,'cmd_%s'%action)#调用
430 func(cmd_dict)
431 else:
432 print('没有相关命令!')
433 self.send_mge(241)
434 else:
435 print('数据出错!')
436 self.send_mge(240)
437 #except Exception as e:
438 # print('客户端断开了!',e)
439 # break
|           |      |- - -logs.py#日志主要逻辑 类
 1 #!usr/bin/env python
2 #-*-coding:utf-8-*-
3 # Author calmyan
4 import os,logging,time
5 from cfg import config
6 LOG_LEVEL=config.LOG_LEVEL
7
8
9 def log_log():#登陆日志,传入内容
10 logger=logging.getLogger('用户成功登陆日志')#设置日志模块
11 logger.setLevel(logging.DEBUG)
12 fh=logging.FileHandler(config.USER_LOG,encoding='utf-8')#写入文件
13 fh.setLevel(config.LOG_LEVEL)#写入信息的级别
14 fh_format=logging.Formatter('%(asctime)s %(message)s',datefmt='%m/%d/%Y %I:%M:%S %p')#日志格式
15 fh.setFormatter(fh_format)#关联格式
16 logger.addHandler(fh)#添加日志输出模式
17 #logger.warning(info_str)
18 return logger
19
20 def user_opert():#用户操作日志,传入内容
21 logger=logging.getLogger('用户操作日志')#设置日志模块
22 logger.setLevel(logging.CRITICAL)
23 fh=logging.FileHandler(config.USER_OPERT,encoding='utf-8')#写入文件
24 fh.setLevel(config.LOG_LEVEL)#写入信息的级别
25 fh_format=logging.Formatter('%(asctime)s %(message)s',datefmt='%m/%d/%Y %I:%M:%S %p')#日志格式
26 fh.setFormatter(fh_format)#关联格式
27 logger.addHandler(fh)#添加日志输出模式
28 #logger.critical(info_str)
29 return logger
|           |      |- - -main.py#服务端启动主程序
 1 #!usr/bin/env python
2 #-*-coding:utf-8-*-
3 # Author calmyan
4
5 import socketserver,os,json,pickle
6 import os ,sys
7 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量
8 sys.path.append(BASE_DIR)#增加环境变量
9 from cfg import config
10
11
12 from core.ftp_server import MyTCPHandler
13
14 import optparse
15 class ArvgHandler(object):
16 def __init__(self):# 可 传入系统参数
17 self.paresr=optparse.OptionParser()#启用模块
18 #self.paresr.add_option('-s','--host',dest='host',help='服务绑定地址')
19 #self.paresr.add_option('-s','--port',dest='host',help='服务端口')
20 (options,args)=self.paresr.parse_args()#返回一个字典与列表的元组
21
22 self.verufy_args(options,args)#进行校验
23 def verufy_args(self,options,args):
24 '''校验与调用'''
25 if hasattr(self,args[0]):#反射判断参数
26 func=getattr(self,args[0])#生成一个实例
27 func()#开始调用
28 else:
29 self.paresr.print_help()#打印帮助文档
30 def start(self):
31 print('服务启动中....')
32 s=socketserver.ThreadingTCPServer((config.HOST,config.PORT),MyTCPHandler)#实例化一个服务端对象
33 s.serve_forever()#运行服务器
34 print('服务关闭')

(转)python高级FTP的更多相关文章

  1. python作业高级FTP

    转载自:https://www.cnblogs.com/sean-yao/p/7882638.html 作业需求: 1. 用户加密认证 2. 多用户同时登陆 3. 每个用户有自己的家目录且只能访问自己 ...

  2. python第四十八天--高级FTP

    高级FTP服务器1. 用户加密认证2. 多用户同时登陆3. 每个用户有自己的家目录且只能访问自己的家目录4. 对用户进行磁盘配额.不同用户配额可不同5. 用户可以登陆server后,可切换目录6. 查 ...

  3. python作业高级FTP(第八周)

    作业需求: 1. 用户加密认证 2. 多用户同时登陆 3. 每个用户有自己的家目录且只能访问自己的家目录 4. 对用户进行磁盘配额.不同用户配额可不同 5. 用户可以登陆server后,可切换目录 6 ...

  4. Python09作业思路及源码:高级FTP服务器开发(仅供参考)

    高级FTP服务器开发 一,作业要求 高级FTP服务器开发 用户加密认证(完成) 多用户同时登陆(完成) 每个用户有不同家目录且只能访问自己的家目录(完成) 对用户进行磁盘配额,不同用户配额可不同(完成 ...

  5. 老男孩Python高级全栈开发工程师【真正的全套完整无加密】

    点击了解更多Python课程>>> 老男孩Python高级全栈开发工程师[真正的全套完整无加密] 课程大纲 老男孩python全栈,Python 全栈,Python教程,Django ...

  6. python高级学习目录

    1. Linux介绍.命令1.1. 操作系统(科普章节) 1.2. 操作系统的发展史(科普章节) 1.3. 文件和目录 1.4. Ubuntu 图形界面入门 1.5. Linux 命令的基本使用 1. ...

  7. 第十一章:Python高级编程-协程和异步IO

    第十一章:Python高级编程-协程和异步IO Python3高级核心技术97讲 笔记 目录 第十一章:Python高级编程-协程和异步IO 11.1 并发.并行.同步.异步.阻塞.非阻塞 11.2 ...

  8. python 高级之面向对象初级

    python 高级之面向对象初级 本节内容 类的创建 类的构造方法 面向对象之封装 面向对象之继承 面向对象之多态 面向对象之成员 property 1.类的创建 面向对象:对函数进行分类和封装,让开 ...

  9. python高级之函数

    python高级之函数 本节内容 函数的介绍 函数的创建 函数参数及返回值 LEGB作用域 特殊函数 函数式编程 1.函数的介绍 为什么要有函数?因为在平时写代码时,如果没有函数的话,那么将会出现很多 ...

随机推荐

  1. char与CString相互转换

    Char -> CStringchar ch[] = "Hello";CString str;str.Format("%s",ch);CString -& ...

  2. SpringMVC(一)helloWorld

    web.xml文件配置如下: <?xml version="1.0" encoding="UTF-8"?> <web-app version= ...

  3. 文档/视图(01):第一个Demo

    学习文档视图编程的第一个demo,程序比较简单,主要对文档模板,文档,视图等相互关系的一个了解. 功能:菜单添加一个[操作]项,然后新建四份空白文档,点击[操作]之后,在四份空白文档上面各绘制一个Bu ...

  4. TinyMCE Editor

    TinyMCE Editor(https://www.tinymce.com/features/) is an online text editor, it is used to write post ...

  5. python list 和 tuple, dict 用于迭代代价

    #!/usr/bin/env python #-*- coding:utf-8 -*- import time start = time.time() for i in range(1000000): ...

  6. Android 如何通过浏览器打开App

    首先给出参考资料http://my.oschina.net/liucundong/blog/354029?p=3#rpl_291191828 通过浏览器直接打开Android应用程序 Android ...

  7. hdu 4915 括号匹配+巧模拟

    http://acm.hdu.edu.cn/showproblem.php?pid=4915 给定一个序列,由()?组成,其中?可以表示(或者),问说有一种.多种或者不存在匹配. 从左向右,优先填满n ...

  8. unigui发展路线图

    unigui发展路线图 易博龙收购了SENCHA EXT JS和UNIGUI,随之官方发布了UNIGUI的发展路线图. UNIGUI是开发WEB桌面和手机移动应用的利器. UNIGUI将支持以下新特性 ...

  9. Convolution Neural Network (CNN) 原理与实现

    本文结合Deep learning的一个应用,Convolution Neural Network 进行一些基本应用,参考Lecun的Document 0.1进行部分拓展,与结果展示(in pytho ...

  10. ip网段变更

    背景 公司网络跟集团靠拢,先走第一步:IP网段变更.从XX网段切换到OO网段 方法 1. 准备工作 a. 保证IPMI连接正常 b. 获得新IP并核对对应主机名.旧IP是否相符 2. 确认网卡名称 # ...