Python3.6下FTP_TLS会话复用问题:文件上传失败求助
解决FTPS会话复用导致的450错误及SSLSession获取问题
问题分析
你遇到的两个问题本质都是TLS会话复用未正确实现:
- 初始脚本没有复用会话,服务器返回
450 TLS session of data connection has not resumed or the session does not match the control connection,导致上传文件为空。 - 自定义
MyFTP_TLS类时,错误地尝试从self.sock直接获取session属性,但在某些阶段self.sock可能还不是SSLSocket,或者获取方式不对,引发'SSLSocket' object has no attribute 'session'。
正确的会话复用实现方案
Python 3.6+确实支持TLS会话复用,关键是要在建立数据连接时,复用控制连接的TLS会话。修正你的自定义类如下:
import ftplib import ssl class MyFTP_TLS(ftplib.FTP_TLS): """支持会话复用的显式FTPS客户端""" def ntransfercmd(self, cmd, rest=None): # 先调用父类的ntransfercmd获取基础连接 conn, size = super().ntransfercmd(cmd, rest) # 只有在启用了保护数据连接的情况下才处理会话复用 if self._prot_p: # 确保控制连接已经是SSL套接字,并且获取会话 if isinstance(self.sock, ssl.SSLSocket): session = self.sock.session # 用控制连接的会话包装数据连接 conn = self.context.wrap_socket( conn, server_hostname=self.host, session=session ) return conn, size
完整的可用脚本
把这个类整合到你的上传脚本中,注意还要确保正确调用prot_p()启用数据连接加密,完整代码示例:
import ftplib import ssl class MyFTP_TLS(ftplib.FTP_TLS): """支持会话复用的显式FTPS客户端""" def ntransfercmd(self, cmd, rest=None): conn, size = super().ntransfercmd(cmd, rest) if self._prot_p: if isinstance(self.sock, ssl.SSLSocket): session = self.sock.session conn = self.context.wrap_socket( conn, server_hostname=self.host, session=session ) return conn, size # 配置参数 USER = "username" PASS = "password" SERVER = "1.2.3.4" PORT = 21 filepath = '/path/to/myfile.txt' filename = 'myfile.txt' # 初始化FTP客户端 ftp = MyFTP_TLS() ftp.set_debuglevel(2) ftp.connect(SERVER, PORT) ftp.login(USER, PASS) # 启用数据连接的TLS保护(必须调用,否则不会触发会话复用逻辑) ftp.prot_p() ftp.set_pasv(True) ftp.cwd("testfolder") # 上传文件 with open(filepath, 'rb') as content: ftp.storbinary(f'STOR {filename}', content) ftp.quit()
关键注意点
- 必须调用
prot_p():这个方法会启用数据连接的TLS加密,只有开启后,自定义的ntransfercmd里的会话复用逻辑才会生效。 - 会话获取时机:
self.sock.session只有在控制连接已经完成TLS握手后才存在,也就是login()之后、prot_p()之后,所以在ntransfercmd里判断isinstance(self.sock, ssl.SSLSocket)是必要的。 - 上下文复用:如果需要自定义SSL上下文(比如忽略证书验证,仅测试用),可以在初始化
MyFTP_TLS时传入context参数,比如:
但生产环境不建议关闭证书验证,应该正确配置信任的CA证书。context = ssl.create_default_context() context.check_hostname = False context.verify_mode = ssl.CERT_NONE ftp = MyFTP_TLS(context=context)
为什么你的原自定义类出错?
你原代码里重复了session = self.sock.session的赋值,而且没有确保在prot_p()之后调用ntransfercmd,另外,某些情况下self.sock可能还未被包装成SSLSocket(比如调用connect后还没调用prot_p()),所以直接访问session会报错。上面的修正版本通过类型检查避免了这个问题,并且使用super()正确调用父类方法,逻辑更严谨。
内容的提问来源于stack exchange,提问作者Ministry
相关产品推荐
相关产品推荐

