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

调用ftp.storbinary上传文件时出现ssl.SSLEOFError协议违规EOF错误

FTPS上传空文件并报SSL EOF错误的解决

问题现象

  • 使用storbinary上传文件时,服务器仅生成空文件,无内容写入
  • nlst、retrbinary等方法可正常执行,无异常
  • 报错信息:ssl.SSLEOFError: EOF occurred in violation of protocol (_ssl.c:2672),触发于ftplib执行unwrap阶段

复现代码

import ftplib
import ssl
import io

class MyFTP_TLS(ftplib.FTP_TLS):
    """Explicit FTPS, with shared TLS session"""
    def ntransfercmd(self, cmd, rest=None):
        conn, size = ftplib.FTP.ntransfercmd(self, cmd, rest)
        if self._prot_p:
            conn = self.context.wrap_socket(conn,
                                            server_hostname=self.host,
                                            session=self.sock.session)  # this is the fix
        return conn, size

# METHOD TO ESTABLISH CONNECTION
def connect():
    ftp = MyFTP_TLS()
    ftp.set_pasv(True)
    ftp.ssl_version = ssl.PROTOCOL_TLSv1_1
    ftp.debugging = 2

    ftp.connect(pac_hostname, pacific_ftp_port, timeout=100)
    ftp.auth()
    ftp.login(pac_username, pac_password)
    ftp.prot_p()
    return ftp
 
# ESTABLISH FTP CONNECTION
ftp = connect()

bytes_buffer = io.BytesIO()
bytes_buffer.write(b"Testing")
bytes_buffer.seek(0)
res = ftp.storbinary("STOR UNSYNCED_FILE_2.txt", bytes_buffer)

问题根源

这个错误核心是数据连接的SSL/TLS会话处理不兼容:

  1. 自定义ntransfercmd方法复用控制连接会话时,未适配storbinary的完整流程,导致数据传输完成前SSL连接被异常终止
  2. 强制指定PROTOCOL_TLSv1_1(已被淘汰的协议版本),部分服务器对该版本的数据连接会话复用支持存在缺陷
  3. 数据连接关闭时未正确处理SSL层的双向关闭,触发服务器端EOF异常

解决方法

1. 修正storbinary的SSL连接处理逻辑

重写storbinary方法,确保数据传输完成后正确关闭SSL连接的写方向,避免提前终止:

import socket

class MyFTP_TLS(ftplib.FTP_TLS):
    """Explicit FTPS, with shared TLS session"""
    def ntransfercmd(self, cmd, rest=None):
        conn, size = super().ntransfercmd(cmd, rest)
        if self._prot_p:
            conn = self.context.wrap_socket(
                conn,
                server_hostname=self.host,
                session=self.sock.session
            )
        return conn, size

    def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None):
        conn, size = self.ntransfercmd(cmd, rest)
        try:
            while True:
                buf = fp.read(blocksize)
                if not buf:
                    break
                conn.sendall(buf)
                if callback:
                    callback(buf)
            # 发送完数据后,关闭SSL写方向,告知服务器传输结束
            if self._prot_p:
                conn.shutdown(socket.SHUT_WR)
            # 等待服务器响应
            return self.getresp()
        finally:
            conn.close()

2. 优化TLS上下文配置

放弃强制指定旧版TLS协议,使用更兼容的默认上下文,若服务器必须用TLSv1.1再做限制:

def connect():
    ftp = MyFTP_TLS()
    ftp.set_pasv(True)
    # 使用系统默认的安全TLS上下文
    ftp.context = ssl.create_default_context()
    # 仅当服务器不支持更高版本时,启用TLSv1.1限制
    # ftp.context.options |= ssl.OP_NO_TLSv1_2 | ssl.OP_NO_TLSv1_3
    ftp.debugging = 2

    ftp.connect(pac_hostname, pacific_ftp_port, timeout=100)
    ftp.auth()
    ftp.login(pac_username, pac_password)
    ftp.prot_p()
    return ftp

3. 排查服务器端配置

  • 确认服务器是否允许数据连接复用控制连接的TLS会话
  • 检查被动模式(PASV)的端口范围是否在防火墙中开放
  • 查看服务器FTP日志,获取数据连接SSL握手或传输失败的详细信息

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 03:10:33