python实现可以断点续传和并发的ftp程序


Posted in Python onSeptember 13, 2016

前言

下载文件时,最怕中途断线,无法成功下载完整的文件。断点续传就是从文件中断的地方接下去下载,而不必重新下载。这项功能对于下载较大文件时非常有用。那么这篇文章就来给大家分享如何利用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<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")
 
def command(command_name):
  '''
  执行命令
  :param command_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')
  for i in range(int(r)): #共需要接收int(r)次
    ret=obj.recv(1024) #等待客户端发送
    r=str(ret,encoding='GBK')
    print(r)
 
def login(usrname,pwd):
  '''
  登陆
  :param usrname:用户名
  :param pwd:密码
  :return:是否登陆成功
  '''
  obj.sendall(bytes(usrname+','+pwd,encoding='utf-8'))
  ret=obj.recv(1024)
  r=str(ret,encoding='utf-8')
  if r=='y':
    return 1
  else:
    return 0
 
def regist(usrname,pwd):
  '''
  注册
  :param usrname:用户名
  :param pwd:密码
  :return:是否注册成功
  '''
  obj.sendall(bytes(usrname+','+pwd,encoding='utf-8'))
  ret=obj.recv(1024)
  r=str(ret,encoding='utf-8')
  if r=='y':
    return 1
  else:
    return 0
def before(usrname,pwd):
  '''
  选择登陆或注册,展示用户的详细目录信息,支持cd和ls命令
  :return:
  '''
  a=input('请选择1.登陆 2.注册')
  obj.sendall(bytes(a,encoding='utf-8'))
  obj.recv(1024)
  if a=='1':
    ret=login(usrname,pwd)
    if ret:
      print('登陆成功')
      return 1
    else:
      print('用户名或密码错误')
      return 0
  elif a=='2':
    ret=regist(usrname,pwd)
    if ret:
      print('注册成功')
      return 1
    else:
      print('用户名已存在')
      return 0
def usr_file(usrname):
  obj.sendall(bytes('打印用户文件路径',encoding='utf-8'))
  ret=obj.recv(1024) #等待客户端发送
  r=str(ret,encoding='utf-8')
  print(r)
  while True:
    a=input('输入cd切换目录,ls查看目录详细信息,q退出>:')
 
    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带来一定的帮助。

Python 相关文章推荐
Python中操作mysql的pymysql模块详解
Sep 13 Python
Flask框架的学习指南之用户登录管理
Nov 20 Python
Python中协程用法代码详解
Feb 10 Python
python抓取网页内容并进行语音播报的方法
Dec 24 Python
Python中的几种矩阵乘法(小结)
Jul 10 Python
对django views中 request, response的常用操作详解
Jul 17 Python
基于MATLAB和Python实现MFCC特征参数提取
Aug 13 Python
Python Gitlab Api 使用方法
Aug 28 Python
python except异常处理之后不退出,解决异常继续执行的实现
Apr 25 Python
Python实现CAN报文转换工具教程
May 05 Python
解决运行出现'dict' object has no attribute 'has_key'问题
Jul 15 Python
基于python实现删除指定文件类型
Jul 21 Python
Python安装第三方库及常见问题处理方法汇总
Sep 13 #Python
Python中操作mysql的pymysql模块详解
Sep 13 #Python
python常用函数详解
Sep 13 #Python
python如何查看系统网络流量的信息
Sep 12 #Python
Python爬取三国演义的实现方法
Sep 12 #Python
python 读写、创建 文件的方法(必看)
Sep 12 #Python
Python读写Json涉及到中文的处理方法
Sep 12 #Python
You might like
php程序的国际化实现方法(利用gettext)
2011/08/14 PHP
使用php测试硬盘写入速度示例
2014/01/27 PHP
ThinkPHP模板比较标签用法详解
2014/06/30 PHP
PHP实现定时执行任务的方法
2014/10/05 PHP
PHP与MYSQL中UTF8编码的中文排序实例
2014/10/21 PHP
深入解析PHP的Yii框架中的event事件机制
2016/03/17 PHP
PHP简单遍历对象示例
2016/09/28 PHP
利用php抓取蜘蛛爬虫痕迹的示例代码
2016/09/30 PHP
ThinkPHP使用getlist方法实现数据搜索功能示例
2017/05/08 PHP
解析使用js判断只能输入数字、字母等验证的方法(总结)
2013/05/14 Javascript
jQuery对象的length属性用法实例
2014/12/27 Javascript
js实现简单随机抽奖的方法
2015/01/27 Javascript
第一次接触神奇的Bootstrap网格系统
2016/07/27 Javascript
jQuery实现简单的tab标签页效果
2016/09/12 Javascript
微信小程序 教程之条件渲染
2016/10/18 Javascript
JS如何生成一个不重复的ID的函数
2016/12/25 Javascript
Vue中div contenteditable 的光标定位方法
2018/08/25 Javascript
angularjs http与后台交互的实现示例
2018/12/21 Javascript
用Python的Tornado框架结合memcached页面改善博客性能
2015/04/24 Python
详解Python的单元测试
2015/04/28 Python
python删除过期文件的方法
2015/05/29 Python
Python的Django框架可适配的各种数据库介绍
2015/07/15 Python
python中input()与raw_input()的区别分析
2016/02/27 Python
python调用百度REST API实现语音识别
2018/08/30 Python
python 运用Django 开发后台接口的实例
2018/12/11 Python
在pycharm下设置自己的个性模版方法
2019/07/15 Python
python实现while循环打印星星的四种形状
2019/11/23 Python
解决pytorch 交叉熵损失输出为负数的问题
2020/07/07 Python
AE美国鹰日本官方网站: American Eagle Outfitters
2016/12/10 全球购物
速卖通欧盟:Aliexpress EU
2020/08/19 全球购物
会议邀请函范文
2014/01/09 职场文书
学雷锋演讲稿
2014/03/04 职场文书
大一学生职业生涯规划
2014/03/11 职场文书
农村老人去世追悼词
2015/06/23 职场文书
十大冰系宝可梦排名,颜值最高的阿罗拉九尾,第三使用率第一
2022/03/18 日漫
xhunter1.sys可以删除嘛? win11提示xhunter1.sys驱动不兼容解决办法
2022/09/23 数码科技