通过分段传送避免FTP传输文件假死

背景

在工作过程中,经常需要在不同的服务器之间用FTP拷贝文件。

如果文件过大,网络状况不好的时候,会出现假死的现象,即一个文件传输成功,客户端却没有感知到,一直阻塞,无法继续执行脚本。

原因

FTP使用两个TCP连接来通信,一条控制连接(control connection)用来提交命令和接受回复;一条数据连接(data connection)来处理实际的文件传输。在文件传输过程中,控制连接是很容易进入空闲状态的,TCP标准也没有规定一个连接的最大空闲时间。但是路由器和防火墙经常会把空闲的连接给关闭掉,并且不通知双方,就造成了传输100%但最后还是超时的现象。

常见的解决方案如下:

①将大文件切分成较小的文件进行传输,传输后再拼接成大文件

②另起线程监控FTP的传输,再监控是否传输完成,完成后杀掉FTP传输线程

由于仅有FTP服务器的FTP账户,因此对于方案①无法完成文件的拼接。后来,无意间看到一篇关于FTP断点重传的介绍,因此想到基于断点重传实现大文件的传送。

即将FTP上传拆分成若干次,每次基于上次传输进行断点重传。代码如下。

import os
import sys
from ftplib import FTP


class ChunckFTP(FTP):
    def storbinary(self, cmd, fp, blocksize=8192, chunck_count=-1, callback=None, rest=None, chunck_rest=0):
        self.voidcmd('TYPE I')
        conn = self.transfercmd(cmd, rest)
        count = 0
        try:
            while 1:
                buf = fp.read(blocksize)
                if not buf:
                    break
                conn.sendall(buf)
                if callback:
                    callback(buf)
                # out chunck cout
                count += 1
                if count >= chunck_count > 0:
                    break
        finally:
            conn.close()
        return self.voidresp()


def sep_trans(ftp, remote_file, local_file, chunck_size=8192, chunck_count=4096):
    file_size = os.path.getsize(local_file)
    cmd = 'STOR ' + remote_file
    file_out = open(local_file, 'rb')
    for i in range(0, file_size, chunck_size * chunck_count):
        ftp.storbinary(cmd, file_out, blocksize=chunck_size, chunck_count=chunck_count, rest=i)


def main(ftp_host, ftp_name, ftp_password, local_path, local_file_name, remote_path):
    ftp = ChunckFTP(host=ftp_host, user=ftp_name, passwd=ftp_password)
    remote_file = os.path.join(remote_path, local_file_name + "test")
    local_file = os.path.join(local_path, local_file_name)
    ftp.set_debuglevel(2)
    build_remote_dir(ftp, remote_path)
    sep_trans(ftp, remote_file, local_file)
    ftp.quit()


def build_remote_dir(ftp, remote_dir):
    path_list = remote_dir.split('/')
    temp_path = ''
    for path in path_list:
        temp_path = os.path.join(temp_path, path)
        if path and not ftp.nlst(temp_path):
            ftp.mkd(temp_path)


if __name__ == '__main__':
    ftp_host = sys.argv[1]
    ftp_name = sys.argv[2]
    ftp_password = sys.argv[3]
    local_path = sys.argv[4]
    local_file_name = sys.argv[5]
    remote_path = sys.argv[6]
    main(ftp_host, ftp_name, ftp_password, local_path, local_file_name, remote_path)

参考:https://wenku.baidu.com/view/5fda4462bed5b9f3f80f1c27.html?re=view

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值