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

Java FTP 大文件傳輸實戰:斷點續傳、被動模式、NAT 穿透與 Applet 客戶端整合

     編輯:Java 相關知識

FTP 協議核心概念

模式命令連接數據連接防火牆友好度
主動模式客戶端 → 伺服器 21伺服器 20 → 客戶端隨機端口差(需客戶端開放入站)
被動模式客戶端 → 伺服器 21客戶端 → 伺服器隨機端口好(僅需伺服器開放範圍)

生產環境強制使用被動模式,並配置伺服器 `pasv_address`(公網 IP)與 `pasv_min_port`/`pasv_max_port` 範圍。

斷點續傳核心實現

原理

  1. 上傳前檢查伺服器檔案大小(SIZE 指令)
  2. 對比本地檔案,若伺服器較小 → 計算偏移量
  3. 發送 REST offset 指令告知伺服器從偏移處寫入
  4. 本地用 RandomAccessFile.seek(offset) 定位讀取

關鍵代碼片段


public class ResumableFtpClient {
    private FTPClient ftpClient;
    
    public void uploadWithResume(File localFile, String remotePath) throws IOException {
        // 1. 連結並進入被動模式
        ftpClient.connect(server, port);
        ftpClient.login(user, pass);
        ftpClient.enterLocalPassiveMode();
        ftpClient.setFileType(FTP.BINARY_FILE_TYPE);

        // 2. 檢查遠端檔案大小
        long remoteSize = 0;
        if (ftpClient.getFileSize(remotePath) > 0) {
            remoteSize = ftpClient.getFileSize(remotePath);
        }

        long localSize = localFile.length();
        if (remoteSize >= localSize) {
            log.info("檔案已完整或本地較舊,跳過: {}", remotePath);
            return;
        }

        // 3. 斷點續傳:設置偏移量
        try (RandomAccessFile raf = new RandomAccessFile(localFile, "r")) {
            raf.seek(remoteSize); // 定位到未上傳處
            
            // 發送 REST 指令(Apache Commons Net 支援)
            ftpClient.setRestartOffset(remoteSize);
            
            // 4. 流式上傳
            try (InputStream is = new BufferedInputStream(new FileInputStream(raf.getFD()))) {
                boolean success = ftpClient.storeFile(remotePath, is);
                if (!success) throw new IOException("上傳失敗: " + ftpClient.getReplyString());
            }
        }
    }
    
    public void downloadWithResume(String remotePath, File localFile) throws IOException {
        long localSize = localFile.exists() ? localFile.length() : 0;
        long remoteSize = ftpClient.getFileSize(remotePath);
        
        if (localSize >= remoteSize) return; // 已完整
        
        try (RandomAccessFile raf = new RandomAccessFile(localFile, "rw")) {
            raf.seek(localSize);
            ftpClient.setRestartOffset(localSize);
            
            try (OutputStream os = new BufferedOutputStream(new FileOutputStream(raf.getFD()))) {
                boolean success = ftpClient.retrieveFile(remotePath, os);
                if (!success) throw new IOException("下載失敗: " + ftpClient.getReplyString());
            }
        }
    }
}

NAT 穿透與端口映射實戰

場景

FTP 伺服器部署於內網(192.168.x.x),客戶端從公網訪問。需在邊界防火牆/路由器配置:

  1. 命令通道:DNAT 公網 IP:21 → 內網 FTP IP:21
  2. 被動模式數據通道:DNAT 公網 IP:50000-50100 → 內網 FTP IP:50000-50100
  3. FTP 伺服器配置:pasv_address=公網IP、pasv_min_port=50000、pasv_max_port=50100

否則客戶端收到內網 IP 發起數據連接失敗。

Applet 客戶端整合(歷史方案,現代替代見下)

Java Applet 核心能力

  • 瀏覽器沙箱外訪問本地檔案系統(需簽名 + 用戶授權)
  • 直接使用 Apache Commons Net 實現 FTP 邏輯
  • 與 JavaScript 雙向通信:JSObject.getWindow(this)

安全策略配置


/* java.policy 關鍵權限 */
grant {
    permission java.io.FilePermission "<>", "read,write,delete";
    permission java.net.SocketPermission "*", "connect,accept,resolve";
    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www.protocol.ftp";
    permission java.security.AllPermission; // 簽名 Applet 通常需全權限
};

部署時將 JRE + 簽名 JAR + policy 打包為安裝包(如 Install4j、IzPack),簡化用戶安裝。

現代替代方案:Web 直傳 + 對象存儲

方案原理優勢適用場景
預簽名 URL後端生成帶簽名的 S3/OSS 上傳 URL,前端直傳無伺服器壓力、海量併發、支持斷點續傳(分片上傳)圖片、視頻、文檔上傳
WebUploader / Uppy前端分片、並發、斷點續傳、進度條純前端、無插件、用戶體驗佳通用文件上傳
WebRTC DataChannelP2P 傳輸,伺服器僅信令極低延遲、去中心化大文件點對點、內網穿透
Tus.io 協議開放斷點續傳標準,支援多語言客戶端標準化、可斷點、可暫停企業級文件上傳服務

Java 後端對接預簽名上傳範例


@RestController
public class UploadController {
    @Autowired private AmazonS3 s3Client;
    
    @PostMapping("/presign")
    public Map<String, String> presign(@RequestParam String fileName, 
                                        @RequestParam String contentType) {
        String key = "uploads/" + UUID.randomUUID() + "_" + fileName;
        
        GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucket, key)
            .withMethod(HttpMethod.PUT)
            .withExpiration(Date.from(Instant.now().plusMinutes(15)))
            .withContentType(contentType);
        
        URL url = s3Client.generatePresignedUrl(request);
        return Map.of("uploadUrl", url.toString(), "key", key, "method", "PUT");
    }
    
    @PostMapping("/complete")
    public void complete(@RequestParam String key) {
        // 可選:驗證文件完整性、觸發後續處理(轉碼、索引等)
    }
}

總結:技術選型建議

  1. 新專案:首選對象存儲 + 預簽名直傳 + 前端分片上傳組件
  2. 存量 FTP 系統:Apache Commons Net + 被動模式 + 斷點續傳 + NAT 映射
  3. 內網大文件傳輸:Tus.io 協議 + 自建 Tus 伺服器
  4. 避免:Java Applet(瀏覽器已淘汰 NPAPI)、ActiveX、未加密 FTP
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved