Java FTPSClient retrievefile()挂起

Java FTPSClient retrievefile()挂起,java,apache,ftp,ftp-client,ftps,Java,Apache,Ftp,Ftp Client,Ftps,我正在创建一个ApacheFTPS客户端(因为远程服务器不允许普通FTP)。我可以毫无问题地连接和删除文件,但在使用retrieveFile()或retrieveFileStream()时,它会挂起 出于某些原因,非常小的文件会传输(最多5792字节),但其他任何文件都会提供以下PrintCommandListener输出: 运行: 220------欢迎来到纯FTPd[privsep][TLS]------ 220您是50个用户中的第2个。 当地时间现在是19点42分。服务器端口:21。 22

我正在创建一个ApacheFTPS客户端(因为远程服务器不允许普通FTP)。我可以毫无问题地连接和删除文件,但在使用retrieveFile()或retrieveFileStream()时,它会挂起

出于某些原因,非常小的文件会传输(最多5792字节),但其他任何文件都会提供以下PrintCommandListener输出:

运行:
220------欢迎来到纯FTPd[privsep][TLS]------
220您是50个用户中的第2个。
当地时间现在是19点42分。服务器端口:21。
220这是一个专用系统-无匿名登录
此服务器上也欢迎220-IPv6连接。
220您将在15分钟不活动后断开连接。
认证TLS
234认证TLS正常。
用户
331用户确认。需要密码
通过
230好的。当前受限目录为/
A型
200类型现在是ASCII
EPSV
229扩展被动模式正常(| | | | 53360 |)
RETR test.txt
150接受的数据连接
150 7.3千字节可供下载

代码如下:

try {

    FTPSClient ftpClient = new FTPSClient("tls",false);

    ftpClient.addProtocolCommandListener(new PrintCommandListener(new  PrintWriter(System.out)));

    ftpClient.connect(host, port);

    int reply = ftpClient.getReplyCode();

    if (FTPReply.isPositiveCompletion(reply)) {
        ftpClient.enterLocalPassiveMode();
        ftpClient.login(username, password);
        ftpClient.enterLocalPassiveMode();
        FileOutputStream outputStream = new FileOutputStream(tempfile);
        ftpClient.setFileType(FTPClient.ASCII_FILE_TYPE);
        ftpClient.retrieveFile("test.txt", outputStream);
        outputStream.close();
        ftpClient.logout();
        ftpClient.disconnect();
    }
} catch (IOException ioe) {
    System.out.println("FTP client received network error");
}

非常感谢您的任何想法。

通常,FTPS连接的FTP命令顺序是(per)
AUTH TLS
PBSZ 0
,然后是
USER
PASS
,等等。因此,您可以尝试:

FTPSClient ftpClient = new FTPSClient("tls",false);
ftpClient.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out)));
ftpClient.connect(host, port);
int reply = ftpClient.getReplyCode();
if (FTPReply.isPositiveCompletion(reply)) {
    ftpClient.execPBSZ(0);
    reply = ftpClient.getReplyCode();
    // Check for PBSZ error responses...
    ftpClient.execPROT("P");
    reply = ftpClient.getReplyCode();
    // Check for PROT error responses...

    ftpClient.enterLocalPassiveMode();
这明确地告诉服务器不要缓冲数据连接(
PBSZ 0
),并使用TLS保护数据传输(
PROT p

您能够传输一些字节这一事实表明问题是而不是防火墙/路由器/NAT的常见问题,这是另一个常见的FTPS问题


希望这有帮助

即使以正确的顺序调用了
PBSZ 0
PROT p
,有时服务器确实需要SSL会话重用,而客户端默认情况下并非如此

例如,当尝试列出目录时,会出现以下回复。因此,不会返回任何内容列表,这样客户端就会看到目录为空:

LIST /
150 Here comes the directory listing.
522 SSL connection failed; session reuse required: see require_ssl_reuse option in sftpd.conf man page
为了克服这一问题,需要通过重写
\u prepareDataSocket()
方法对FTPSClient进行自定义初始化

此处详细说明了解决方案:

从上述链接获取的工作代码示例:

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.Socket;
import java.util.Locale;

import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.SSLSocket;

import org.apache.commons.net.ftp.FTPSClient;

import com.google.common.base.Throwables;

public class SSLSessionReuseFTPSClient extends FTPSClient {

  // adapted from: https://trac.cyberduck.io/changeset/10760
  @Override
  protected void _prepareDataSocket_(final Socket socket) throws IOException {
    if(socket instanceof SSLSocket) {
      final SSLSession session = ((SSLSocket) _socket_).getSession();
      final SSLSessionContext context = session.getSessionContext();
      try {
        final Field sessionHostPortCache = context.getClass().getDeclaredField("sessionHostPortCache");
        sessionHostPortCache.setAccessible(true);
        final Object cache = sessionHostPortCache.get(context);
        final Method putMethod = cache.getClass().getDeclaredMethod("put",Object.class, Object.class);
        putMethod.setAccessible(true);
        final Method getHostMethod = socket.getClass().getDeclaredMethod("getHost");
        getHostMethod.setAccessible(true);
        Object host = getHostMethod.invoke(socket);
        final String key = String.format("%s:%s", host, String.valueOf(socket.getPort())).toLowerCase(Locale.ROOT);
        putMethod.invoke(cache, key, session);
      } catch(Exception e) {
        throw Throwables.propagate(e);
      }
    }
  }

}

希望几年后有人觉得我的评论有用。
在我的例子中,我将
retrieveFile
替换为
retrieveFileStream
。它需要更多的代码,但至少可以工作。

请正确设置代码格式。您只需标记它,然后使用编辑器顶部提供的工具点击格式化即可。谢谢,这很有用,因为我有点期待它会成为防火墙。但该代码不起作用,防止挂起,但输出“FTP客户端收到网络错误”。我想知道
FTPSClient
是否不希望数据传输受到保护。如果改用
execPROT(“C”)
会发生什么?这会告诉服务器清除数据传输保护通道(仅用于测试)。您还可以使用
lftp
(或其他FTPS客户端)下载文件,再次检查服务器(以及任何防火墙/NATs/路由器)是否正常工作。如果成功了,那么你就知道这是找到正确代码的问题;如果不是,那么问题可能不在您的代码中,而是在其他地方。我可以使用filezilla(浏览器超时)下载服务器文件。我们将研究lftp,再次感谢。虽然此链接可以回答问题,但最好在此处包含答案的基本部分,并提供链接供参考。如果链接页面发生更改,仅链接的答案可能无效。@MattKe谢谢,在这里发布了工作代码示例。