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

Posted

技术标签:

【中文标题】Apache Commons Net FTP 正在上传损坏的文件【英文标题】:Apache Commons Net FTP is uploading corrupted files 【发布时间】:2011-03-27 18:52:16 【问题描述】:

我正在尝试使用 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 秒内完成,并且服务器上的所有生成文件都已损坏。

我也进行了测试,没有设置任何超时,问题仍然存在。

【问题讨论】:

【参考方案1】:

解决方案

我遇到了同样的问题并通过调用解决了它

ftpClient.setFileType(FTP.BINARY_FILE_TYPE)

在每个方法之前retrieveFileretrieveFileStreamstoreFile

说明

文件已损坏,因为默认文件类型为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”的这种行为真的很糟糕。 模式(至少在某些 FTP 服务器上)重置为login,所以setFileType has to be called after it。通常,listFiles 不会重置它,但您的特定 FTP 服务器可能会这样做。 Apache Commons Net 库本身从不自行重置模式。 谢谢 - 好点 - 我记得,在我的情况下,登录后调用 setFileType 是不够的。你是对的,这个问题可能是由服务器引起的,而不是由客户端库引起的。【参考方案2】:

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 不仅在connect,之后,它还必须在login之后,参见Apache Java FTP client does not switch to binary transfer mode on some servers。【参考方案3】:

尽管指定了binary file type,但我还是遇到了这个问题,所以我编写了代码来通过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 代码取自 here。

【讨论】:

以上是关于Apache Commons Net FTP 正在上传损坏的文件的主要内容,如果未能解决你的问题,请参考以下文章

如何导入 org.apache.commons.net.ftp.FTPClient

Apache commons-net FTP - 将文件上传到安全 FTP IIS

JAVA-使用Commons Net连接FTP

[Java] 使用 Apache的 Commons-net库 实现FTP操作

org.apache.commons.net.ftp.FTPClient下载中文文件夹乱码如何处理?

maven中,POM.XML中怎么配置org.apache.commons.net.ftp引用包,求配置代码- -