【问题标题】:Apache FTPClient fails to retrieve a file when it's almost downloaded几乎下载文件时,Apache FTPClient 无法检索文件
【发布时间】:2018-01-04 11:07:54
【问题描述】:

Apache 的FTPClient 无法下载 FileZilla 完美下载的文件。

基本上,我在成功登录并列出后要做的是下载一个特定文件:

FTPClient client = new FTPClient();
client.setDataTimeout(20000);
client.setConnectTimeout(20000);
client.setBufferSize(65536);
//...
client.connect(host);
client.login(user, pswd);
// response validation
client.enterLocalPassiveMode();
// some listings with validations

InputStream in = new BufferedInputStream(client.retrieveFileStream(ftpFilePath), 16384);
// ...
byte[] buffer = new byte[8192];
while ((rd = in.read(buffer)) > 0) {
// .. reading the file and updating download progress

最后几行可以很容易地替换为FTPClient的文件下载,结果几乎相同,但是我们无法跟踪下载进度:

client.setControlKeepAliveTimeout(30);
client.retrieveFile(ftpFilePath, new org.apache.commons.io.output.NullOutputStream());

作为所有这些操作的结果,我可以看到文件正在下载,直到非常接近 100%,然后出现异常:

java.net.SocketTimeoutException: Read timed out
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(Unknown Source)
        at java.net.SocketInputStream.read(Unknown Source)
        at java.net.SocketInputStream.read(Unknown Source)
        at java.io.FilterInputStream.read(Unknown Source)
        at java.io.BufferedInputStream.fill(Unknown Source)
        at java.io.BufferedInputStream.read1(Unknown Source)
        at java.io.BufferedInputStream.read(Unknown Source)
        at java.io.FilterInputStream.read(Unknown Source)
        at <my code from here on>

似乎没有防火墙,但是当互联网连接速度更好时,下载成功(可能是某种超时)。我认为问题出在连接上,但问题是 FileZilla 成功下载了相同的文件。

所以,我可以这样重新表述我的问题:下载文件时如何让我的FTPClient 表现得像 FileZilla。可能有一些我不知道的复杂的 ping 逻辑重试。

公共网络:commons-net-3.6

FTP 服务器:CentOS 5.8 上的 proftpd-1.3.3g-6.el5,默认配置,不支持 FTP over TLS。

【问题讨论】:

    标签: java ftp apache-commons-net


    【解决方案1】:

    我不知道这种现象的实际原因是什么(文件的最后一个块超时),但我已经用 Wireshark 检查了 FileZilla 下载文件的做法并发现它受到了影响从具有相同超时的相同问题,它正在重新连接到服务器并发送REST FTP 查询以从该特定文件中止时重新开始下载,即仅下载最后一个块。

    因此,解决方案是在下载过程中添加某种重试逻辑,以便这段代码:

    InputStream in = new BufferedInputStream(client.retrieveFileStream(ftpFilePath), 16384);
    // ...
    byte[] buffer = new byte[8192];
    while ((rd = in.read(buffer)) > 0) {
    

    变成这样:

    InputStream in = new BufferedInputStream(client.retrieveFileStream(ftpFilePath), 16384);
    // ...
    byte[] buffer = new byte[8192];
    long totalRead = 0;
    for (int resumeTry = 0; resumeTry <= RESUME_TRIES; ++resumeTry) {
        try {
            while ((rd = in.read(buffer)) > 0) {
                //...
                totalRead += rd;
            }
            break;
        } catch (SocketTimeoutException ex) {
            // omitting exception handling
            in.close();
            client.abort();
            client.connect(...);
            client.login(...);
            client.setFileType(FTPClient.BINARY_FILE_TYPE);
            client.enterLocalPassiveMode();
            client.setRestartOffset(totalRead);
            in = client.retrieveFileStream(...);
            if (in == null) {
                // the FTP server doesn't support REST FTP query
                throw ex;
            }
            in = new BufferedInputStream(in, 16384);
        }
    }
    

    【讨论】:

      【解决方案2】:

      似乎是由以下行定义的数据超时引起的:

      client.setDataTimeout(20000);
      

      根据JavaDoc:

      设置从数据连接读取时使用的超时时间(以毫秒为单位)。此超时将在打开数据连接后立即设置,前提是该值≥0。

      注意:在建立活动的本地数据连接时调用 accept() 时也会应用超时。

      参数: timeout - 打开数据连接套接字时使用的默认超时时间(以毫秒为单位)。值 0 表示无限超时。

      您可以尝试将此值设置为 0(在这种情况下意味着无限)吗?

      【讨论】:

      • 感谢您的回复,我会尽力让您知道
      • 它没有帮助,也不应该有,因为它是单个 read 操作的超时,必须在 20 秒内成功
      • 啊,对不起,我的检查错了,我在错误的地方删除了它,需要再检查一次。
      • 是的,现在它永远挂在 100%
      猜你喜欢
      • 2013-08-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-25
      • 2014-11-21
      • 2015-01-16
      • 2017-11-23
      相关资源
      最近更新 更多