簡體   English   中英

Apache Commons Net FTP 正在上傳損壞的文件

[英]Apache Commons Net FTP is uploading corrupted files

我正在嘗試使用 Apache Commons Net 進行 FTP 文件傳輸。

問題是文件間歇性地到達服務器損壞。 '損壞' 我的意思是 WinRAR 告訴我一個 ZIP 文件有一個'Unexpected end of archive' 有時文件是完全空的。 我注意到這種情況對於較大的文件(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 秒內完成,並且服務器上的所有結果文件都已損壞。

我也測試過沒有設置任何超時,問題仍然存在。

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)

在每個方法之前retrieveFileretrieveFileStreamstoreFile

解釋

文件已損壞,因為默認文件類型為FTP.ASCII_FILE_TYPE 這會導致問題。 如果您在 linux 上,所有字節\\n\\r (Windows 文件結尾)都將更改為\\n字節。 這破壞了文件。

為了避免這種行為,你必須調用ftpClient.setFileType(FTP.BINARY_FILE_TYPE) 不幸的是,這個設置被每個connect方法重置回ASCII_FILE_TYPE 在我的情況下,這甚至被方法listFiles重置。 我猜,這是因為我在passiveMode上使用了passiveMode

因此,如果您想避免麻煩,請在每次文件傳輸之前調用setFileType(FTP.BINARY_FILE_TYPE)

盡管指定了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代碼取自此處

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM