背景
在工作过程中,经常需要在不同的服务器之间用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