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

Java 實現 HTTP 斷點續傳下載:從原理到完整框架實戰

     編輯:Java 相關知識

前言

斷點續傳是下載工具的核心功能,本文系統講解 HTTP 斷點續傳的協議原理、Java 關鍵 API 使用,並提供一個基於多線程分片下載的完整框架級實現代碼(含 6 個核心類),適合作為下載模塊參考架構。

第一部分:斷點續傳協議原理

標準下載請求/響應

GET /down.zip HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: */*
Connection: Keep-Alive

--- 響應 ---
HTTP/1.1 200 OK
Content-Length: 106786028
Accept-Ranges: bytes
Content-Type: application/octet-stream
Last-Modified: Mon, 30 Apr 2001 12:56:11 GMT

斷點續傳請求(關鍵:Range 頭)

GET /down.zip HTTP/1.0
User-Agent: NetFox
Range: bytes=2000070-        # 從第 2000070 字節開始
Accept: */*

--- 響應 ---
HTTP/1.1 206 Partial Content
Content-Length: 104785958
Content-Range: bytes 2000070-106786027/106786028
Content-Type: application/octet-stream

關鍵差異:請求添加 Range: bytes=start-,響應狀態碼變為 206,新增 Content-Range 頭指示實際返回範圍。

第二部分:Java 核心 API 實現要點

1. 發送 Range 請求

URL url = new URL("https://www.example.com/down.zip");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("User-Agent", "NetFox");
conn.setRequestProperty("Range", "bytes=2000070-"); // 關鍵設置
InputStream in = conn.getInputStream(); // 獲取斷點數據流

2. 隨機訪問文件寫入

RandomAccessFile raf = new RandomAccessFile("down.zip", "rw");
raf.seek(2000070); // 定位寫入位置
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
    raf.write(buf, 0, len);
}

第三部分:完整框架實現(6 核心類)

架構圖解

SiteFileFetch (主控線程)
    │
    ├─ 管理 SiteInfoBean (任務配置)
    ├─ 動態分割文件 → 創建 FileSplitterFetch[] (工作線程)
    ├─ 監控進度、持久化斷點信息 (.info 文件)
    └─ 協調完成/失敗/停止狀態
    
FileSplitterFetch (分片下載線程)
    │
    ├─ 負責 [startPos, endPos] 區間下載
    ├─ 發送 Range 請求、寫入文件
    ├─ 異常自動重試(從 curPos 繼續)
    └─ 通過 FileAccessI 寫入文件
    
FileAccessI (文件訪問接口)
    └─ 封裝 RandomAccessFile,提供線程安全 write()
    
SiteInfoBean (任務配置 Bean)
    └─ URL、保存路徑、文件名、分片數
    
Utility (工具類)
    └─ sleep、log 等通用方法
    
TestMethod (測試入口)

核心類關鍵代碼片段

SiteFileFetch - 主控調度邏輯

public class SiteFileFetch extends Thread {
    // ... 字段定義 ...
    
    public void run() {
        try {
            if (bFirst) {
                nFileLength = getFileSize(); // HEAD 請求獲取文件大小
                // 等分文件計算 startPos/endPos 數組
            }
            
            // 啟動所有分片線程
            fileSplitterFetch = new FileSplitterFetch[nStartPos.length];
            for (int i = 0; i < nStartPos.length; i++) {
                fileSplitterFetch[i] = new FileSplitterFetch(...);
                fileSplitterFetch[i].start();
            }
            
            // 監控循環:持久化進度、檢查完成
            while (!bStop) {
                write_nPos(); // 保存各線程當前位置到 .info 文件
                Utility.sleep(500);
                // 檢查所有線程是否完成
            }
        } catch (Exception e) { e.printStackTrace(); }
    }
    
    // 斷點持久化:保存每個線程的 startPos/endPos
    private void write_nPos() { /* DataOutputStream 寫入 .info 文件 */ }
    private void read_nPos()  { /* DataInputStream 讀取 .info 文件恢復 */ }
}

FileSplitterFetch - 分片下載核心

public class FileSplitterFetch extends Thread {
    public void run() {
        while (nStartPos < nEndPos && !bStop) {
            try {
                URL url = new URL(sURL);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestProperty("Range", "bytes=" + nStartPos + "-");
                
                InputStream in = conn.getInputStream();
                byte[] buf = new byte[1024];
                int nRead;
                while ((nRead = in.read(buf)) > 0 && nStartPos < nEndPos && !bStop) {
                    nStartPos += fileAccessI.write(buf, 0, nRead); // 寫入並更新位置
                }
                bDownOver = true;
            } catch (Exception e) { e.printStackTrace(); }
        }
    }
}

FileAccessI - 線程安全文件寫入

public class FileAccessI implements Serializable {
    private RandomAccessFile raf;
    private long pos;
    
    public synchronized int write(byte[] b, int off, int len) {
        try {
            raf.write(b, off, len);
            return len;
        } catch (IOException e) { e.printStackTrace(); return -1; }
    }
}

第四部分:生產級優化建議

維度現狀問題優化方案
連接管理每線程獨立創建 HttpURLConnection使用連接池、Keep-Alive、HttpClient 復用連接
進度持久化頻繁寫入 .info 文件,性能差內存緩衝 + 定時刷盤、或用 SQLite/LevelDB
錯誤處理單純 printStackTrace、無限重試分類異常、指數退避重試、熔斷機制
校驗機制無文件完整性校驗下載完成後驗證 MD5/SHA256、ETag/Last-Modified
速度控制無限速、佔滿帶寬令牌桶/漏桶算法實現下載限速
暫停/恢復僅支持停止標誌暴露 pause/resume 接口、狀態機管理
多文件/隊列單任務設計引入下載隊列、優先級調度、並發數控制

現代替代方案

  • OkHttp:原生支持 Range 請求、連接池、透明壓縮、現代 API
  • ResumableDownload (Google):專門解決斷點續傳的庫
  • Apache HttpClient 5:成熟的 HTTP 客戶端,支持非阻塞 IO
  • Netty:高性能非阻塞網絡框架,適合構建下載服務端

總結

本文從 HTTP 協議層面徹底講解斷點續傳原理,展示 Java 核心 API(HttpURLConnection + RandomAccessFile)實現要點,並提供一個完整的多線程分片下載框架代碼結構。理解這些基礎原理對於排查下載問題、優化性能、擴展功能至關重要。實際項目中建議基於成熟庫二次開發,專注於業務邏輯而非重複造輪子。

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