python第四十八天--高级FTP
高级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#客户端视图启动
#!usr/bin/env python
#-*-coding:utf-8-*-
# Author calmyan
import socket,os,json,getpass,hashlib
import os ,sys,optparse STATUS_CODE={
240:'格式出错,格式:{"action":"get","filename":"filename","size":100}',
241:'指令错误',
242:'用户密码出错',
243:'用户或密码出错',
244:'用户密码通过校验',
}
BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量
sys.path.append(BASE_DIR)#增加环境变量
from cfg import config
class FTPClient(object):
def __init__(self):
paresr=optparse.OptionParser()
paresr.add_option('-s','--server',dest='server',help='服务器地址')
paresr.add_option('-P','--port',type="int",dest='port',help='服务器端口')
paresr.add_option('-u','--username',dest='username',help='用户名')
paresr.add_option('-p','--password',dest='password',help='密码')
(self.options,self.args)=paresr.parse_args()#返回一个字典与列表的元组
self.verify_args(self.options,self.args)#判断参数
self.ser_connect()#连接服务端
self.cmd_list=config.CMD_LIST
self.rat=0#文件断点 #实例化一个连接端
def ser_connect(self):
self.c=socket.socket()#实例化一个连接端
self.c.connect((self.options.server,self.options.port))#进行连接 #判断用户与密码是否成对出现
def verify_args(self,options,args):
if (options.username is None and options.password is None) or (options.username is not None and options.password is not None):#判断用户与密码是否成对出现
pass##判断用户与密码单个出现
else:
exit('出错:请输入用户与密码!')#退出
if options.server and options.port:#端口判断
if options.port>0 and options.port<65535:
return True
else:
print('端口号:[%s]错误,端口范围:0-65535'%options.port) #登陆方法
def landing(self):#登陆方法
'''用户验证'''
if self.options.username is not None:#判断用户名已经输入
#print(self.options.username,self.options.password)
return self.get_user_pwd(self.options.username,self.options.password)#返回结果
else:
print('用户登陆'.center(60,'='))
ret_count=0#验证次数
while ret_count<5:
username=input('用户名:').strip()
password=getpass.getpass('密码:').strip()
if self.get_user_pwd(username,password):
return self.get_user_pwd(username,password)#调用远程验证用户 返回结果
else:
ret_count+=1#次数加一
print('认证出错次数[%s]'%ret_count)
else:
print('密码出错次数过多!')
exit() #'''用户名与密码检验'''
def get_user_pwd(self,username,password):
'''用户名与密码检验'''
#发送 头文件
data={
'action':'auth',
'username':username,
'password':password
}
self.c.send(json.dumps(data).encode())#发送到服务器
response = self.get_response()#得到服务端的回复
if response.get('status_code') == 244:
print(STATUS_CODE[244])
self.user = username#存下用户名
self.user_dir=response.get('dir')#目录
return True
else:
print(response.get("status_msg") ) #服务器回复
def get_response(self):#服务器回复
'''服务器回复信息'''
data=self.c.recv(1024)#接收回复
data = json.loads(data.decode())
return data #指令帮助
def help(self):#指令帮助
attr='''
help 指令帮助
----------------------------------
info 个人信息
----------------------------------
ls 查看当前目录(linux/windows)
----------------------------------
pwd 查看当前路径(linux/windows)
----------------------------------
cd 目录 切换目录(linux/windows)
----------------------------------
get filename 下载文件
----------------------------------
put filename 上传文件
----------------------------------
--md5 使用md5 在get/put 后
----------------------------------
mkdir name 创建目录(linux/windows)
----------------------------------
rmdir name 删除目录(linux/windows)
----------------------------------
rm filename 删除文件 (linux/windows)
----------------------------------
exit 退出
----------------------------------
'''.format()
print(attr) ##交互
def inter(self):#交互
if self.landing():#通过用户密码认证
print('指令界面'.center(60,'='))
self.help()
while True:
cmd = input('[%s]-->指令>>>:'%self.user_dir).strip()
if len(cmd)==0:continue#输入空跳过
if cmd=='exit':exit()#退出指令
cmd_str=cmd.split()#用空格分割 取命令到列表
#print(cmd_str)
#print(len(cmd_str))
if len(cmd_str)==1 and cmd_str[0] in self.cmd_list:#如果是单个命令 并且在命令列表中
#if len(cmd_str)==1:#如果是单个命令 并且在命令列表中
if cmd_str[0]==config.HELP:
self.help()
continue
func=getattr(self,'cmd_compr')#调用此方法
ret=func(cmd_str)
if ret:
continue
else:
pass
elif len(cmd_str)>1:
if hasattr(self,'cmd_%s'%cmd_str[0]):#判断类中是否有此方法
func=getattr(self,'cmd_%s'%cmd_str[0])#调用此方法
func(cmd_str)#执行
continue
else:
print('指令出错!')
self.help()# #'''是否要md5'''
def cmd_md5_(self,cmd_list):
'''是否要md5'''
if '--md5' in cmd_list:
return True #进度条
def show_pr(self,total):#进度条
received_size = 0 #发送的大小
current_percent = 0 #
while received_size < total:
if int((received_size / total) * 100 ) > current_percent :
print("#",end="",flush=True)#进度显示
current_percent = int((received_size / total) * 100 )
new_size = yield #断点跳转 传入的大小
received_size += new_size #单个命令
def cmd_compr(self,cmd_str,**kwargs):
mag_dict={
"action":"compr",
'actionname':cmd_str[0]
}
self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送数据
cmd_res_attr=self.get_response()#得到服务器的回复
if type(cmd_res_attr) is not int:#如果不int 类型
if cmd_res_attr["status_code"] ==241:#命令不对
print(cmd_res_attr['status_msg'])
return
if cmd_res_attr["status_code"] ==240:#命令不对
print(cmd_res_attr['status_msg'])
return
size_l=0#收数据当前大小
self.c.send('准备好接收了,可以发了'.encode('utf-8'))
receive_data= ''.encode()
while size_l< cmd_res_attr:
data=self.c.recv(1024)#开始接收数据
size_l+=len(data)#加上
receive_data += data
else:
receive_data=receive_data.decode()
try:
receive_data=eval(receive_data)#转为列表 或字典
except Exception as e:
pass
if type(receive_data) is dict:#如果是字典
for i in receive_data:
print(i,receive_data[i])
return 1
if type(receive_data) is list:#如果是列表
for i in receive_data:
print(i)
return 1
print(receive_data)
return 1 #切换目录
def cmd_cd(self,cmd_list,**kwargs):
'''切换目录'''
mag_dict={
"action":"cd",
'actionname':cmd_list[1]
}
self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送数据
msg_l=self.c.recv(1024)#接收数据 消息
data=json.loads(msg_l.decode())
if data["status_code"] ==251:#目录不可切换
print(data['status_msg'])
return
elif data["status_code"] ==252:#目录可以换
print(data['status_msg'])
self.c.send(b'')#发送到服务器,表示可以了
data=self.c.recv(1024)
print(data.decode())
user_dir=data.decode()
print(user_dir)
self.user_dir=user_dir
return
elif data["status_code"] ==256:#目录不存在
print(data['status_msg'])
return #删除文件
def cmd_rm(self,cmd_list,**kwargs):
mag_dict={
"action":"rm",
'filename':cmd_list[1]
}
self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送文件信息
data=self.get_response()#得到服务器的回复
if data["status_code"] ==245:#文件不存在
print(data['status_msg'])
#print('删除前空间:',data['剩余空间'])
return
elif data["status_code"] ==254:#文件删除完成
print(data['status_msg'])
print('删除前空间:',data['剩余空间'])
pass
self.c.send(b'')#发送到服务器,表示可以
data=self.get_response()#得到服务器的回复
if data["status_code"] ==255:#文件删除完成
print(data['status_msg'])
print('删除后空间:',data['剩余空间'])
return #创建目录
def cmd_mkdir(self,cmd_list,**kwargs):
mag_dict={
"action":"mkdir",
'filename':cmd_list[1]
}
self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送文件信息
data=self.get_response()#得到服务器的回复
if data["status_code"] ==257:#目录已经存在
print(data['status_msg'])
return
elif data["status_code"] ==256:#目录创建中
print(data['目录'])
pass
self.c.send(b'')#发送到服务器,表示可以
data=self.get_response()#得到服务器的回复
if data["status_code"] ==258:#目录创建中完成
print(data['status_msg'])
return
pass #删除目录
def cmd_rmdir(self,cmd_list,**kwargs):
mag_dict={
"action":"rmdir",
'filename':cmd_list[1]
}
self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送文件信息
data=self.get_response()#得到服务器的回复
if data["status_code"] ==256:#目录不存在
print(data['status_msg'])
return
elif data["status_code"] ==260:#目录不为空
print(data['status_msg'])
print(data['目录'])
return
elif data["status_code"] ==257:#目录删除中
print(data['目录'])
pass
self.c.send(b'')#发送到服务器,表示可以
data=self.get_response()#得到服务器的回复
if data["status_code"] ==259:#目录删除完成
print(data['status_msg'])
return
pass #上传方法
def cmd_put(self,cmd_list,**kwargs):#上传方法
if len(cmd_list) > 1:
filename=cmd_list[1]#取文件名
filename_dir=config.PUT_DIR+filename#拼接文件名路径 if os.path.isfile(filename_dir):#是否是一个文件
filesize=os.stat(filename_dir).st_size#获取文件大小
#执行行为 名字,大小,是否
mag_dict={
"action":"put",
'filename':filename,
'size':filesize,
'overridden':True,
'md5':False
}
if self.cmd_md5_(cmd_list):#判断是否进行MD5
mag_dict['md5'] = True
self.c.send(json.dumps(mag_dict).encode('utf-8'))#发送文件信息
data=self.get_response()#得到服务器的回复
if data["status_code"] ==250:#磁盘空间不足
print(data['status_msg'])
print(mag_dict['size'])
return
if data["status_code"] ==249:#磁盘空间足够
print(data['status_msg'])
print('剩余空间',data['剩余空间'])
self.c.send(b'')#发送到服务器,表示可以上传文件了
data=self.get_response()#得到服务器的回复
if data["status_code"] ==230:#断点续传
print(data['status_msg'])
print(data['文件大小'])
self.rat=data['文件大小']#文件指针位置
pass
elif data["status_code"] ==231:#非断点续传
print(data['status_msg'])
self.rat=0#文件指针位置
pass
f=open(filename_dir,'rb')#打开文件
f.seek(self.rat)#移动到位置
print(mag_dict['md5'])
self.c.send(b'')#发送到服务器,表示可以上传文件了
if mag_dict['md5']==True:
md5_obj = hashlib.md5()#定义MD5
progress = self.show_pr(mag_dict['size']) #进度条 传入文件大小
progress.__next__()
while self.rat<filesize:
line=f.read(1024)
self.c.send(line)
try:
progress.send(len(line))#传入当前数据大小
except StopIteration as e:
print("100%")
break
md5_obj.update(line)#计算MD5 else:
print(filename,'发送完成!')
f.close()
md5_val = md5_obj.hexdigest()
md5_from_server = self.get_response()#服务端的MD5
if md5_from_server['status_code'] == 248:
if md5_from_server['md5'] == md5_val:
print("%s 文件一致性校验成功!" % filename)
return
else:
progress = self.show_pr(mag_dict['size']) #进度条 传入文件大小
progress.__next__()
#for line in f:
while self.rat<filesize:
line=f.read(1024)
self.c.send(line)
try:
progress.send(len(line))#传入当前数据大小
except StopIteration as e:
print("100%")
break
#print(line)
else:
print(filename,'发送完成!')
f.close()
return
else:
print(filename,'文件不存在!') #下载方法
def cmd_get(self,cmd_list,**kwargs):#下载方法
#cmd_split= args[0].split()#指令解析
# if len(cmd_list) == 1:
# print("没有输入文件名.")
# return
#down_filename = cmd_list[1].split('/')[-1]#文件名
down_filename=cmd_list[1]#取文件名
file_path='%s/%s'%(config.GET_DIR,down_filename)#拼接文件路径 用户down目录
if os.path.isfile(file_path):#文件是否存
filesize=os.stat(file_path).st_size#获取文件大小
name_down=True
else:
filesize=0
name_down=False
mag_dict={
"action":"get",
'filename':cmd_list[1],
'name_down':name_down,
'size':filesize
}
if self.cmd_md5_(cmd_list):#判断是否进行MD5
mag_dict['md5'] = True
self.c.send(json.dumps(mag_dict).encode())#发送
self.c.send(b'')#发送到服务器,防粘包 response = self.get_response()#服务器返回文件 的信息
if response["status_code"] ==247:#如文件存在
if name_down==True and response['file_size']==filesize:
print('文件已经下载完成')
self.c.send(b'')
return
self.c.send(b'')#发送到服务器,表示可以接收文件了
#if name_down:
received_size = filesize#当前接收的数据大小
#else:
#received_size = 0#当前接收的数据大小 file_obj = open(file_path,"ab")#打开文件
if self.cmd_md5_(cmd_list):
md5_obj = hashlib.md5()
progress = self.show_pr(response['file_size']) #进度条 传入文件大小
progress.__next__()
while received_size< response['file_size']:
if response['file_size'] - received_size>1024:#表示接收不止一次
size=1024
else:#最后一次
size=response['file_size'] - received_size
#print('最后一个大小',size)
data= self.c.recv(size)#接收数据 try:
progress.send(len(data))#传入当前数据大小
except StopIteration as e:
print("100%")
received_size+=len(data)#接收数据大小累加
file_obj.write(data)#写入文件
md5_obj.update(data)#进行MD5验证
else:
print("下载完成".center(60,'-'))
file_obj.close()
md5_val = md5_obj.hexdigest()#获取MD5
#print(md5_val)
md5_from_server = self.get_response()#服务端的MD5
#print(md5_from_server['md5'])
if md5_from_server['status_code'] == 248:
if md5_from_server['md5'] == md5_val:
print("%s 文件一致性校验成功!" % down_filename)
pass
else:
progress = self.show_pr(response['file_size']) #进度条 传入文件大小
progress.__next__()
while received_size< response['file_size']:
if response['file_size'] - received_size>1024:#表示接收不止一次
size=1024
else:#最后一次
size=response['file_size'] - received_size
#print('最后一个大小',size)
data= self.c.recv(size)#接收数据 try:
progress.send(len(data))#传入当前数据大小
except StopIteration as e:
print("100%")
received_size+=len(data)#接收数据大小累加
file_obj.write(data)#写入文件
pass else:
print("下载完成".center(60,'-'))
file_obj.close()
pass
self.c.send(b'')#发送到服务器,表示可以接收文件了 if __name__=='__main__': c=FTPClient()
c.inter()
| |- - -cfg/#配置目录
| | |- - -__init__.py
| | |- - -config.py#配置文件
#!usr/bin/env python
#-*-coding:utf-8-*-
# Author calmyan import os ,sys
BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量
sys.path.append(BASE_DIR)#增加环境变量
#print(BASE_DIR) PUT_DIR=BASE_DIR+'\putfile\\'#定义用户上传目录文件路径变量
GET_DIR=BASE_DIR+'\down\\'#定义用户下载目录文件路径变量
HELP='help'
CMD_LIST=['ls','pwd','info','help']
|- - -ftp_server/#服务端程序目录
| |- - -__init__.py
| |- - -bin/#启动目录
| | |- - -__init__.py
| | |- - -start.py#服务端视图启动
#!usr/bin/env python
#-*-coding:utf-8-*-
# Author calmyan
import socket,os,json
import os ,sys
BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量
sys.path.append(BASE_DIR)#增加环境变量 from core import main if __name__ == '__main__': main.ArvgHandler()
| | |- - -user_reg.py#用户注册启动
#!usr/bin/env python
#-*-coding:utf-8-*-
# Author calmyan import configparser
import os ,sys
BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量
sys.path.append(BASE_DIR)#增加环境变量
from cfg import config
#修改个信息 磁盘大小
def set_info(name,pwd,size):
config_info=configparser.ConfigParser()#读数据
config_info.read(config.AUTH_FILE)#读文件 用户名密码
#print(config_info.options(name))
config_info[name]={}
config_info.set(name,config.PWD,pwd)#密码
config_info.set(name,config.QUOTATION,size)#磁盘信息
config_info.write(open(config.AUTH_FILE,'w'))#写入文件
file_path='%s/%s'%(config.USER_HOME,name)#拼接目录路径
os.mkdir(file_path)#创建目录
print('创建完成'.center(60,'='))
print('用户名:[%s]\n密码:[%s]\n磁盘空间:[%s]'%(name,pwd,size)) if __name__ == '__main__':
name=input('name:')
pwd=input('pwd:')
size=input('size:')
set_info(name,pwd,size)
| | |- - -userpwd.cfg#用户信息文件
#!usr/bin/env python
#-*-coding:utf-8-*-
# Author calmyan import os ,sys
BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量
sys.path.append(BASE_DIR)#增加环境变量
#HOME_PATH = os.path.join(BASE_DIR, "home") #USER_DIR='%s\\data\\'%BASE_DIR#定义用户数据目录文件路径变量
#USER_DIR='%s/data'%BASE_DIR#定义用户数据目录文件路径变量
#USER_HOME='%s\\home\\'%BASE_DIR#定义用户家目录文件路径变量
USER_HOME='%s/home'%BASE_DIR#定义用户家目录文件路径变量
#LOG_DIR='%s\\log\\'%BASE_DIR#日志目录
USER_LOG='%s/log/user_log.log'%BASE_DIR#日志登陆文件
USER_OPERT='%s/log/user_opert.log'%BASE_DIR#日志操作文件 LOG_LEVEL='DEBUG'#日志级别 AUTH_FILE='%s/cfg/userpwd.cfg'%BASE_DIR#用户名密码文件
HOST='0.0.0.0'# IP
PORT=9500#端口
QUOTATION='Quotation'#磁盘空间
PWD='PWD'#密码
| |- - -core/#服务端主要文件目录
| | |- - -__init__.py
| | |- - -ftp_server.py#服务端主要逻辑 类
#!usr/bin/env python
#-*-coding:utf-8-*-
# Author calmyan
import socketserver,os,json,pickle,configparser,time
time_format='%Y%m%d%H%M%S'#定义时间格式
times=time.strftime(time_format)#定义时间 STATUS_CODE={
230:'文件断点继传',
231:'新文件',
240:'格式出错,格式:{"action":"get","filename":"filename","size":100}',
241:'指令错误',
242:'用户名或密码为空',
243:'用户或密码出错',
244:'用户密码通过校验',
245:'文件不存在或不是文件',
246:'服务器上该文件不存在',
247:'准备发送文件,请接收',
248:'md5',
249:'准备接收文件,请上传',
250:'磁盘空间不够',
251:'当前已经为主目录',
252:'目录正在切换',
253:'正在查看路径',
254:'准备删除文件',
255:'删除文件完成',
256:'目录不存在',
257:'目录已经存在',
258:'目录创建完成',
259:'目录删除完成',
260:'目录不是空的',
}
import os ,sys,hashlib
BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量
sys.path.append(BASE_DIR)#增加环境变量
from cfg import config
from core.logs import log_log
from core.logs import user_opert class MyTCPHandler (socketserver.BaseRequestHandler):# def setup(self):
print('监听中。。。')
#'''用户名与密码是否为空'''
def cmd_auth(self,*args,**kwargs):#用户校验
'''用户名与密码是否为空'''
data=args[0]#获取 传来的数据
if data.get('username') is None or data.get('password') is None:#如果用户名或密码为空
self.send_mge(242)#发送错误码
name=data.get('username')#用户名
pwd=data.get('password')#密码
print(name,pwd)
user=self.authusername(name,pwd)#用户名与密码的校验 获名用户名
if user is None:#用户名不存在
self.send_mge(243)
else:
self.user=name#保存用户名
self.home_dir='%s/%s'%(config.USER_HOME,self.user)#拼接 用户home目录路径 用户根目录
self.user_home_dir=self.home_dir#当前所在目录
# self.user_dir=self.user_home_dir.split('/')[-1]#当前所在目录 相对
self.dir_join()#进行目录拼接
self.send_mge(244,data={'dir':self.user_dir})#相对 目录 #目录拼接
def dir_join(self,*args,**kwargs):
self.user_dir=self.user_home_dir.split(self.home_dir)[-1]+'/'#当前所在目录 相对
print(self.user_dir) #'''用户名与密码的校验''
def authusername(self,name,pwd):
'''用户名与密码的校验'''
config_info=configparser.ConfigParser()#读数据
config_info.read(config.AUTH_FILE)#读文件 用户名密码
if name in config_info.sections():#用户名存
password=config_info[name]['PWD']
if password==pwd:#密码正确
print('通过校验!')
config_info[name]['USERname']=name#名字的新字段
info_str='用户[%s],成功登陆'%name
self.log_log.warning(info_str)#记录日志
#log_log(info_str)
return config_info[name]
else:
info_str='用户[%s],登陆错误'%name
#log_log(info_str)
self.log_log.warning(info_str)#记录日志
return 0 #判断文件 是否存在
def file_name(self,file_path):
if os.path.isfile(file_path):#文件是否存
return True
else:
return False #判断目录是否存在
def file_dir(self,file_path):
if os.path.isdir(file_path):#目录是否存
return True
else:
return False #删除文件
def cmd_rm(self,*args,**kwargs):
cmd_dict=args[0]#获取字典
action=cmd_dict["action"]
filename =cmd_dict['filename']#文件名
file_path='%s/%s'%(self.user_home_dir,filename)#拼接文件路径
if not self.file_name(file_path):
self.send_mge(245)#文件不存在
return
else:
user_size=self.disk_size()#获取磁盘信息
self.send_mge(254,data={'剩余空间':user_size})#准备删除文件
file_size=os.path.getsize(file_path)#获取文件大小
pass
self.request.recv(1) #客户端确认 防粘包
os.remove(file_path)
new_size=float((float(user_size)+float(file_size))/1024000)#空间大小增加
self.set_info(str(new_size))#传入新大小
self.send_mge(255,data={'剩余空间':new_size})#删除文件完成
info_str=self.log_str('删除文件')#生成日志信息
self.user_opert.critical(info_str)#记录日志
return #创建目录
def cmd_mkdir(self,*args,**kwargs):
cmd_dict=args[0]#获取字典
action=cmd_dict["action"]
filename =cmd_dict['filename']#目录名
file_path='%s/%s'%(self.user_home_dir,filename)#拼接目录路径
if self.file_dir(file_path):
self.send_mge(257)#目录已经 存在
return
else:
self.send_mge(256,data={'目录':'创建中...'})#目录创建中
self.request.recv(1) #客户端确认 防粘包
os.mkdir(file_path)#创建目录
self.send_mge(258)#目录完成
info_str=self.log_str('创建目录')#生成日志信息
self.user_opert.critical(info_str)#记录日志
return #删除目录
def cmd_rmdir(self,*args,**kwargs):
cmd_dict=args[0]#获取字典
action=cmd_dict["action"]
filename =cmd_dict['filename']#目录名
file_path='%s/%s'%(self.user_home_dir,filename)#拼接目录路径
if not self.file_dir(file_path):
self.send_mge(256)#目录不存在
return
elif os.listdir(file_path):
self.send_mge(260,data={'目录':'无法删除'})#目录不是空的
return
else:
self.send_mge(257,data={'目录':'删除中...'})#目录创建中
self.request.recv(1) #客户端确认 防粘包
os.rmdir(file_path)#删除目录
self.send_mge(259)#目录删除完成
info_str=self.log_str('删除目录')#生成日志信息
self.user_opert.critical(info_str)#记录日志
return #磁盘空间大小
def disk_size(self):
attr_list=self.user_info()#调用个人信息
put_size=attr_list[1]#取得磁盘信息
user_size=float(put_size)*1024000#字节
return user_size #'''客户端上传文件 '''
def cmd_put(self,*args,**kwargs):
'''客户端上传文件 '''
cmd_dict=args[0]#获取字典
filename =cmd_dict['filename']#文件名
file_size= cmd_dict['size']#文件大小
#user_home_dir='%s/%s'%(config.USER_HOME,self.user)#拼接 用户home目录路径
file_path='%s/%s'%(self.user_home_dir,filename)#拼接文件路径
user_size=self.disk_size()#取得磁盘信息
if float(file_size)>float(user_size):#空间不足
self.send_mge(250,data={'剩余空间':user_size})
return
self.send_mge(249,data={'剩余空间':user_size})#发送一个确认
self.request.recv(1) #客户端确认 防粘包
if self.file_name(file_path):#判断文件名是否存在,
s_file_size=os.path.getsize(file_path)##获取服务器上的文件大小
if file_size>s_file_size:#如果服务器上的文件小于要上传的文件进
tmp_file_size=os.stat(file_path).st_size#计算临时文件大小
reversed_size=tmp_file_size#接收到数据大小
self.send_mge(230,data={'文件大小':reversed_size})#发送临时文件大小
pass
else:# file_size==s_file_size:#如果大小一样
file_path=file_path+'_'+times#命名新的文件 名
reversed_size=0#接收到数据大小
self.send_mge(231)#发送 不是断点文件
pass
else:
reversed_size=0#接收到数据大小
self.send_mge(231)#发送 不是断点文件
pass f=open(file_path,'ab')
self.request.recv(1) #客户端确认 防粘包
if cmd_dict['md5']:#是否有 md5
md5_obj = hashlib.md5() # 进行MD5
while reversed_size< int(file_size):#接收小于文件 大小
if int(file_size) - reversed_size>1024:#表示接收不止一次
size=1024
else:#最后一次
size=int(file_size) - reversed_size
#print('最后一个大小',size)
data= self.request.recv(size)#接收数据
md5_obj.update(data)
reversed_size+=len(data)#接收数据大小累加
f.write(data)#写入文件
else:
f.close()
print('[%s]文件上传完毕'.center(60,'-')%filename)
md5_val = md5_obj.hexdigest()#得出MD5
print(md5_val)
self.send_mge(248,{'md5':md5_val})#发送md5给客户端
else:
while reversed_size< int(file_size):#接收小于文件 大小
if int(file_size) - reversed_size>1024:#表示接收不止一次
size=1024
else:#最后一次
size=int(file_size) - reversed_size
#print('最后一个大小',size)
data= self.request.recv(size)#接收数据
reversed_size+=len(data)#接收数据大小累加
f.write(data)#写入文件
else:
print('[%s]文件上传完毕'%filename.center(60,'-'))
f.close()
new_size=float((float(user_size)-float(file_size))/1024000)#扣除空间大小
self.set_info(str(new_size))#传入新大小
info_str=self.log_str('文件上传')#生成日志信息
self.user_opert.critical(info_str)#记录日志
return #用户下载文件
def cmd_get(self,*args,**kwargs):#用户下载文件
''' 用户下载文件'''
data=args[0]
print(data)
if data.get('filename') is None:#判断文件名不为空
self.send_mge(245)
return self.request.recv(1) #客户端确认 防粘包
file_path='%s/%s'%(self.user_home_dir,data.get('filename'))#拼接文件路径 用户文件路径
if os.path.isfile(file_path):#判断文件是否存在
file_obj=open(file_path,'rb')#打开文件句柄\
file_size=os.path.getsize(file_path)#获取文件大小
if data['name_down']:
send_size=data['size']#已经发送数据大小
#self.send_mge(230,data={'文件大小':file_size})#断点续传
else:
send_size=0
#self.send_mge(231)#非断点续传
#self.request.recv(1) #客户端确认 防粘包
file_obj.seek(send_size)#移动到
self.send_mge(247,data={'file_size':file_size})#发送相关信息
attr=self.request.recv(1024) #客户端确认 防粘包
if attr.decode()=='':return #如果返回是
if data.get('md5'):
md5_obj = hashlib.md5()
while send_size<file_size:
line=file_obj.read(1024)
#for line in file_obj:
self.request.send(line)
md5_obj.update(line)
else:
file_obj.close()
md5_val = md5_obj.hexdigest()
self.send_mge(248,{'md5':md5_val})
print("发送完毕.")
else:
while send_size<file_size:
line=file_obj.read(1024)
#for line in file_obj:
self.request.send(line)
else:
file_obj.close()
print("发送完毕.")
self.request.recv(1) #客户端确认 防粘包
info_str=self.log_str('下载文件')#生成日志信息
#user_opert(info_str)#记录日志
self.user_opert.critical(info_str)#记录日志
return #切换目录
def cmd_cd(self,cmd_dict,*args,**kwargs):
'''切换目录'''
cmd_attr=cmd_dict['actionname']#获取命令
if cmd_attr=='..' or cmd_attr=='../..':
if (self.home_dir)==self.user_home_dir:
self.send_mge(251)
return
elif cmd_attr=='../..':
self.send_mge(252)#可以切换到上级目录
self.user_home_dir=self.home_dir#绝对目录 = home
self.user_dir='/'
clinet_ack=self.request.recv(1024)#为了去粘包
self.request.send(self.user_dir.encode())#返回相对目录
return
else:
self.send_mge(252)#可以切换到上级目录
print(self.user_home_dir)#绝对目录
print(os.path.dirname(self.user_home_dir))#父级目录
self.user_home_dir=os.path.dirname(self.user_home_dir)#父级目录
self.dir_join()#目录拼接切换
clinet_ack=self.request.recv(1024)#为了去粘包
self.request.send(self.user_dir.encode())#返回相对目录
return elif os.path.isdir(self.user_home_dir+'/'+cmd_attr):#如果目录存在
self.send_mge(252)
self.user_home_dir=self.user_home_dir+'/'+cmd_attr#目录拼接
self.dir_join()#相对目录拼接切换
clinet_ack=self.request.recv(1024)#为了去粘包
print(clinet_ack.decode())
self.request.send(self.user_dir.encode())
return
else:
self.send_mge(256)#目录不存在
return #查看目录路径 CD
def cmd_pwd(self,cmd_dict):
self.request.send(str(len(self.user_dir.encode('utf-8'))).encode('utf-8'))#发送大小
clinet_ack=self.request.recv(1024)#为了去粘包
self.request.send(self.user_dir.encode())#发送相对路径
info_str=self.log_str('查看目录路径')#生成日志信息
#logger.warning
self.user_opert.critical(info_str)#记录日志
return #修改个信息 磁盘大小
def set_info(self,new_size):
config_info=configparser.ConfigParser()#读数据
config_info.read(config.AUTH_FILE)#读文件 用户名密码
print(config_info.options(self.user))
config_info.set(self.user,config.QUOTATION,new_size)
config_info.write(open(config.AUTH_FILE,'w')) #读取个人信息
def user_info(self):
config_info=configparser.ConfigParser()#读数据
config_info.read(config.AUTH_FILE)#读文件 用户名密码
print(config_info.options(self.user))
pwds=config_info[self.user][config.PWD]#密码
Quotation=config_info[self.user][config.QUOTATION]#磁盘配额 剩余
user_info={}
user_info['用户名']=self.user
user_info['密码']=pwds
user_info['剩余磁盘配额']=Quotation
return user_info,Quotation #查看用户信息
def cmd_info(self,*args,**kwargs):
attr=self.user_info()
info_dict=attr[0]
self.request.send(str(len(json.dumps(info_dict))).encode('utf-8'))#
clinet_ack=self.request.recv(1024)#为了去粘包
self.request.send(json.dumps(info_dict).encode('utf-8'))#发送指令
info_str=self.log_str('查看用户信息')#生成日志信息
self.user_opert.critical(info_str)#记录日志
return #日志信息生成
def log_str(self,msg,**kwargs):
info_str='用户[%s]进行了[%s]操作'%(self.user,msg)
return info_str #目录查看
def cmd_ls(self,*args,**kwargs):
data=os.listdir(self.user_home_dir)#查看目录文件
print(data)
datas=json.dumps(data)#转成json格式
self.request.send(str(len(datas.encode('utf-8'))).encode('utf-8'))#发送大小
clinet_ack=self.request.recv(1024)#为了去粘包
self.request.send(datas.encode('utf-8'))#发送指令
info_str=self.log_str('目录查看')#生成日志信息
self.user_opert.critical(info_str)#记录日志
return
##单个命令
def cmd_compr(self,cmd_dict,**kwargs):
attr=cmd_dict['actionname']#赋于变量
if hasattr(self,'cmd_%s'%attr):#是否存在
func=getattr(self,'cmd_%s'%attr)#调用
func(cmd_dict)
return
else:
print('没有相关命令!')
self.send_mge(241)
return #'''发送信息码给客户端'''
def send_mge(self,status_code,data=None):
'''发送信息码给客户端'''
mge={'status_code':status_code,'status_msg':STATUS_CODE[status_code]}#消息
if data:#不为空
mge.update(data)#提示码进行更新
print(mge)
self.request.send(json.dumps(mge).encode())#发送给客户端 #重写handle方法
def handle(self):#重写handle方法
while True:
#try:
self.data=self.request.recv(1024).strip()#接收数据
print('ip:{}'.format(self.client_address[0]))#连接的ip
print(self.data)
self.log_log=log_log()#登陆日志
self.user_opert=user_opert()#操作日志
if not self.data:
print("[%s]客户端断开了!."%self.user)
info_str='用户[%s],退出'%self.user break
cmd_dict=json.loads(self.data.decode())#接收 数据
if cmd_dict.get('action') is not None:#判断数据格式正确
action=cmd_dict['action']#文件 头
if hasattr(self,'cmd_%s'%action):#是否存在
func=getattr(self,'cmd_%s'%action)#调用
func(cmd_dict)
else:
print('没有相关命令!')
self.send_mge(241)
else:
print('数据出错!')
self.send_mge(240)
#except Exception as e:
# print('客户端断开了!',e)
# break
| | |- - -logs.py#日志主要逻辑 类
#!usr/bin/env python
#-*-coding:utf-8-*-
# Author calmyan
import os,logging,time
from cfg import config
LOG_LEVEL=config.LOG_LEVEL def log_log():#登陆日志,传入内容
logger=logging.getLogger('用户成功登陆日志')#设置日志模块
logger.setLevel(logging.DEBUG)
fh=logging.FileHandler(config.USER_LOG,encoding='utf-8')#写入文件
fh.setLevel(config.LOG_LEVEL)#写入信息的级别
fh_format=logging.Formatter('%(asctime)s %(message)s',datefmt='%m/%d/%Y %I:%M:%S %p')#日志格式
fh.setFormatter(fh_format)#关联格式
logger.addHandler(fh)#添加日志输出模式
#logger.warning(info_str)
return logger def user_opert():#用户操作日志,传入内容
logger=logging.getLogger('用户操作日志')#设置日志模块
logger.setLevel(logging.CRITICAL)
fh=logging.FileHandler(config.USER_OPERT,encoding='utf-8')#写入文件
fh.setLevel(config.LOG_LEVEL)#写入信息的级别
fh_format=logging.Formatter('%(asctime)s %(message)s',datefmt='%m/%d/%Y %I:%M:%S %p')#日志格式
fh.setFormatter(fh_format)#关联格式
logger.addHandler(fh)#添加日志输出模式
#logger.critical(info_str)
return logger
| | |- - -main.py#服务端启动主程序
#!usr/bin/env python
#-*-coding:utf-8-*-
# Author calmyan import socketserver,os,json,pickle
import os ,sys
BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取相对路径转为绝对路径赋于变量
sys.path.append(BASE_DIR)#增加环境变量
from cfg import config from core.ftp_server import MyTCPHandler import optparse
class ArvgHandler(object):
def __init__(self):# 可 传入系统参数
self.paresr=optparse.OptionParser()#启用模块
#self.paresr.add_option('-s','--host',dest='host',help='服务绑定地址')
#self.paresr.add_option('-s','--port',dest='host',help='服务端口')
(options,args)=self.paresr.parse_args()#返回一个字典与列表的元组 self.verufy_args(options,args)#进行校验
def verufy_args(self,options,args):
'''校验与调用'''
if hasattr(self,args[0]):#反射判断参数
func=getattr(self,args[0])#生成一个实例
func()#开始调用
else:
self.paresr.print_help()#打印帮助文档
def start(self):
print('服务启动中....')
s=socketserver.ThreadingTCPServer((config.HOST,config.PORT),MyTCPHandler)#实例化一个服务端对象
s.serve_forever()#运行服务器
print('服务关闭')
python第四十八天--高级FTP的更多相关文章
- 孤荷凌寒自学python第四十八天通用同一数据库中复制数据表函数最终完成
孤荷凌寒自学python第四十八天通用同一数据库中复制数据表函数最终完成 (完整学习过程屏幕记录视频地址在文末) 今天继续建构自感觉用起来顺手些的自定义模块和类的代码. 今天经过反复折腾,最终基本上算 ...
- python作业高级FTP
转载自:https://www.cnblogs.com/sean-yao/p/7882638.html 作业需求: 1. 用户加密认证 2. 多用户同时登陆 3. 每个用户有自己的家目录且只能访问自己 ...
- (转)python高级FTP
原文地址:http://www.itnose.net/detail/6754889.html高级FTP服务器1. 用户加密认证2. 多用户同时登陆3. 每个用户有自己的家目录且只能访问自己的家目录4. ...
- python作业高级FTP(第八周)
作业需求: 1. 用户加密认证 2. 多用户同时登陆 3. 每个用户有自己的家目录且只能访问自己的家目录 4. 对用户进行磁盘配额.不同用户配额可不同 5. 用户可以登陆server后,可切换目录 6 ...
- python学习第四十八天json模块与pickle模块差异
在开发过程中,字符串和python数据类型进行转换,下面比较python学习第四十八天json模块与pickle模块差异. json 的优点和缺点 优点 跨语言,体积小 缺点 只能支持 int st ...
- Python09作业思路及源码:高级FTP服务器开发(仅供参考)
高级FTP服务器开发 一,作业要求 高级FTP服务器开发 用户加密认证(完成) 多用户同时登陆(完成) 每个用户有不同家目录且只能访问自己的家目录(完成) 对用户进行磁盘配额,不同用户配额可不同(完成 ...
- 简学Python第四章__装饰器、迭代器、列表生成式
Python第四章__装饰器.迭代器 欢迎加入Linux_Python学习群 群号:478616847 目录: 列表生成式 生成器 迭代器 单层装饰器(无参) 多层装饰器(有参) 冒泡算法 代码开发 ...
- 孤荷凌寒自学python第五十八天成功使用python来连接上远端MongoDb数据库
孤荷凌寒自学python第五十八天成功使用python来连接上远端MongoDb数据库 (完整学习过程屏幕记录视频地址在文末) 今天是学习mongoDB数据库的第四天.今天的感觉是,mongoDB数据 ...
- 孤荷凌寒自学python第三十八天初识python的线程控制
孤荷凌寒自学python第三十八天初识python的线程控制 (完整学习过程屏幕记录视频地址在文末,手写笔记在文末) 一.线程 在操作系统中存在着很多的可执行的应用程序,每个应用程序启动后,就可以看 ...
随机推荐
- 启动elasticsearch报错
could not find java; set JAVA_HOME or ensure java is in PATH 首先需要安装java 1.yum list installed |grep j ...
- 14-03 java BigInteger类,BigDecimal类,Date类,DateFormat类,Calendar类
BigInteger类 发 package cn.itcast_01; import java.math.BigInteger; /* * BigInteger:可以让超过Integer范围内的数据进 ...
- 【转】php结合redis实现高并发下的抢购、秒杀功能
抢购.秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个:1 高并发对数据库产生的压力2 竞争状态下如何解决库存的正确减少("超卖"问题)对于第一个问题,已经很容易想到用缓存 ...
- 使用crypt配置Basic Auth登录认证
简介 Basic Auth用于服务端简单的登录认证,通常使用服务器Nginx.Apache本身即可完成.比如我们要限定某个域名或者页面必须输入用户名.密码才能登录,但又不想使用后端开发语言,此时Bas ...
- Python爬取网易云歌单
目录 1. 关键点 2. 效果图 3. 源代码 1. 关键点 使用单线程爬取,未登录,爬取网易云歌单主要有三个关键点: url为https://music.163.com/discover/playl ...
- Linux 常用命令 | mkdir/rmdir/touch 的使用
一.创建空目录 命令:mkdir 原意:make directories 所在路径: /bin/mkdir 1.创建空目录 2.递归创建空目录 选项:-p 如果直接使用mkdir 创建空目录: W ...
- Java并发编程笔记之CyclicBarrier源码分析
JUC 中 回环屏障 CyclicBarrier 的使用与分析,它也可以实现像 CountDownLatch 一样让一组线程全部到达一个状态后再全部同时执行,但是 CyclicBarrier 可以被复 ...
- React Native顶|底部导航使用小技巧
导航一直是App开发中比较重要的一个组件,ReactNative提供了两种导航组件供我们使用,分别是:NavigatorIOS和Navigator,但是前者只能用于iOS平台,后者在ReactNati ...
- PXE | 开关机
PXE | 开关机流程 linuxPXE 主要阶段 引导的主要6个阶段 从MBR中读取引导加载程序boot loader 加载并初始化内核: 检测和配置设备: 创建内核进程: 系统管理员干预(单用户模 ...
- Bootstrap-table 使用总结
一.什么是Bootstrap-table? 在业务系统开发中,对表格记录的查询.分页.排序等处理是非常常见的,在Web开发中,可以采用很多功能强大的插件来满足要求,且能极大的提高开发效率,本随笔介绍这 ...