You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Apache FTPClient接近下载完成时失败,FileZilla可正常下载该文件

解决FTPClient下载接近完成时抛出SocketTimeoutException的问题

我之前碰到过几乎一模一样的场景,结合你描述的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

火山引擎 最新活动