Apache FTPClient接近下载完成时失败,FileZilla可正常下载该文件
我之前碰到过几乎一模一样的场景,结合你描述的proftpd服务器环境、FileZilla正常但FTPClient超时的现象,大概率是几个容易忽略的配置或代码细节没处理到位,下面给你几个针对性的解决方案:
1. 务必设置二进制传输模式
这是最容易踩的坑!如果你的文件是二进制类型(比如压缩包、图片、视频等),没设置二进制模式的话,FTPClient会默认用文本模式传输,自动转换换行符,导致文件传输异常,甚至在接近完成时卡住触发超时。
在登录后添加这行代码:
client.setFileType(FTP.BINARY_FILE_TYPE);
2. 调用completePendingCommand()完成传输收尾
当你用retrieveFileStream()获取输入流下载时,读取完流后必须调用completePendingCommand()——这一步是用来确认服务器的传输完成响应的。如果跳过它,FTPClient会一直等待服务器返回结果,而proftpd可能已经关闭了数据连接,最终就会触发超时。
修改你的代码逻辑:
InputStream in = new BufferedInputStream(client.retrieveFileStream(ftpFilePath), 16384); try { byte[] buffer = new byte[8192]; int rd; while ((rd = in.read(buffer)) > 0) { // 读取文件并更新下载进度 } } finally { in.close(); // 关键:完成命令收尾,确认服务器响应 if (!client.completePendingCommand()) { System.out.println("文件传输未正常完成,服务器可能返回了错误"); } }
3. 优化控制连接保活与超时设置
虽然你已经设置了controlKeepAliveTimeout,但可以补充controlKeepAliveReplyTimeout,同时在下载循环中主动发送NOOP命令,防止控制连接因长时间无交互被断开。另外,数据超时时间可以适当调大,应对最后阶段的小延迟:
// 控制连接每30秒发送一次保活命令 client.setControlKeepAliveTimeout(30); // 等待保活命令响应的超时时间(10秒) client.setControlKeepAliveReplyTimeout(10000); // 调大数据超时时间到60秒(可根据你的网络情况调整) client.setDataTimeout(60000); // 在下载循环中,每读取一定次数就发送NOOP命令维护连接 int count = 0; while ((rd = in.read(buffer)) > 0) { // 处理数据、更新进度... count++; // 每读取100次(可自行调整频率)发送一次NOOP if (count % 100 == 0) { try { client.sendNoOp(); } catch (IOException e) { // 处理保活命令异常,比如尝试重新连接等 } count = 0; } }
4. 尝试对齐缓冲区大小
你已经设置了setBufferSize(65536),可以试试让BufferedInputStream和读取缓冲区的大小与FTPClient的bufferSize保持一致,减少IO层面的额外开销:
InputStream in = new BufferedInputStream(client.retrieveFileStream(ftpFilePath), 65536); byte[] buffer = new byte[65536]; // 用和FTPClient相同的缓冲区大小
为什么FileZilla能正常下载?
FileZilla默认处理了这些细节:自动检测文件类型并设置二进制模式、使用更合理的超时阈值、主动维护控制连接保活、正确处理传输收尾命令,所以不会触发超时。
你可以先从设置二进制模式和调用completePendingCommand()这两点入手,这两个是此类问题最常见的根源。
内容的提问来源于stack exchange,提问作者Dmitry Ginzburg




