python实现可以断点续传和并发的ftp程序
前言
下载文件时,最怕中途断线,无法成功下载完整的文件。断点续传就是从文件中断的地方接下去下载,而不必重新下载。这项功能对于下载较大文件时非常有用。那么这篇文章就来给大家分享如何利用python实现可以断点续传和并发的ftp程序。
一、要求
1、用户md5认证
2、支持多用户同时登陆(并发)
3、进入用户的命令行模式,支持cd切换目录,ls查看目录子文件
4、执行命令(ipconfig)
5、传输文件:
a、支持断点续传
b、传输中显示进度条
二、思路
1.客户端用户登录和注册:
a、客户端仅提供用户名和密码,选择登录或注册,
b、服务器端进行注册并将加密后的密码写入文件,最后返回给客户端是否登录或注册成功
2.ls和cd命令
a、客户端输入命令,服务器端处理并返回给客户端
3.执行命令:
a、客户端发送需要执行的命令
b、服务器端执行命令,并返回客户端需要接收该命令的次数s=r[0]+1,其中r=divmod(结果总长度,1024)
c、客户端收到次数,告诉服务端已经收到
d、服务端发送执行结果,客户端进行for循环接收该结果
4.发送文件:
a、客户端输入文件路径(测试版路径为:f.png),发送文件名和文件大小
b、服务器端检测指定目录是否含有该文件,如果没有,返回给客户端字符串s,即从头开始发送start,has_recv=0
如果有,即需要断点续传,返回给客户端已经上传了多少has_recv
c、客户端接收返回值,并seek到has_recv的位置,进行循环收发,打印当前进度,直到传输完毕。
注:本程序可循环接收用户选择传输文件和执行命令
三、代码
配置文件:
#!/usr/bin/envpython #-*-coding:utf-8-*- importos BASE_DIR=os.path.dirname(os.path.dirname(__file__))#配置文件的上层目录 NEW_FILENAME=os.path.join(BASE_DIR,'view')#新文件目录 NAME_PWD=os.path.join(BASE_DIR,'db','name_pwd')#用户名和密码目录 USER_FILE=os.path.join(BASE_DIR,'db')
服务器端:
#!/usr/bin/envpython
#-*-coding:utf-8-*-
importsys,os
importtime
importsocket
importhashlib
importpickle
importsubprocess
importsocketserver
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
fromconfigimportsettings
new=settings.NEW_FILENAME
classMyserver(socketserver.BaseRequestHandler):
defrecv_file(self):
'''
文件传输
:return:
'''
conn=self.request
a=str(conn.recv(1024),encoding='utf-8')
file_size,file_name=a.split(',')
new_file_name=os.path.join(new,file_name)
iffile_nameinnew:#检测文件是否已存在,涉及断点续传
has_recv=os.stat(new).st_size#计算临时文件大小
conn.sendall(bytes(has_recv,encoding='utf-8'))
withopen(new_file_name,'ab')asf:#追加模式
whilehas_recv<=int(file_size):
data=conn.recv(1024)
f.write(data)
has_recv+=len(data)
else:
has_recv=0
conn.sendall(bytes('s',encoding='utf-8'))#客户端收到字符串s,从0开始发送
withopen(new_file_name,'wb')asf:
whilehas_recv<=int(file_size):
data=conn.recv(1024)
f.write(data)
has_recv+=len(data)
defcommand(self):
'''
执行命令
:return:
'''
conn=self.request
a=conn.recv(1024)
ret=str(a,encoding='utf-8')
ret2=subprocess.check_output(ret,shell=True)
r=divmod(len(ret2),1024)
s=r[0]+1#客户端需要接收的次数
conn.sendall(bytes(str(s),encoding='utf-8'))
conn.recv(1024)#确认客户端收到需要接收的次数
conn.sendall(ret2)
defmd5(self,pwd):
'''
对密码进行加密
:parampwd:密码
:return:
'''
hash=hashlib.md5(bytes('xx7',encoding='utf-8'))
hash.update(bytes(pwd,encoding='utf-8'))
returnhash.hexdigest()
deflogin(self,usrname,pwd):
'''
登陆
:paramusrname:用户名
:parampwd:密码
:return:是否登陆成功
'''
conn=self.request
s=pickle.load(open(settings.NAME_PWD,'rb'))
ifusrnameins:
ifs[usrname]==self.md5(pwd):#和加密后的密码进行比较
returnTrue
else:
returnFalse
else:
returnFalse
defregist(self,usrname,pwd):
'''
注册
:paramusrname:用户名
:parampwd:密码
:return:是否注册成功
'''
conn=self.request
s=pickle.load(open(settings.NAME_PWD,'rb'))
ifusrnameins:
returnFalse
else:
s[usrname]=self.md5(pwd)
mulu=os.path.join(settings.USER_FILE,usrname)
os.makedirs(mulu,'a')
pickle.dump(s,open(settings.NAME_PWD,'wb'))
returnTrue
defbefore(self,usrname,pwd,ret):
'''
判断注册和登陆,并展示用户的详细目录信息,支持cd和ls命令
:return:
'''
conn=self.request
ifret=='1':
r=self.login(usrname,pwd)
ifr:
conn.sendall(bytes('y',encoding='utf-8'))
else:
conn.sendall(bytes('n',encoding='utf-8'))
elifret=='2':
#print(usrname,pwd)
r=self.regist(usrname,pwd)
ifr:
conn.sendall(bytes('y',encoding='utf-8'))
else:
conn.sendall(bytes('n',encoding='utf-8'))
defusr_file(self,usrname):
'''
展示用户的详细目录信息,支持cd和ls命令
:paramusrname:用户名
:return:
'''
conn=self.request
conn.recv(1024)
mulu=os.path.join(settings.USER_FILE,usrname)
conn.sendall(bytes(mulu,encoding='utf-8'))
whileTrue:
b=conn.recv(1024)
ret=str(b,encoding='utf-8')
try:
a,b=ret.split('',1)
exceptExceptionase:
a=ret
ifa=='cd':
ifb=='..':
mulu=os.path.dirname(mulu)
else:
mulu=os.path.join(mulu,b)
conn.sendall(bytes(mulu,encoding='utf-8'))
elifa=='ls':
ls=os.listdir(mulu)
print(ls)
a=','.join(ls)
conn.sendall(bytes(a,encoding='utf-8'))
elifa=='q':
break
defhandle(self):
conn=self.request
conn.sendall(bytes('welcome',encoding='utf-8'))
b=conn.recv(1024)
ret=str(b,encoding='utf-8')
print(ret)
conn.sendall(bytes('bok',encoding='utf-8'))
c=conn.recv(1024)
r=str(c,encoding='utf-8')
usrname,pwd=r.split(',')
self.before(usrname,pwd,ret)#登陆或注册验证
self.usr_file(usrname)#展示用户的详细目录信息,支持cd和ls命令
whileTrue:
a=conn.recv(1024)
conn.sendall(bytes('收到a',encoding='utf-8'))
ret=str(a,encoding='utf-8')
ifret=='1':
self.recv_file()
#conn.sendall(bytes('fileok',encoding='utf-8'))
elifret=='2':
self.command()
elifret=='q':
break
else:
pass
if__name__=='__main__':
sever=socketserver.ThreadingTCPServer(('127.0.0.1',9999),Myserver)
sever.serve_forever()
客户端:
#!/usr/bin/envpython
#-*-coding:utf-8-*-
importsys
importtime
importos
importsocket
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
fromconfigimportsettings
defsend_file(file_path):
'''
发送文件
:paramfile_name:文件名
:return:
'''
size=os.stat(file_path).st_size
file_name=os.path.basename(file_path)
obj.sendall(bytes(str(size)+','+file_name,encoding='utf-8'))#发送文件大小和文件名
ret=obj.recv(1024)#接收已经传了多少
r=str(ret,encoding='utf-8')
ifr=='s':#文件不存在,从头开始传
has_send=0
else:#文件存在
has_send=int(r)
withopen(file_path,'rb')asf:
f.seek(has_send)#定位到已经传到的位置
whilehas_send<size:
data=f.read(1024)
obj.sendall(data)
has_send+=len(data)
sys.stdout.write('\r')#清空文件内容
time.sleep(0.2)
sys.stdout.write('已发送%s%%|%s'%(int(has_send/size*100),(round(has_send/size*40)*'★')))
sys.stdout.flush()#强制刷出内存
print("上传成功\n")
defcommand(command_name):
'''
执行命令
:paramcommand_name:
:return:
'''
obj.sendall(bytes(command_name,encoding='utf-8'))
ret=obj.recv(1024)#接收命令需要接收的次数
obj.sendall(bytes('收到次数',encoding='utf-8'))
r=str(ret,encoding='utf-8')
foriinrange(int(r)):#共需要接收int(r)次
ret=obj.recv(1024)#等待客户端发送
r=str(ret,encoding='GBK')
print(r)
deflogin(usrname,pwd):
'''
登陆
:paramusrname:用户名
:parampwd:密码
:return:是否登陆成功
'''
obj.sendall(bytes(usrname+','+pwd,encoding='utf-8'))
ret=obj.recv(1024)
r=str(ret,encoding='utf-8')
ifr=='y':
return1
else:
return0
defregist(usrname,pwd):
'''
注册
:paramusrname:用户名
:parampwd:密码
:return:是否注册成功
'''
obj.sendall(bytes(usrname+','+pwd,encoding='utf-8'))
ret=obj.recv(1024)
r=str(ret,encoding='utf-8')
ifr=='y':
return1
else:
return0
defbefore(usrname,pwd):
'''
选择登陆或注册,展示用户的详细目录信息,支持cd和ls命令
:return:
'''
a=input('请选择1.登陆2.注册')
obj.sendall(bytes(a,encoding='utf-8'))
obj.recv(1024)
ifa=='1':
ret=login(usrname,pwd)
ifret:
print('登陆成功')
return1
else:
print('用户名或密码错误')
return0
elifa=='2':
ret=regist(usrname,pwd)
ifret:
print('注册成功')
return1
else:
print('用户名已存在')
return0
defusr_file(usrname):
obj.sendall(bytes('打印用户文件路径',encoding='utf-8'))
ret=obj.recv(1024)#等待客户端发送
r=str(ret,encoding='utf-8')
print(r)
whileTrue:
a=input('输入cd切换目录,ls查看目录详细信息,q退出>:')
obj.sendall(bytes(a,encoding='utf-8'))
ifa=='q':
break
else:
ret=obj.recv(1024)#等待客户端发送
r=str(ret,encoding='utf-8')
iflen(r)==1:#判断是cd结果还是ls的结果(ls只有一个子目录也可以直接打印)
print(r)
else:
li=r.split(',')
foriinli:
print(i)#打印每一个子目录
defmain(usrname,pwd):
ret=obj.recv(1024)#等待客户端发送
r=str(ret,encoding='utf-8')
print(r)
result=before(usrname,pwd)#登陆或注册
ifresult:
usr_file(usrname)
whileTrue:
a=input('请选择1.传文件2.执行命令q退出:')
obj.sendall(bytes(str(a),encoding='utf-8'))
ret=obj.recv(1024)#确认是否收到a
r=str(ret,encoding='utf-8')
print(r)
ifa=='1':
b=input('请输入文件路径(测试版路径为:f.png):')
#b='f.png'
ifos.path.exists(b):
send_file(b)
obj.sendall(bytes('hhe',encoding='utf-8'))
#obj.recv(1024)
elifa=='2':
b=input('请输入command:')
command(b)
elifa=='q':
break
else:
print('输入错误')
obj.close()
if__name__=='__main__':
obj=socket.socket()#创建客户端socket对象
obj.connect(('127.0.0.1',9999))
usrname=input('请输入用户名')
pwd=input('请输入密码')
main(usrname,pwd)
总结
以上就是python实现可以断点续传和并发的ftp程序的全部内容,文章介绍的很详细,希望对大家学习或者使用python带来一定的帮助。