我正在尝试使用 FTPS 将文件上传到 FTP 站点,但是当我尝试存储该文件时,它在文件完全传输后挂起。
global f_blocksize
global total_size
global size_written
f_blocksize = 1024
total_size = os.path.getsize(file_path)
size_written = 0
file = open(file_path, "rb")
try:
ftps = FTP_TLS("ftp.example.com")
ftps.auth()
ftps.sendcmd("USER username")
ftps.sendcmd("PASS password")
ftps.prot_p()
print(ftps.getwelcome())
try:
print("File transfer started...")
ftps.storbinary("STOR myfile.txt", file, callback=handle, blocksize=f_blocksize)
print("File transfer complete!")
except OSError as ex:
print(ftps.getresp())
except Exception as ex:
print("FTP transfer failed.")
print("%s: %s" %(type(ex), str(ex)))
def handle(block):
global size_written
global total_size
global f_blocksize
size_written = size_written + f_blocksize if size_written + f_blocksize < total_size else total_size
percent_complete = size_written / total_size * 100
print("%s percent complete" %str(percent_complete))
我得到以下输出:
220 Microsoft FTP Service
File transfer started...
3.5648389904264577 percent complete
7.129677980852915 percent complete
10.694516971279374 percent complete
14.25935596170583 percent complete
17.824194952132288 percent complete
21.389033942558747 percent complete
24.953872932985206 percent complete
28.51871192341166 percent complete
32.083550913838124 percent complete
35.648389904264576 percent complete
39.213228894691035 percent complete
42.778067885117494 percent complete
46.342906875543946 percent complete
49.90774586597041 percent complete
53.472584856396864 percent complete
57.03742384682332 percent complete
60.60226283724979 percent complete
64.16710182767625 percent complete
67.7319408181027 percent complete
71.29677980852915 percent complete
74.8616187989556 percent complete
78.42645778938207 percent complete
81.99129677980854 percent complete
85.55613577023499 percent complete
89.12097476066144 percent complete
92.68581375108789 percent complete
96.25065274151436 percent complete
99.81549173194082 percent complete
100.0 percent complete
之后就没有进一步的进展,直到连接超时...
FTP transfer failed.
<class 'ftplib.error_temp'>: 425 Data channel timed out due to not meeting the minimum bandwidth requirement.
当程序运行时,如果我手动连接并查看,我可以在 FTP 站点中看到一个空的
myfile.txt
,但是当我取消它或连接超时时,这个空文件就会消失。
我是否遗漏了一些东西,需要在文件完全传输后调用来关闭文件?
这似乎是 Python 的
SSLSocket
类的问题,该类在运行时等待来自服务器的数据 unwrap
。由于它从未从服务器接收到此数据,因此无法从套接字解包 SSL,因此会超时。
该服务器特别是我通过欢迎消息识别为某些 Microsoft FTP 服务器,这与此博客
中所写的问题非常吻合“修复”(如果你可以这样称呼它)是通过编辑
SSLSocket
并修改 ftplib.py
方法来阻止 FTP_TLS.storbinary()
尝试完全解开连接。
def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None):
self.voidcmd('TYPE I')
with self.transfercmd(cmd, rest) as conn:
while 1:
buf = fp.read(blocksize)
if not buf: break
conn.sendall(buf)
if callback: callback(buf)
# shutdown ssl layer
if isinstance(conn, ssl.SSLSocket):
# HACK: Instead of attempting unwrap the connection, pass here
pass
return self.voidresp()
我在使用 python 的
ftplib.FTP_TLS
、prot_p
和 Microsoft FTP 服务器时在 STORBINARY 函数上遇到了这个问题。
示例:
ftps = FTP_TLS(host,username,password)
ftps.prot_p
STORBINARY...
错误表明解包函数超时。
与以下问题有关:
https://www.sami-lehtinen.net/blog/python-32-ms-ftps-ssl-tls-lockup-fix
https://bugs.python.org/issue10808
https://bugs.python.org/issue34557
分辨率:
打开 ftplib 的 python 页面:https://docs.python.org/3/library/ftplib.html
单击源代码,您将看到如下所示的内容:https://github.com/python/cpython/blob/3.10/Lib/ftplib.py
将此代码的副本创建到您的项目中(示例:
my_lib\my_ftplib.py
)
对于失败的方法,在您的情况 STORBINARY 中,错误看起来位于该方法中显示
conn.unwrap()
的行上。评论这一行。输入关键字 pass
否则空的 if
块将给出语法错误。
将上述库导入到您要实例化 FTP_TLS 的文件中。现在您将不再面临此错误。
推理: 函数
def ntransfercmd
(在 FTP_LTS
类下)中的代码将 conn
对象封装到 SSL 会话中。您评论的上述行负责拆除传输后的 SSL 会话。由于某种原因,当使用 Microsoft 的 FTP 服务器时,代码会在该行被阻止并导致超时。这可能是因为传输后服务器断开连接,也可能是服务器从其一侧解开 SSL。我不知道。评论该行是无害的,因为最终连接无论如何都会关闭 - 有关详细信息,请参阅下文:
在 ftplib 的 python 代码中,您会注意到 STORBINARY 函数中的
conn
对象包含在 with
块中,并且它是使用 socket.create_connection
创建的。这意味着当代码退出 .close()
块时,会自动调用 with
(您可以通过查看 python 套接字类源代码中的 __exit__
方法来确认这一点)。
有一个简单的解决方案:只需将参数
timeout=10
传递给 FTP
或 FTP_TLS
类即可。当 storbinary()
抛出错误时重试上传。
示例:
FtpInstance = FTP_TLS(Hostname, timeout=10)
FtpInstance.login(Username, Password)
FtpInstance.cwd(TargetFolder)
TryAgain = True
while TryAgain:
try:
with open(LocalFilePath, 'rb') as LocalFile:
FtpInstance.storbinary('STOR ' + RemoteFilePath, LocalFile)
TryAgain = False
except Exception as e:
print('Error when uploading: ' + str(e))
TryAgain = True
对我有用。
除了 @Martin Prikryl 提案之外,我们还可以动态重写标准
storbinary()
方法。
首先定义
new_storbinary()
方法:
try:
import ssl
except ImportError:
_SSLSocket = None
else:
_SSLSocket = ssl.SSLSocket
def new_storebinary(self, cmd, fp, blocksize=8192, callback=None, rest=None):
self.voidcmd('TYPE I')
with self.transfercmd(cmd, rest) as conn:
while 1:
buf = fp.read(blocksize)
if not buf: break
conn.sendall(buf)
if callback: callback(buf)
# shutdown ssl layer
if isinstance(conn, ssl.SSLSocket):
# HACK: Instead of attempting unwrap the connection, pass here
pass
return self.voidresp()
然后使用
types
模块替换实例级别的方法:
import types
ftps = ftplib.FTP_TLS()
ftps.storbinary = types.MethodType(new_storebinary, ftps)