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带来一定的帮助。