本次ftpV2,完善了上次未完成的代码,实现上传下载,断点续传,客户端任务进度条等功能
废话不多说,上代码,详细解释,请查看代码注释
#!/usr/bin/env python
# --coding = utf-8
# Author Allen Lee
import socketserver,json,os
import subprocess
#创建ftpserver类,并继承socktserver,借用其io多路复用的select方法,以实现多响应用户的并发
class Myftp(socketserver.BaseRequestHandler):
#再次重写handle方法,由于类的继承中,self遵循自下而上,从左到右的寻址规则,因此被重写的方法将优先被调用
def handle(self):
#权限控制
while True:
if not self.checkuser():
break
else:
self.request.sendall(bytes('welcome to myftp',encoding='utf-8'))
while True:
res_data = self.request.recv(1024).strip()
if not res_data:
break
#再次统一处理client端发出的json包,
res_msg = json.loads(str(res_data,encoding='utf-8'))
#通过反射来引导用户到对应的方法中
res_tag = res_msg.get('action')
if hasattr(self,res_tag):
getattr(self,res_tag)(res_msg)
else:break
self.request.close()
#用户验证
def checkuser(self):
server_tag = False
res = self.request.recv(1024)
if len(res) == 0:
return error
userinfo = json.loads(str(res,encoding='utf-8'))
if userinfo[0].get('username') == 'username' and userinfo[0].get('password') == 'password':
server_tag = True
return True
else:
return False
def put(self,res_msg):
file_name = res_msg.get('file_name')
file_size = res_msg.get('file_size')
ret = os.path.isfile(file_name)
if not ret:
#此为正常传输
with open(file_name,'wb+')as f:
self.request.sendall(bytes('ok',encoding='utf-8'))
recv_data = self.request.recv(1024)
f.write(recv_data)
else:
#此为断点续传处理
with open(file_name,'ab+')as f:
file_newsize = len(file_name)
self.request.sendall(bytes('continue|%s'%(file_newsize), encoding='utf-8'))
recv_data = self.request.recv(1024)
f.write(recv_data)
def get(self,res_msg):
file_name = res_mas.get('file_name')
ret = os.path.isfile(file_name)
if not ret:
print('this file is not exists')
return False
else:
total_size =os.stat(file_name).st_size
with open(file_name,'rb')as f:
self.request.sendall(bytes('ready|%d'%(total_size),encoding='utf-8'))
reg = str(self.request.recv(1024),encoding='utf-8')
if reg.startswith('ok'):
new_size = 0
for line in f:
client.sendall(bytes(line,encoding='utf-8'))
new_size += len(line)
if new_size >= total_size:break
if reg.startswith('continue'):
new_size = reg.split('|')[1]
#使用seek方法来实现将文件指针放在上次断点处,并继续传输,进而完成断点续传的功能,第一个参数表示指针偏移位数,第二个参数表示,从文件开头开偏移
f.seek(new_size,0)
for line in f:
client.sendall(bytes(line,encoding='utf-8'))
new_size +=len(line)
if new_size >= total_size:break
#次方法为处理寻常请求,用户可以在这里实现切换目录、删除文件、创建目录、等等
def cmd(self,res_msg):
cmd = res_msg.get("cmd")
result = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE)
ret = result.stdout.read()
if not ret:
send_data = 'there is no such commons'
print(send_data)
else:
send_data = bytes(ret,encoding='utf-8')
self.request.sendall('ready|%s'%(len(send_data)))
ret = str(self.request.recv(1024),encoding=utf-8)
if ret == 'start':
self.request.sendall(send_data)
if __name__ == '__main__':
#创建对象时使用ThreadindTCP,来实现多线程的处理
ftpserver = socketserver.ThreadingTCPServer(('127.0.0.1', 9909), Myftp)
ftpserver.serve_forever()
#!/usr/bin/env python
# --coding = utf-8
# Author Allen Lee
import socket, os, json,sys
class Myclient():
#在构造方法中启动client的socket对象以及连接server端
def __init__(self):
self.client = socket.socket()
self.client.connect(('127.0.0.1', 9909))
#run为client的主方法,其中通过对用户输入的命令来判断具体操作,并通过反射将其引导到对应的方法
def run(self):
while True:
#权限控制
check = self.login()
#通过while循环实现用户输入代码的复用
while True:
res_data = self.client.recv(1024).strip()
if not res_data:break
print(str(res_data, encoding='utf-8'))
send_data = input('>> : ').strip()
if not send_data:
continue
if hasattr(self,send_data):
getattr(self,send_data)(send_data)
#当用户输入的不是指定的put get方法时,将被引致平常的cmd命令行,在此可以完成切换目录、创建文件、删除文件
else:
getattr(self,'cmd')(send_data)
self.client.close()
#用户登录验证
def login(self):
self.tag = False
for i in range(3):
username = input('please input your username:')
passwd = input('please input your passwd:')
password = commons.md5(passwd)
userinfo = {"username":username,"password":password }
self.client.send(bytes(json.dumps(userinfo),encoding='utf-8'))
ret = self.client.recv(1024)
res_check = str(ret,encoding='utf-8')
if res_check == 'ok':
self.tag = True
return True
else:
return False
print("your username or passwd is wrong")
self.client.close()
def put(self,send_data):
pathh = input('input the file `s path ;')
#首先判断文件是否存在,通过os.path.isfile来实现
ret = os.path.isfile(pathh)
if not ret:
print('the file is not exists')
return False
#如果存在,则将操作类型、文件名、文件大小以字典形势格式化,再以json的方法进行格式化
else:
#通过os.stat方法来取文件的大小
file_size = os.stat(pathh).st_size
file_name = pathh.split('\\')[-1]
send_msg = {"action":send_data,
"filename":file_name,
"filesize":file_size,
}
self.client.sendall(bytes(json.dumps(send_msg),encoding='utf-8'))
res_tag = self.client.recv(1024)
if not res_tag:
return False
if str(res_tag,encoding='utf-8').startswith('ok'):
#上传文件只用r就够了
with open(pathh,'rb') as f:
new_size = 0
for line in f:
self.client.sendall(bytes(line,encoding='utf-8'))
new_size += len(line)
#以下为实现传输文件时,显示当前进度条
ret = new_size/file_size
num = int(ret*100)
view = '\r [%-100s]%d%%'%("="*num,100,)
sys.stdout.write(view)
sys.stdout.flush()
if new_size >= file_size:
break
if str(res_tag,encoding='utf-8').startswith('continue'):
with open(pathh, 'rb') as f:
#此处使用seek来进行文件指针的偏移,进而完成断点续传的功能
file_newsize=str(res_tag,encoding='utf-8').split('|')[1]
f.seek(file_name,0)
for line in f:
self.client.sendall(bytes(line,encoding='utf-8'))
new_size += len(line)
#以下为实现传输文件时,显示当前进度条
ret = new_size/file_size
num = int(ret*100)
#以下使用了sys模块的标准输出
view = '\r [%-100s]%d%%'%("="*num,100,)
sys.stdout.write(view)
sys.stdout.flush()
if new_size >= file_size:
break
def get(self,send_data):
get_name = input('input what your want:').strip()
if not get_name:
return False
send_msg = {"action":send_data,
"filename":get_name
}
self.client.sendall(bytes(json.dumps(send_msg),encoding='utf-8'))
res_data = self.client.recv(1024)
res_tag = str(res_data,encoding='utf-8')
#客户端接收到服务端的确认信息ready之后开始传输,相当于tcp的三次握手中的ack包
if res_tag.startswith('ready'):
#通过判断本地是否有此文件,来确认是否启用断点续传
if not os.path.isfile(get_name):
with open(file_name,'wb')as f:
self.client.sendall(bytes('start', encoding='utf-8'))
total_size = res_data.split('|')[1]
new_size = 0
while new_size 1024)
new_size += len(res_data)
f.write(res_data)
#以下为下载文件的进度条实现
ret = new_size/total_size
num = int(ret*100)
view = '\r [%-100s]%d%%'%('='*num,100)
sys.stdout.write(view)
sys.stdout.flush()
else:
#如果本地不存在此文件将给服务端发送,断点续传的请求标签
with open(file_name,'ab')as f:
new_size = os.stat(file_name).st_size
self.client.sendall(bytes('continue|%d'%(new_size), encoding='utf-8'))
total_size = res_data.split('|')[1]
while new_size 1024)
new_size += len(res_data)
f.write(res_data)
#以下为下载文件的进度条实现
ret = new_size/total_size
num = int(ret*100)
view = '\r [%-100s]%d%%'%('='*num,100)
sys.stdout.write(view)
sys.stdout.flush()
def cmd(self,send_data):
send_msg = {"action":'cmd',
"cmd":send_data,
}
self.client.sendall(bytes(json.dumps(send_msg), encoding='utf-8'))
res_data = self.client.recv(1024)
res_tag = str(res_data, encoding='utf-8')
if res_tag.startswith('ready'):
self.client.sendall(bytes('start', encoding='utf-8'))
#以下为解决发送命令过程中出现的粘报现象,思路为提前告诉对方总大小
total_size = res_data.split('|')[1]
new_size = 0
while new_size < total_size:
res_data = self.client.recv(1024)
new_size += len(res_data)
print(str(res_data, encoding='utf-8'))
def client_exit(self):
self.client.close()
if __name__ == '__main__':
Myclient.run()
具体一些bug还未调试,主题代码思路已经实现