FTP下载时连接正常获取不到数据

今天项目中要下载快钱的对账单,快钱对账单文件的FTP服务器是Unix系统,connectServer方法中已连接成功,reply code:220。

但是问题是download方法中的ftpClient.listFiles(remote)不能找到具体某一文件,如果使用ftpClient.listFiles()而不具体指定某一远程文件时可以列举出所有的文件,包括remote这个需要下载的文件。且代码中的ftpClient.retrieveFile(remote, out);会出现长时间的等待,upNewStatus为false,最终会抛出:FTP response 421 received.  Server closed connection.

 public static boolean connectServer(String host, int port, String user, String password, String defaultPath)
            throws SocketException, IOException {
        ftpClient = new FTPClient();
        // org.apache.commons.net.MalformedServerReplyException: Could not parse response code.
        // Server Reply: SSH-2.0-OpenSSH_7.2
        // ftpClient.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out)));
        // 设置以二进制方式传输
        ftpClient.setDataTimeout(5000);
        ftpClient.setConnectTimeout(connectTimeout);
        ftpClient.setControlEncoding("UTF-8");
        ftpClient.connect(host, port);
        log.info("Connected to " + host + ".");
        log.info("FTP server reply code:" + ftpClient.getReplyCode());
        if (FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {
            if (ftpClient.login(user, password)) {
                // Path is the sub-path of the FTP path
                if (defaultPath != null && defaultPath.length() != 0) {
                    ftpClient.changeWorkingDirectory(defaultPath);
                }
                return true;
            }
        }
        disconnect();
        return false;
    }

 public static File download(String remote, String local) throws IOException {
        log.info("remote={},local={}", remote, local);
        File downloadFile = null;
        // 检查远程文件是否存在
        FTPFile[] files = ftpClient.listFiles(remote);
        if (files.length == 0) {
            log.info("远程文件不存在: {}, {}", remote, files);
            return downloadFile;
        }

        File f = new File(local);
        OutputStream out = null;
        if (f.exists()) {
            f.delete();
        }
        try {
            //ftpClient.enterLocalPassiveMode();
            out = new FileOutputStream(f);
            boolean upNewStatus = ftpClient.retrieveFile(remote, out);
            out.flush();

            if (upNewStatus) {
                downloadFile = f;
            }
        }
        catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        finally {
            if (out != null) {
                out.close();
            }
        }
        return downloadFile;
    }

FTP需要在自己测试服务器开通21端口,这个已经叫运维开通了。且需要快钱把我们这边测试服务器ip加入快钱的白名单,否则是链接不到那边的FTP服务器的。

开始一直以为是快钱那边提供的用户是否需要什么文件连接权限,定位了很久的问题,最后搜索问题才发现对FTP连接模式和原理不清楚导致。

首先FTP分2中模式:主动模式(port)和被动模式(pasv).FTP标准命令TCP端口号为21,Port方式数据端口为20

不管哪种模式,都必须通过21这个端口建立起到FTP的管道连接,通过这个通道发送命令。

port模式:1.通过tcp的21端口建立起通道

     2.客户端在此通道发起PORT命令,并产生一个随机非特殊的端口号N(1023<N<65536)给到FTP服务器。

     3.此时客户端监听N+1端口(N+1>=1025,不一定是N端口+1),同时通过21的通道发送命令通知FTP服务器客户点通过此端口接受数据传输。

     4.FTP服务器接收到上一步的响应后通过自己的数据源端口20,去链接远程的客户端的N+1端口(此时是FTP服务端主动发起的一个端口链接

     5.如果此时客户端的防火墙策略是不能随意外部链接内部服务器的端口,则会造成上一步出现数据端链接失败!

      6.如果没有上一步的情况,FTP客户端则会接收到服务端响应并返回响应信息,则建立起了数据链接通道。

pasv模式:1.通过tcp的21端口建立起通道

        2.但与主动方式的FTP不同,客户端不会提交PORT命令并允许服务器来回连它的数据端口,而是提交 PASV命令.会产生两个随机非特殊的端口N(1023<N<65536)                   和N+1给到FTP服务器。其中N端口跟主动模式一样,会把N给到服务端的远程的21端口。相当于FTP服务端被动接受数据端口号而不是之前port模式的主动发起连接

      3.FTP服务端会则会打开N+1的端口号

      4.客户端发起N+1端口的链接,并建立数据链接。

                                       port模式:                          

   pasv模式: 

 上面的图都省略了建立tcp的21端口这个步骤。

正是因为之前采用主动模式,但是测试服务器防火墙阻止了快钱发起的数据端口的连接。也就是port模式的第5步出现问题。

因而FTPClient.listFiles(remote)或者FTPClient.retrieveFile(remote)方法时获取不了数据,就停止在那里,什么反应都没有,出现假死状态。

解决办法:在调用这两个方法之前,调用FTPClient.enterLocalPassiveMode();

这个方法的意思就是每次数据连接之前,ftp client告诉ftp server:数据连接的端口号已经告诉你了,你只需被动接受数据连接的请求就行

参考:http://www.cnblogs.com/xiaohh/p/4789813.html

    http://blog.csdn.net/u010154760/article/details/45458219

原文地址:https://www.cnblogs.com/hupu-jr/p/6669471.html