(转)python高级FTP

时间:2023-03-10 07:01:00
(转)python高级FTP
原文地址: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 先上流程图:

(转)python高级FTP

详细代码如下:

|- - -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('服务关闭')