在 Android 模拟器中从本地 FTP 服务器下载时出现错误“227 进入被动模式”/"Connection refused"

Error "227 Entering Passive Mode"/"Connection refused" when downloading from local FTP server in Android emulator

我正在尝试在 Android 模拟器中使用 Java FTPSClient 运行 从本地 FileZilla 服务器下载文件。

我写了这个帮助代码来下载一个文件:

public boolean downloadSingleFile(FTPSClient ftpClient,
                                  String remoteFilePath, File downloadFile) {
    OutputStream outputStream;
    Log.i("t1", remoteFilePath + " - " + downloadFile.getAbsolutePath());
    try {
        outputStream = new BufferedOutputStream(new FileOutputStream(
                downloadFile));
        ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
        boolean retval = ftpClient.retrieveFile(remoteFilePath, outputStream);
        outputStream.flush();
        return retval;
    } catch (Exception e) {
        Log.e("dwFile", e.toString());
        Log.e("dwFile", ftpClient.getReplyString());
    } return false;
}

我这样调用这个函数:

FTPSClient dwClient = new FTPSClient();
dwClient.addProtocolCommandListener(
    new PrintCommandListener(
    new PrintWriter(new OutputStreamWriter(System.out, "UTF-8")), true));
dwClient.setConnectTimeout(30 * 1000);
dwClient.connect(OmsSettingsFunctions.getFTPServer());
Log.i("dwDB", dwClient.getReplyString());
if (dwClient.login(FPTuser, FTPpass))  {
    Log.i("dwDB", dwClient.getReplyString());
    dwClient.enterLocalPassiveMode();
    File dwFile = new File(externalPath + "/Android/data/com.myapp/files/Documents/db.temp");
    if(!downloadSingleFile(dwClient, "/DBs/db.txt", dwFile)) {
        Log.e("dwDB", "Download could not finish (DB)");
        Log.e("dwDB", dwClient.getReplyString());
    }...

但是我一直收到这个错误:

I/System.out: 220-FileZilla Server version 0.9.41 beta
I/System.out: 220-written by Tim Kosse (Tim.Kosse@gmx.de)
              220 Please visit http://sourceforge.net/projects/filezilla/
I/System.out: AUTH TLS
D/EGL_emulation: eglMakeCurrent: 0xa209dd60: ver 3 0 (tinfo 0x9f652ff0)
D/EGL_emulation: eglMakeCurrent: 0xa209dd60: ver 3 0 (tinfo 0x9f652ff0)
I/System.out: 234 Using authentication type TLS
I/dwDB: 234 Using authentication type TLS
I/Permission: Readingpermission is granted
I/Permission: Writingpermission is granted
I/System.out: USER *******
I/System.out: 331 Password required for omstest
I/System.out: PASS *******
I/System.out: 230 Logged on
I/dwDB: 230 Logged on
I/t1: /DBs/db.txt - /storage/0FF0-280B/Android/data/com.myapp/files/Documents/db.temp
I/System.out: TYPE I
I/System.out: 200 Type set to I
I/System.out: PASV
I/System.out: 227 Entering Passive Mode (127,0,0,1,199,113)
E/dwFile: java.net.ConnectException: Connection refused
          227 Entering Passive Mode (127,0,0,1,199,113)
E/dwDB: Download could not finish (DB)
        227 Entering Passive Mode (127,0,0,1,199,113)

我已经尝试使用 enterLocalActivemode() 而不是 enterLocalPassivmode() 但它没有帮助。 FTP 服务器在我的本地计算机上执行 TLS 和 运行。我通过 10.0.2.2(Android 环回)连接到它。我该如何解决这个问题?

虽然我不熟悉 Android 模拟器,但我假设您需要连接到 10.0.2.2 才能连接到模拟器主机。

在FTP 被动模式下,服务器发回一个IP 地址,FTP 客户端需要连接到该IP 地址以传输文件(或目录列表)。当您的 FTP 服务器侦听 127.0.0.1 时,它会发回该 IP 地址。但是 127.0.0.1 在您的 Android 代码的上下文中指的是(模拟的)Android 主机。因此“连接被拒绝”。

这与连接到 NAT 后面的 FTP 服务器的常见问题非常相似。参见

因此解决方案是相同的:

  • 在 FileZilla Server 界面中,转到编辑 > 设置 > 被动模式设置 > 特定 IPv4 > 被动模式传输的外部服务器 IP 地址。并输入 10.0.2.2.
  • 也许您还需要取消选中 “不要将外部 IP 用于本地连接”

显然,这反过来会使 FTP 服务器无法用于普通客户端。

正如您正确评论的那样,此问题仅在从 Android 模拟器连接到模拟器主机上的 FTP 服务器 运行 时出现。


另一个解决方案是使用 FTPClient.setPassiveNatWorkaroundStrategy。它接受 HostnameResolver 接口的实现。如果您以将 127.0.0.1 转换为 10.0.2.2 的方式实施,它将允许您的 Java 代码连接,即使服务器上没有任何更改。

public static class ServerResolverImpl implements HostnameResolver {
    private FTPClient client;

    public ServerResolverImpl(FTPClient client) {
        this.client = client;
    }

    @Override
    public String resolve(String hostname) throws UnknownHostException {
        // Ignore "hostname" returned by the server.
        // Instead always use the primary address of the FTP server.
        return this.client.getRemoteAddress().getHostAddress();
    }
}