我一直在尝试解决从 ftp/ftps 下载文件时出现的问题。文件已成功下载,但文件下载完成后未执行任何操作。没有发生错误,这可以提供有关该问题的更多信息。我尝试在 stackoverflow 上搜索此问题,并找到了此关联谈到了类似的问题陈述,看起来我也遇到了类似的问题,但我不确定。需要更多帮助来解决问题。
我尝试将 FTP 连接超时设置为 60 分钟,但帮助不大。在此之前,我使用 ftplib 的 retrbinary(),但那里也出现了同样的问题。我尝试传递不同的块大小和窗口大小,但问题仍然重现。
我正在尝试从 AWS EMR 集群下载大小约为 3GB 的文件。示例代码如下所示。
def download_ftp(self, ip, port, user_name, password, file_name, target_path):
try:
os.chdir(target_path)
ftp = FTP(host=ip)
ftp.connect(port=int(port), timeout=3000)
ftp.login(user=user_name, passwd=password)
if ftp.nlst(file_name) != []:
dir = os.path.split(file_name)
ftp.cwd(dir[0])
for filename in ftp.nlst(file_name):
sock = ftp.transfercmd('RETR ' + filename)
def background():
fhandle = open(filename, 'wb')
while True:
block = sock.recv(1024 * 1024)
if not block:
break
fhandle.write(block)
sock.close()
t = threading.Thread(target=background)
t.start()
while t.is_alive():
t.join(60)
ftp.voidcmd('NOOP')
logger.info("File " + filename + " fetched successfully")
return True
else:
logger.error("File " + file_name + " is not present in FTP")
except Exception, e:
logger.error(e)
raise
上述链接中建议的另一个选项是在下载完小块文件后关闭连接,然后重新启动连接。有人能建议如何实现这一点吗?不确定如何在关闭连接之前从上次文件下载停止的同一点恢复下载。这种方法是否是下载整个文件的完整证据?
我不太了解 FTP 服务器级超时设置,所以不知道需要更改什么以及如何更改。我基本上想编写一个通用的 FTP 下载器,它可以帮助从 FTP/FTPS 下载文件。
当我使用 ftplib 的 retrbinary() 方法并将调试级别设置为 2 时。
ftp.set_debuglevel(2)
ftp.retrbinary('RETR ' + filename, fhandle.write)
正在打印以下日志。
命令'I 类' 放‘类型 I\r\n’ 得到'200 类型设置为 I.\r\n' 相应‘200 类型设置为 I。’ 命令'PASV' 放‘PASV\r\n’ 得到'227 进入被动模式 (64,27,160,28,133,251)。\r\n' 相应‘227 进入被动模式 (64,27,160,28,133,251)。’ 命令'返回 FFFT_BRA_PM_R_201711.txt' 放'返回 FFFT_BRA_PM_R_201711.txt\r\n' 得到'150 为 FFFT_BRA_PM_R_201711.txt 打开二进制模式数据连接。\r\n' 相应‘150 打开 FFFT_BRA_PM_R_201711.txt 的二进制模式数据连接。’
答案1
在做任何事情之前,请注意您的连接存在严重问题,诊断并修复它比绕过它要好得多。但有时,您只需要处理损坏的服务器,即使发送保持连接也无济于事。那么,您能做什么呢?
诀窍是一次下载一个块,然后中止下载 - 或者,如果服务器无法处理中止,则关闭并重新打开连接。
请注意,我正在使用以下所有内容进行测试ftp://speedtest.tele2.net/5MB.zip,希望这不会导致数百万人开始攻击他们的服务器。当然,您需要使用实际服务器进行测试。
测试REST
当然,整个解决方案依赖于服务器能够恢复传输,但并非所有服务器都能做到这一点——尤其是当您处理严重损坏的服务器时。所以我们需要对此进行测试。请注意,此测试将非常缓慢,并且对服务器的负担非常大,因此不要使用 3GB 文件进行测试;找一些小得多的文件。此外,如果您可以在那里放置一些可读的内容,这将有助于调试,因为您可能无法在十六进制编辑器中比较文件。
def downit():
with open('5MB.zip', 'wb') as f:
while True:
ftp = FTP(host='speedtest.tele2.net', user='anonymous', passwd='[email protected]')
pos = f.tell()
print(pos)
ftp.sendcmd('TYPE I')
sock = ftp.transfercmd('RETR 5MB.zip', rest=pos)
buf = sock.recv(1024 * 1024)
if not buf:
return
f.write(buf)
您可能不会一次获得 1MB,而是获得 8KB 以下的数据。假设您看到 1448,然后是 2896、4344 等。
- 如果您收到异常
REST
,则服务器不会处理恢复 - 放弃吧,您完蛋了。 - 如果文件超出实际文件大小,请按 ^C,然后在十六进制编辑器中检查。
- 如果您一遍又一遍地看到相同的 1448 字节或其他数字(您看到打印出来的数字),那么您就遇到了麻烦。
- 如果您拥有正确的数据,但每 1448 字节块之间有额外的字节,那么这实际上是可以修复的。如果您遇到这种情况并且无法弄清楚如何使用 修复它
f.seek
,我可以解释——但您可能不会遇到它。
测试ABRT
我们可以做的一件事是尝试中止下载并不是重新连接。
def downit():
with open('5MB.zip', 'wb') as f:
ftp = FTP(host='speedtest.tele2.net', user='anonymous', passwd='[email protected]')
while True:
pos = f.tell()
print(pos)
ftp.sendcmd('TYPE I')
sock = ftp.transfercmd('RETR 5MB.zip', rest=pos)
buf = sock.recv(1024 * 1024)
if not buf:
return
f.write(buf)
sock.close()
ftp.abort()
您可能想尝试多种变化:
- 不
sock.close
。 - 不
ftp.abort
。 - 与
sock.close
之后ftp.abort
。 - 与
ftp.abort
之后sock.close
。 - 以上所有四个都重复进行,
TYPE I
但每次都移至循环之前。
有些会引发异常。其他的似乎会永远挂起。如果所有 8 个都是这样,我们需要放弃中止。但如果其中任何一个有效,那就太好了!
下载完整块
另一种加快速度的方法是每次下载 1MB(或更多),然后再中止或重新连接。只需替换此代码:
buf = sock.recv(1024 * 1024)
if buf:
f.write(buf)
有了这个:
chunklen = 1024 * 1024
while chunklen:
print(' ', f.tell())
buf = sock.recv(chunklen)
if not buf:
break
f.write(buf)
chunklen -= len(buf)
现在,您每次传输读取的数据不再是 1442 或 8192 字节,而是高达 1MB。尝试将其推得更远。
与 Keepalive 结合
假设您的下载在 10MB 时失败,而问题中的 keepalive 代码将下载量提升至 512MB,但还不够 3GB — 您可以将两者结合起来。使用 keepalive 一次读取 512MB,然后中止或重新连接并读取下一个 512MB,直到完成。