调用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会话处理不兼容:
- 自定义
ntransfercmd方法复用控制连接会话时,未适配storbinary的完整流程,导致数据传输完成前SSL连接被异常终止 - 强制指定
PROTOCOL_TLSv1_1(已被淘汰的协议版本),部分服务器对该版本的数据连接会话复用支持存在缺陷 - 数据连接关闭时未正确处理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
相关产品推荐
相关产品推荐

