首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Apache Commons Net 實現 FTP 斷點續傳上傳下載

     編輯:Java 相關知識

依賴引入

<dependency>
    <groupId>commons-net</groupId>
    <artifactId>commons-net</artifactId>
    <version>3.9.0</version> <!-- 請檢查最新版本 -->
</dependency>

核心原理

  • 被動模式:enterLocalPassiveMode(),適應防火牆/NAT 環境。
  • 二進制模式:setFileType(FTP.BINARY_FILE_TYPE),避免文字模式轉換損壞檔案。
  • 斷點續傳關鍵:
    • 下載:setRestartOffset(localFile.length()) + retrieveFile(remote, outputStream),OutputStream 以 append=true 開啟。
    • 上傳:setRestartOffset(remoteFileSize) + storeFile(remote, inputStream),InputStream skip(remoteFileSize) 定位。
  • 目錄自動建立:上傳前遞迴 makeDirectory + changeWorkingDirectory 確保遠端路徑存在。

完整工具類

package utils;

import org.apache.commons.net.ftp.*;
import org.apache.commons.net.PrintCommandListener;
import java.io.*;
import java.io.PrintWriter;

public class FtpResumeClient {
    private FTPClient ftp = new FTPClient();

    public FtpResumeClient() {
        ftp.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out), true));
    }

    public boolean connect(String host, int port, String user, String pass) throws IOException {
        ftp.connect(host, port);
        if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
            disconnect();
            return false;
        }
        return ftp.login(user, pass);
    }

    public boolean download(String remote, String local) throws IOException {
        ftp.enterLocalPassiveMode();
        ftp.setFileType(FTP.BINARY_FILE_TYPE);

        File localFile = new File(local);
        FTPFile[] files = ftp.listFiles(remote);
        if (files.length != 1) return false;

        long remoteSize = files[0].getSize();
        try (OutputStream out = new FileOutputStream(localFile, localFile.exists())) {
            if (localFile.exists()) {
                if (localFile.length() >= remoteSize) return false; // 已完整
                ftp.setRestartOffset(localFile.length());
            }
            return ftp.retrieveFile(remote, out);
        }
    }

    public enum UploadStatus {
        SUCCESS, FILE_EXISTS, REMOTE_LARGER, CREATE_DIR_FAIL, DELETE_FAIL, UPLOAD_FAIL
    }

    public UploadStatus upload(String local, String remote) throws IOException {
        ftp.enterLocalPassiveMode();
        ftp.setFileType(FTP.BINARY_FILE_TYPE);

        // 解析遠端目錄與檔名
        String remoteFileName = remote.contains("/") ? remote.substring(remote.lastIndexOf('/') + 1) : remote;
        String remoteDir = remote.contains("/") ? remote.substring(0, remote.lastIndexOf('/') + 1) : "/";

        // 確保遠端目錄存在
        if (!remoteDir.equals("/") && !ftp.changeWorkingDirectory(remoteDir)) {
            if (!createRemoteDirs(remoteDir)) return UploadStatus.CREATE_DIR_FAIL;
        }

        FTPFile[] remoteFiles = ftp.listFiles(remoteFileName);
        File localFile = new File(local);
        long localSize = localFile.length();

        if (remoteFiles.length == 1) {
            long remoteSize = remoteFiles[0].getSize();
            if (remoteSize == localSize) return UploadStatus.FILE_EXISTS;
            if (remoteSize > localSize) return UploadStatus.REMOTE_LARGER;

            // 斷點續傳
            try (InputStream in = new FileInputStream(localFile)) {
                if (in.skip(remoteSize) == remoteSize) {
                    ftp.setRestartOffset(remoteSize);
                    if (ftp.storeFile(remoteFileName, in)) return UploadStatus.SUCCESS;
                }
            }
            // 續傳失敗:刪除遠端檔案重傳
            if (!ftp.deleteFile(remoteFileName)) return UploadStatus.DELETE_FAIL;
        }

        // 全新上傳
        try (InputStream in = new FileInputStream(localFile)) {
            return ftp.storeFile(remoteFileName, in) ? UploadStatus.SUCCESS : UploadStatus.UPLOAD_FAIL;
        }
    }

    private boolean createRemoteDirs(String path) throws IOException {
        int start = path.startsWith("/") ? 1 : 0;
        int end = path.indexOf('/', start);
        while (true) {
            String sub = path.substring(start, end > 0 ? end : path.length());
            if (!ftp.changeWorkingDirectory(sub)) {
                if (!ftp.makeDirectory(sub) || !ftp.changeWorkingDirectory(sub)) return false;
            }
            if (end < 0) break;
            start = end + 1;
            end = path.indexOf('/', start);
        }
        return true;
    }

    public void disconnect() throws IOException {
        if (ftp.isConnected()) ftp.disconnect();
    }

    // 測試主程式略
}

常見坑點與解決

  1. 伺服器不支援 REST 指令:部分舊版/嵌入式 FTP Server 不支援斷點續傳,上傳會失敗。建議先用 ftp.getReplyString() 觀察指令回應。
  2. 權限問題:確保執行帳號對遠端目錄有寫入/建立權限。
  3. 大檔案傳輸超時:設定 ftp.setControlKeepAliveTimeout(300) 發送 NOOP 保持連線。
  4. 編碼問題:中文檔名需設定 ftp.setControlEncoding("UTF-8")(Server 端也需支援)。

總結

Apache Commons Net 提供了完整的 FTP 協定支援,配合 setRestartOffset 與隨機存取流,即可實現健壯的斷點續傳。實務上建議封裝重試邏輯、進度回呼與校驗和(MD5/SHA)以確保資料完整性。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved