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