前言
下载文件时,最怕中途断线,无法成功下载完整的文件。断点续传就是从文件中断的地方接下去下载,而不必重新下载。这项功能对于下载较大文件时非常有用。那么这篇文章就来给大家分享如何利用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/env python # -*- coding: utf-8 -*- import os 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/env python
# -*- coding: utf-8 -*-
import sys,os
import time
import socket
import hashlib
import pickle
import subprocess
import socketserver
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from config import settings
new=settings.NEW_FILENAME
class Myserver(socketserver.BaseRequestHandler):
def recv_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)
if file_name in new: #检测文件是否已存在,涉及断点续传
has_recv=os.stat(new).st_size #计算临时文件大小
conn.sendall(bytes(has_recv,encoding=\'utf-8\'))
with open(new_file_name,\'ab\') as f: #追加模式
while has_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开始发送
with open(new_file_name,\'wb\') as f:
while has_recv<=int(file_size):
data=conn.recv(1024)
f.write(data)
has_recv+=len(data)
def command(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)
def md5(self,pwd):
\'\'\'
对密码进行加密
:param pwd: 密码
:return:
\'\'\'
hash=hashlib.md5(bytes(\'xx7\',encoding=\'utf-8\'))
hash.update(bytes(pwd,encoding=\'utf-8\'))
return hash.hexdigest()
def login(self,usrname,pwd):
\'\'\'
登陆
:param usrname: 用户名
:param pwd: 密码
:return:是否登陆成功
\'\'\'
conn=self.request
s=pickle.load(open(settings.NAME_PWD,\'rb\'))
if usrname in s:
if s[usrname]==self.md5(pwd): #和加密后的密码进行比较
return True
else:
return False
else:
return False
def regist(self,usrname,pwd):
\'\'\'
注册
:param usrname: 用户名
:param pwd: 密码
:return:是否注册成功
\'\'\'
conn=self.request
s=pickle.load(open(settings.NAME_PWD,\'rb\'))
if usrname in s:
return False
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\'))
return True
def before(self,usrname,pwd,ret):
\'\'\'
判断注册和登陆,并展示用户的详细目录信息,支持cd和ls命令
:return:
\'\'\'
conn=self.request
if ret==\'1\':
r=self.login(usrname,pwd)
if r:
conn.sendall(bytes(\'y\',encoding=\'utf-8\'))
else:
conn.sendall(bytes(\'n\',encoding=\'utf-8\'))
elif ret==\'2\':
# print(usrname,pwd)
r=self.regist(usrname,pwd)
if r:
conn.sendall(bytes(\'y\',encoding=\'utf-8\'))
else:
conn.sendall(bytes(\'n\',encoding=\'utf-8\'))
def usr_file(self,usrname):
\'\'\'
展示用户的详细目录信息,支持cd和ls命令
:param usrname: 用户名
:return:
\'\'\'
conn=self.request
conn.recv(1024)
mulu=os.path.join(settings.USER_FILE,usrname)
conn.sendall(bytes(mulu,encoding=\'utf-8\'))
while True:
b=conn.recv(1024)
ret=str(b,encoding=\'utf-8\')
try:
a,b=ret.split(\' \',1)
except Exception as e:
a=ret
if a==\'cd\':
if b==\'..\':
mulu=os.path.dirname(mulu)
else:
mulu=os.path.join(mulu,b)
conn.sendall(bytes(mulu,encoding=\'utf-8\'))
elif a==\'ls\':
ls=os.listdir(mulu)
print(ls)
a=\',\'.join(ls)
conn.sendall(bytes(a,encoding=\'utf-8\'))
elif a==\'q\':
break
def handle(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(\'b ok\',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命令
while True:
a=conn.recv(1024)
conn.sendall(bytes(\'收到a\',encoding=\'utf-8\'))
ret=str(a,encoding=\'utf-8\')
if ret==\'1\':
self.recv_file()
# conn.sendall(bytes(\'file ok\',encoding=\'utf-8\'))
elif ret==\'2\':
self.command()
elif ret==\'q\':
break
else:
pass
if __name__==\'__main__\':
sever=socketserver.ThreadingTCPServer((\'127.0.0.1\',9999),Myserver)
sever.serve_forever()
客户端:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import time
import os
import socket
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from config import settings
def send_file(file_path):
\'\'\'
发送文件
:param file_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\')
if r==\'s\': #文件不存在,从头开始传
has_send=0
else: #文件存在
has_send=int(r)
with open(file_path,\'rb\') as f:
f.seek(has_send) #定位到已经传到的位置
while has_send:\')
obj.sendall(bytes(a,encoding=\'utf-8\'))
if a==\'q\':
break
else:
ret=obj.recv(1024) #等待客户端发送
r=str(ret,encoding=\'utf-8\')
if len(r)==1:#判断是cd结果还是ls的结果(ls只有一个子目录也可以直接打印)
print(r)
else:
li=r.split(\',\')
for i in li:
print(i) #打印每一个子目录
def main(usrname,pwd):
ret=obj.recv(1024) #等待客户端发送
r=str(ret,encoding=\'utf-8\')
print(r)
result=before(usrname,pwd)#登陆或注册
if result:
usr_file(usrname)
while True:
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)
if a==\'1\':
b=input(\'请输入文件路径(测试版路径为:f.png):\')
# b=\'f.png\'
if os.path.exists(b):
send_file(b)
obj.sendall(bytes(\'hhe\',encoding=\'utf-8\'))
# obj.recv(1024)
elif a==\'2\':
b=input(\'请输入command:\')
command(b)
elif a==\'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带来一定的帮助。