FTPSClient retrievefile() 挂起

Posted

技术标签:

【中文标题】FTPSClient retrievefile() 挂起【英文标题】:FTPSClient retrievefile() hanging 【发布时间】:2016-08-03 13:33:36 【问题描述】:

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

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

运行: 220--------- 欢迎使用 Pure-FTPd [privsep] [TLS] ---------- 220-您是允许的 50 个用户中的第 2 个用户。 220-当地时间现在是19:42。服务器端口:21. 220-这是一个私人系统 - 没有匿名登录 此服务器也欢迎 220-IPv6 连接。 220 您将在 15 分钟不活动后断开连接。 身份验证 TLS 234 身份验证 TLS 正常。 用户 331 用户正常。需要密码 通过 230 好的。当前受限目录是 / A型 200 TYPE 现在是 ASCII EPSV 229 扩展被动模式正常 (|||53360|) RETR 测试.txt 150-接受数据连接 150 7.3 KB 要下载

代码如下:

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");

非常感谢任何想法。

【问题讨论】:

请正确格式化您的代码。您可以使用编辑器顶部提供的工具对其进行标记和点击格式。 【参考方案1】:

通常,FTPS 连接的 FTP 命令序列是(每个RFC 4217)AUTH TLSPBSZ 0然后USERPASS .因此,您可以尝试:

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 问题。

希望这会有所帮助!

【讨论】:

谢谢,这很有用,因为我有一半期望它是防火墙的东西。但该代码不起作用,防止挂起但输出“FTP客户端收到网络错误”。 嗯。我想知道FTPSClient 是否不期望受保护的数据传输。如果你改用execPROT("C") 会发生什么?这告诉服务器清除保护的数据传输通道(仅用于测试)。 您还可以使用lftp(或其他一些 FTPS 客户端)下载文件来仔细检查服务器(和任何防火墙/NAT/路由器)是否正常运行。如果 that 成功了,那么你就知道这是找到正确代码的问题了;如果不是,那么问题可能不在您的代码中,而在其他地方。 它再次挂起,使用 execPROT("C")。我可以使用 filezilla 下载服务器文件(资源管理器超时)。将研究 lftp,再次感谢。【参考方案2】:

即使 PBSZ 0PROT 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

为了克服这个问题,需要通过重写_prepareDataSocket_() 方法来自定义初始化 FTPSClient。

解决方法在这里详细解释:https://eng.wealthfront.com/2016/06/10/connecting-to-an-ftps-server-with-ssl-session-reuse-in-java-7-and-8/

来自上述链接的工作代码示例:

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);
      
    
  


【讨论】:

虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接的答案可能会失效。 @MattKe 谢谢,在这里发布了工作代码示例。【参考方案3】:

希望几年后有人会发现我的评论有用。 就我而言,我将retrieveFile 替换为retrieveFileStream。它需要更多代码,但至少它可以工作。

【讨论】:

【参考方案4】:

对我来说,我在将 Apache Commons Net 升级到 3.8.0 后解决了这个问题。

dependencies 
    implementation 'commons-net:commons-net:3.8.0'
    ...

【讨论】:

以上是关于FTPSClient retrievefile() 挂起的主要内容,如果未能解决你的问题,请参考以下文章

FtpClient下载文件时文件大小为0

FtpClient

Java SFTP 服务器库? [关闭]

pb datawindow retrieve 怎么用

ftp上传的文件大小全是0kb

XEN虚拟机建立不成功 出现unable to retrieve