通过FTP下载XML文件

4
我有一个储存在数据库中的源列表,用它来从FTP服务器下载XML文件并进行解析。脚本被打包成一个JAR文件,每天由Windows任务计划程序运行。偶尔会在获取某个XML文件时请求失败。到目前为止,在两周内发生了3次,并没有实际模式。
当出现问题时,我会去查看运行该任务电脑上的命令窗口是否已经打开,此时XML文件还未完全下载。如果我关闭命令窗口并手动运行任务,则一切正常运行。
我用于下载XML文件的代码如下:
private void loadFTPFile(String host, String username, String password, String filename, String localFilename){
        System.out.println(localFilename);
        FTPClient client = new FTPClient();
        FileOutputStream fos = null;

        try {
            client.connect(host);
            client.login(username, password);
            String localFilenameOutput = createFile(assetsPath + localFilename);
            fos = new FileOutputStream(localFilenameOutput);
            client.retrieveFile(filename, fos);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fos != null) 
                    fos.close();
                client.disconnect();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

这个函数在循环中被调用,当它失败时,一切都停止了,脚本不会继续执行下一个提要。

我不确定发生了什么,可能是连接丢失,但我认为try/catch应该可以捕获这种情况。我不知道是否需要使用超时或线程来解决问题(但我从未使用过线程)。

有人能告诉我为什么会发生这种情况以及我可以做什么来解决这个问题吗?


当程序卡住时,最后一个文件已经成功下载了吗? - Raffaele
不,这只是文件的一部分。 - locrizak
所以你可以尝试在数据连接上设置超时时间,以防服务器故障 - 请参见我的更新答案。 - Raffaele
3个回答

2

更新 - 为数据连接设置超时时间

由于最后一个文件只被部分下载,并且考虑到FTPClient.retrieveFile()的源代码,我认为这可能是服务器端的问题(可能会导致它挂起,甚至死机 - 谁知道呢)。显然,人们无法修复服务器,甚至无法知道那里发生了什么,无论如何,我建议添加一个超时时间,使用setDataTimeout(int)并单独捕获可能的SocketTimeoutException,以在不同的位置记录日志,并可能将其发送给FTP服务器管理员(连同发生时间信息),以便他们可以合并日志并查看问题所在。

旧回答

我没有注意到你为每个文件连接并登录,因此以下内容仅是优化,不关闭控制连接并成功注销,但它不应解决问题。

您可以以调试模式启动JVM,并在挂起时附加调试器,但根据此答案此线程,它可能是网络设备(路由器)的超时问题。从FTPClient Javadoc中得知:

在文件传输期间,数据连接正在忙碌,但控制连接处于空闲状态。FTP服务器知道控制连接正在使用,因此不会因为缺乏活动而关闭它,但是对于网络路由器来说,很难知道控制和数据连接是否与彼此关联。一些路由器可能将控制连接视为空闲状态,并在数据连接的传输时间超过路由器允许的空闲时间时将其断开连接。

解决此问题的一种方法是通过控制连接发送安全命令(即NOOP)以重置路由器的空闲计时器。如下所示启用此选项:

ftpClient.setControlKeepAliveTimeout(300); // set timeout to 5 minutes

1
文件大小的问题说得很好,但如果你在防火墙或主机环境中,连接设置也非常关键,因为它们会在相当短的时间内终止任何不活动的连接。 - Mike
@Mike 不错的补充 :) 无论如何,在这种环境下,我认为程序能做的就是不断发送NOOP。或者有一些特殊的连接配置吗? - Raffaele
1
网络公共资源并不总是会抛出异常。在您的情况下,使用setControlKeepAliveTimeout(300)可以防止它消失。然而,这里应该不是问题,因为他每次循环都建立了一个新连接(该方法是自包含的)。他也没有切换到被动模式enterLocalPassiveMode(),所以他正在通过控制连接发送文件。我认为它运行得太快了,服务器可能出现问题并拒绝连接。代码中有几个关键位置他没有检查返回值。 - Mike
“他正在通过控制连接发送文件”是什么意思?我对FTP很陌生,但我认为文件传输总是在专用连接中进行的。此外,如果服务器拒绝连接,他会得到一个异常,但实际上程序却一直挂起。你认为程序在哪里无限等待?[retrieveFile()的来源] (http://svn.apache.org/viewvc/commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/FTPClient.java?view=markup#l1759) - Raffaele
你说得对,我疯了一会儿……或者两个……我不确定他卡在哪里,但在繁忙的服务器情况下,由于不是套接字错误,所以您会从服务器那里收到负面回复,不会出现异常。 - Mike
在我看来,它可能会挂起的唯一一行是 Util.copyStream()。我会在套接字上设置超时来捕获这个位置。 - Raffaele

1

你会检查任何调用的返回状态吗?还是代码自己处理?

有一个必须在某些情况下使用的调用completePendingCommand()。这可能是值得研究的内容。

此外,你不会看到IO异常,我认为它会被重新打包为CopyStreamException。

你可能还想将返回值更改为布尔值,因为你捕获了异常,至少调用循环将知道传输是否发生。

private boolean loadFTPFile(String host, String username, String password, String filename, String localFilename){
    System.out.println(localFilename);
    FTPClient client = new FTPClient();
    FileOutputStream fos = null;

    try {
        client.connect(host);

        int reply = client.getReplyCode();

        if (!FTPReply.isPositiveCompletion(reply)){
            client.disconnect();
            System.err.println("FTP server refused connection.");
            return false;
        }


        if (!client.login(username, password)){
            client.logout();
            return false;
        }

        String localFilenameOutput = createFile(assetsPath + localFilename);
        fos = new FileOutputStream(localFilenameOutput);
        boolean result = client.retrieveFile(filename, fos);

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

        if (result){
            System.out.println("\tFile Transfer Completed Successfully at: " + sdf.format(Calendar.getInstance().getTime()));

            // ftp.completePendingCommand();
        }
        else {
            System.out.println("\tFile Transfer Failed at: " + sdf.format(Calendar.getInstance().getTime()));
        }

    return result;
    }catch (CopyStreamException cse){
        System.err.println("\n\tFile Transfer Failed at: " + sdf.format(Calendar.getInstance().getTime()));
        System.err.println("Error Occurred Retrieving File from Remote System, aborting...\n");
        cse.printStackTrace(System.err);
        System.err.println("\n\nIOException Stack Trace that Caused the Error:\n");
        cse.getIOException().printStackTrace(System.err);
        return false;
    }catch (Exception e){
        System.err.println("\tFile Transfer Failed at: " + sdf.format(Calendar.getInstance().getTime()));
        System.out.println("Error Occurred Retrieving File from Remote System, aborting...");
        e.printStackTrace(System.err);
        return false;
    } finally {
        try {
            if (fos != null) 
                fos.close();
            client.disconnect();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

0

这不是一个线程问题。很可能是由于循环内部的某些原因导致的,因为那段代码看起来应该可以很好地清理。话虽如此,在测试时,您可能需要添加

catch (Exception e) {
    e.printStackTrace();
}

在捕获IOException的catch子句之后。可能会抛出另一个异常。

另外,如果您一次从数据库结果集中获取一个结果并执行FTP获取操作,那可能会有问题。除非所有结果都是通过JDBC调用一次性返回的,否则也可能超时。并非所有数据库查询实际上都会一次性将整个结果集返回给客户端。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接