FTP上传大文件报[WinError 10054]远程主机强制关闭连接 小文件正常
FTP大文件上传触发10054连接强制关闭故障排查
故障现象
- 小体积文件可正常上传,服务端返回
226 文件存储成功响应,全流程走完后可正常收到221退出响应 - 上传约200MB、耗时15分钟左右的大文件时,即便观测到文件已上传完成,Python脚本的try/except逻辑会触发报错中断流程,报错信息如下:
[WinError 10054] An existing connection was forcibly closed by the remote host
- 同个大文件用FileZilla上传也会失败:传输约90MB、耗时303秒时触发
ECONNABORTED连接中断,自动重试后仍反复提示被动响应地址不可路由,无法完成上传。
现有实现与已尝试方案
此前参考相关方案自定义了SmartFTP类,修复FTP被动模式返回无效主机地址的问题,才实现小文件基础上传功能,当前使用的上传脚本如下:
class SmartFTP(FTP): def makepasv(self): invalidhost, port = super(SmartFTP, self).makepasv() return self.host, port try: for file_name in os.listdir(fullPath): if file_name == 'file.csv': file_path = fullPath + "\\" + file_name print('Moving File - ' + file_path + ': ' + str(datetime.now())) with SmartFTP(FTP_HOST, FTP_USER, FTP_PASS, timeout=2700) as ftp, open(file_path, 'rb') as file: ftp.set_debuglevel(2) print('here1') test = ftp.pwd() print('here2') ftp.storbinary(f'STOR {file_name}', file) print('here3') print('FTP Move Completed') except Exception as e: print("FTP and File Move process failed: " + str(e))
已尝试的排查操作均未解决问题:
- 调整连接超时时间到2700秒
- 切换FTP主动/被动模式(切主动模式后进程无限阻塞无响应)
- 修复被动模式返回的无效主机地址问题
日志对比
大文件上传异常日志
传输持续约15分钟后,未收到226传输完成响应,直接触发连接关闭报错:
Moving File - file.csv: 2022-07-12 15:27:35.932522 here1 *cmd* 'PWD' *put* 'PWD\r\n' *get* '257 "/folder"\n' *resp* '257 "/folder"' here2 *cmd* 'TYPE I' *put* 'TYPE I\r\n' *get* '200 Transfer Mode: BINARY\n' *resp* '200 Transfer Mode: BINARY' *cmd* 'PASV' *put* 'PASV\r\n' *get* '227 Entering PASV Mode (numbers)\n' *resp* '227 Entering PASV Mode (numbers)' *cmd* 'STOR file.csv' *put* 'STOR file.csv\r\n' *get* '150 Connecting Data Port...\n' *resp* '150 Connecting Data Port...' Then it loads for about 15 minutes *cmd* 'QUIT' *put* 'QUIT\r\n' FTP and File Move process failed: [WinError 10054] An existing connection was forcibly closed by the remote host
小文件上传正常日志
可正常收到226传输完成响应、221退出响应,全流程无异常:
Moving File - file.csv: 2022-07-12 15:50:39.489980 here1 *cmd* 'PWD' *put* 'PWD\r\n' *get* '257 "/Atlas_In"\n' *resp* '257 "/Atlas_In"' here2 *cmd* 'TYPE I' *put* 'TYPE I\r\n' *get* '200 Transfer Mode: BINARY\n' *resp* '200 Transfer Mode: BINARY' *cmd* 'PASV' *put* 'PASV\r\n' *get* '227 Entering PASV Mode (numbers)\n' *resp* '227 Entering PASV Mode (numbers)' *cmd* 'STOR file.csv' *put* 'STOR file.csv\r\n' *get* '150 Connecting Data Port...\n' *resp* '150 Connecting Data Port...' *get* '226 Stored Message: [file.csv] Byte Count: [57824] bytes\n' *resp* '226 Stored Message: [file.csv] Byte Count: [57824] bytes' here3 FTP Move Completed *cmd* 'QUIT' *put* 'QUIT\r\n' *get* '221 GoodBye\n' *resp* '221 GoodBye'
FileZilla大文件上传日志
反复提示被动响应地址不可路由,传输到90MB左右连接中断,重试后仍失败:
Status: Resolving address of web.com Status: Connecting to IP:21... Status: Connection established, waiting for welcome message... Status: Insecure server, it does not support FTP over TLS. Status: Logged in Status: Retrieving directory listing... Status: Directory listing of "/directory" successful Status: Resolving address of web.com Status: Connecting to IP:21... Status: Connection established, waiting for welcome message... Status: Insecure server, it does not support FTP over TLS. Status: Logged in Status: Starting upload of file.csv Status: Server sent passive reply with unroutable address. Using server address instead. Status: Disconnected from server: ECONNABORTED - Connection aborted Command: STOR file.csv Response: 150 Connecting Data Port... Error: Disconnected from server: ECONNABORTED - Connection aborted Error: File transfer failed after transferring 90,439,680 bytes in 303 seconds Fails here then retries (filezilla settings are to try twice I believe) Status: Resolving address of web.com Status: Connecting to IP:21... Status: Connection established, waiting for welcome message... Status: Insecure server, it does not support FTP over TLS. Status: Logged in Status: Starting upload of file.csv Status: Retrieving directory listing of "/directory"... Status: Server sent passive reply with unroutable address. Using server address instead. Status: Server sent passive reply with unroutable address. Using server address instead.
已知背景信息
- 服务端配置了自动任务,会将上传目标目录内的文件移动到下载目录,暂不确定该机制是否为故障诱因
- 切换FTP主动模式后进程无限阻塞,无法完成传输
根因定位优先级
- FTP控制连接空闲超时断连:FTP传输数据时走单独的数据连接,控制连接全程处于空闲状态,绝大多数FTP服务器、中间防火墙/NAT设备会对空闲超过阈值的控制连接主动断开。你设置的2700秒(45分钟)超时是客户端侧配置,服务端/网络设备的空闲超时大概率小于15分钟,导致数据还在传输,控制连接已经被强制断开,等数据传完要接收226响应时就会触发10054错误。
- 服务端自动移文件任务冲突:如果自动任务的扫描间隔小于大文件上传时长,会在文件还没传完、仍处于写入状态时就尝试移动文件,直接打断传输连接。
- 被动模式端口/会话限制:服务端被动模式端口开放范围不足,或者防火墙对长连接的数据传输端口有会话超时限制,长时传输的连接被中间设备拦截。
可落地修复方案
- 开启控制连接心跳保活
传输大文件时定期在控制连接发送NOOP命令,避免控制连接因为空闲被断开。修改SmartFTP类增加保活逻辑,在数据上传过程中定时发送保活指令,示例修改:
同时在客户端层面开启TCP keepalive,连接建立后设置socket选项,避免TCP连接被中间设备回收:import socket class SmartFTP(FTP): def makepasv(self): invalidhost, port = super(SmartFTP, self).makepasv() return self.host, port def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None): # 每上传32KB数据发送一次NOOP保活 bytes_sent = 0 def keepalive_callback(b): nonlocal bytes_sent bytes_sent += len(b) if callback: callback(b) if bytes_sent >= 32768: self.voidcmd('NOOP') bytes_sent = 0 return super().storbinary(cmd, fp, blocksize, keepalive_callback, rest)with SmartFTP(FTP_HOST, FTP_USER, FTP_PASS, timeout=2700) as ftp: # 开启TCP保活,空闲60秒开始发探测包,每10秒发一次,探测3次失败判定连接断开 ftp.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) # Windows系统专属TCP保活参数设置 ftp.sock.ioctl(socket.SIO_KEEPALIVE_VALS, (1, 60000, 10000)) # 后续上传逻辑不变 - 排查服务端自动任务逻辑
联系服务端管理员确认自动移文件任务的触发规则:- 确认扫描间隔是否小于大文件上传时长
- 确认任务是否会识别正在写入的未完成文件,要求调整规则为仅移动上传完成、不再被占用的文件,避免传输过程中文件被移动打断连接。
- 调整传输配置规避网络拦截
- 调大
storbinary的blocksize参数到64KB甚至更大,减少传输过程中的包数量,降低被中间设备拦截的概率 - 排查本地防火墙是否拦截主动模式的入站连接:主动模式下服务端会主动发起数据连接到客户端的随机端口,需要在本地防火墙放开FTP程序的入站权限,避免连接被拦截导致阻塞。
- 联系服务端管理员确认FTP服务的控制连接空闲超时、被动模式端口范围、防火墙会话超时配置,确保超时阈值大于大文件最长传输时长。
- 调大
- 增加断点续传与上传校验逻辑
大文件上传不要依赖单次长连接成功,增加失败重试+断点续传逻辑,上传完成后通过SIZE命令校验服务端文件大小和本地文件一致,确认上传成功,避免连接提前断开导致的流程异常。
内容的提问来源于stack exchange,提问作者Elliot Gitter
相关产品推荐
相关产品推荐

