You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python3.6下FTP_TLS会话复用问题:文件上传失败求助

解决FTPS会话复用导致的450错误及SSLSession获取问题

问题分析

你遇到的两个问题本质都是TLS会话复用未正确实现:

  1. 初始脚本没有复用会话,服务器返回450 TLS session of data connection has not resumed or the session does not match the control connection,导致上传文件为空。
  2. 自定义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参数,比如:
    context = ssl.create_default_context()
    context.check_hostname = False
    context.verify_mode = ssl.CERT_NONE
    ftp = MyFTP_TLS(context=context)
    
    但生产环境不建议关闭证书验证,应该正确配置信任的CA证书。

为什么你的原自定义类出错?

你原代码里重复了session = self.sock.session的赋值,而且没有确保在prot_p()之后调用ntransfercmd,另外,某些情况下self.sock可能还未被包装成SSLSocket(比如调用connect后还没调用prot_p()),所以直接访问session会报错。上面的修正版本通过类型检查避免了这个问题,并且使用super()正确调用父类方法,逻辑更严谨。

内容的提问来源于stack exchange,提问作者Ministry

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:19:31