Apache Commons Net FTP正在上传损坏的文件。

10

我正在尝试使用Apache Commons Net进行FTP文件传输。

问题是文件有时会损坏地到达服务器。所谓“损坏”,是指WinRAR告诉我一个ZIP文件有一个“意外的归档结尾”。有时文件完全为空。我注意到这种情况更多地发生在较大的文件(100kb +)上,但对于小文件(20kb)也会发生。

我确信被上传的源zip文件是有效的,而且只有243kb。

代码没有产生任何错误/异常。

以下是执行的代码:

int CON_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(20); // fail if can't connect within 20 seconds
int LIVE_TIMEOUT = (int) TimeUnit.MINUTES.toMillis(5); // allow up to 5 minutes for data transfers

FTPClient client = new FTPClient();
client.setConnectTimeout(CON_TIMEOUT);
client.setDataTimeout(LIVE_TIMEOUT);
client.connect(host);
client.setSoTimeout(LIVE_TIMEOUT);
client.login(user, pass);
client.changeWorkingDirectory(dir);
log("client ready");

File file = new File(filePath);
String name = new Date().getTime() + "-" + file.getName();

InputStream fis = null;
try
{
    fis = new FileInputStream(file);
    if (!client.storeFile(name, fis))
        throw new RuntimeException("store failed");
    log("store " + name + " complete");
}
finally
{
    IOUtils.closeQuietly(fis);
    try
    {
        client.logout();
        log("logout");
    }
    catch (Throwable e)
    {
        log("logout failed", e);
    }
    try
    {
        client.disconnect();
        log("disconnect");
    }
    catch (Throwable e)
    {
        log("disconnect failed", e);
    }
}

还有一些日志:

2010-08-10 21:32:38 client ready
2010-08-10 21:32:49 store 1281439958234-file.zip complete
2010-08-10 21:32:49 logout
2010-08-10 21:32:49 disconnect
2010-08-10 21:32:50 client ready
2010-08-10 21:33:00 store 1281439970968-file.zip complete
2010-08-10 21:33:00 logout
2010-08-10 21:33:00 disconnect
2010-08-10 21:33:02 client ready
2010-08-10 21:33:11 store 1281439982234-file.zip complete
2010-08-10 21:33:11 logout
2010-08-10 21:33:11 disconnect
2010-08-10 21:33:15 client ready
2010-08-10 21:33:25 store 1281439995890-file.zip complete
2010-08-10 21:33:26 logout
2010-08-10 21:33:26 disconnect
2010-08-10 21:33:27 client ready
2010-08-10 21:33:36 store 1281440007531-file.zip complete
2010-08-10 21:33:36 logout
2010-08-10 21:33:36 disconnect
2010-08-10 21:33:37 client ready
2010-08-10 21:33:48 store 1281440017843-file.zip complete
2010-08-10 21:33:48 logout
2010-08-10 21:33:48 disconnect
2010-08-10 21:33:49 client ready
2010-08-10 21:33:59 store 1281440029781-file.zip complete
2010-08-10 21:33:59 logout
2010-08-10 21:33:59 disconnect
2010-08-10 21:34:00 client ready
2010-08-10 21:34:09 store 1281440040812-file.zip complete
2010-08-10 21:34:09 logout
2010-08-10 21:34:09 disconnect
2010-08-10 21:34:10 client ready
2010-08-10 21:34:23 store 1281440050859-file.zip complete
2010-08-10 21:34:24 logout
2010-08-10 21:34:24 disconnect
2010-08-10 21:34:25 client ready
2010-08-10 21:34:35 store 1281440065421-file.zip complete
2010-08-10 21:34:35 logout
2010-08-10 21:34:35 disconnect

请注意,所有这些操作都在15秒内完成,但服务器上的所有结果文件都已损坏。
我还进行了没有设置任何超时的测试,问题仍然存在。
3个回答

22

Commons FTP默认使用ASCII文件类型。当处理二进制数据,如ZIP文件时,您需要将其设置为Binary。

来自http://commons.apache.org/net/api/org/apache/commons/net/ftp/FTPClient.html

FTPClient的默认设置是使用FTP.ASCII_FILE_TYPE, FTP.NON_PRINT_TEXT_FORMAT, FTP.STREAM_TRANSFER_MODE和FTP.FILE_STRUCTURE。直接支持的文件类型只有FTP.ASCII_FILE_TYPE和FTP.BINARY_FILE_TYPE。

在发送文件之前,您需要执行setFileType(FTP.BINARY_FILE_TYPE)


在任何连接方法之后,请确保执行ftpClient.setFileType(FTP.BINARY_FILE_TYPE),因为connect()将重置类型为FTP.ASCII_FILE_TYPE - Mohsen
2
不仅在“连接”之后,还必须在“登录”之后执行,参见 Apache Java FTP client does not switch to binary transfer mode on some servers - Martin Prikryl

3

解决方案

我曾遇到类似的问题,通过调用以下方法解决:

ftpClient.setFileType(FTP.BINARY_FILE_TYPE)

before each 方法 retrieveFile, retrieveFileStream, storeFile

说明

文件已损坏,因为默认的fileType为FTP.ASCII_FILE_TYPE。这是引起问题的原因。如果您使用的是 Linux 系统,则所有字节 \n\r (Windows 文件结尾) 都会被转换成 \n 字节。这将导致文件损坏。

为了避免这种行为,您必须调用 ftpClient.setFileType(FTP.BINARY_FILE_TYPE)。不幸的是,每次调用 connect 方法后,此设置都会被重置为 ASCII_FILE_TYPE。 在我的情况下,即使是方法 listFiles 也会重置此项设置。我猜想,这是因为我在 ftpClient 上使用了 passiveMode

因此,如果您想避免麻烦,请在每次文件传输之前调用 setFileType(FTP.BINARY_FILE_TYPE)


顺便说一句:很抱歉,但我必须得说:'Apache Commons Net FTP' 的这种行为真的很糟糕。 - bugs_
在某些 FTP 服务器上,模式会在“登录”时重置,因此必须在此之后调用 setFileType。通常情况下,它不会在 “listFiles”时重置,但是您的特定 FTP 服务器可能会这样做。Apache Commons Net 库本身不会自行重置模式。 - Martin Prikryl
谢谢 - 很好的观点 - 我记得,在登录后调用 setFileType 并不足够解决我的问题。你说得对,这个问题很可能是由服务器而不是客户端库引起的。 - bugs_

0

我尽管指定了二进制文件类型,仍然遇到了这个问题,因此我编写了代码通过MD5哈希验证上传的文件:

public void upload(String sourceFilePath) throws Exception
{
    while (true)
    {
        // Upload
        File sourceFile = new File(sourceFilePath);
        String sourceFileHash = MD5Checksum.getMD5Checksum(sourceFilePath);
        String remoteFile = sourceFile.getName();

        try (InputStream inputStream = new FileInputStream(sourceFile))
        {
            boolean successful = ftpClient.storeFile(remoteFile, inputStream);

            if (!successful)
            {
                throw new IllegalStateException("Upload of " + sourceFilePath + " failed!");
            }
        }

        // Download
        File temporaryFile = File.createTempFile("prefix", "suffix");
        try (OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(temporaryFile)))
        {
            boolean successful = ftpClient.retrieveFile(remoteFile, outputStream);

            if (!successful)
            {
                throw new IllegalStateException("Download of " + sourceFilePath + " failed!");
            }
        }

        String downloadFileHash = MD5Checksum.getMD5Checksum(temporaryFile.getAbsolutePath());
        Files.delete(temporaryFile.toPath());

        // Make sure the file hashes match
        if (sourceFileHash.equals(downloadFileHash))
        {
            break;
        }
    }
}

MD5Checksum.java:

import java.io.*;
import java.security.MessageDigest;

public class MD5Checksum
{
    private static byte[] createChecksum(String filename) throws Exception
    {
        try (InputStream fileInputStream = new FileInputStream(filename))
        {
            byte[] buffer = new byte[1024];
            MessageDigest complete = MessageDigest.getInstance("MD5");
            int numRead;

            do
            {
                numRead = fileInputStream.read(buffer);
                if (numRead > 0)
                {
                    complete.update(buffer, 0, numRead);
                }
            } while (numRead != -1);

            return complete.digest();
        }
    }

    public static String getMD5Checksum(String filename) throws Exception
    {
        byte[] checksum = createChecksum(filename);
        StringBuilder result = new StringBuilder();

        for (byte singleByte : checksum)
        {
            result.append(Integer.toString((singleByte & 0xff) + 0x100, 16).substring(1));
        }

        return result.toString();
    }
}

MD5 代码取自这里


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接