標準下載請求:
GET /file.zip HTTP/1.1
Host: example.com
斷點續傳請求 (從 2,000,070 字節開始):
GET /file.zip HTTP/1.1
Host: example.com
Range: bytes=2000070-
User-Agent: NetFox
伺服器響應 206 Partial Content:
HTTP/1.1 206 Partial Content
Content-Range: bytes 2000070-106786027/106786028
Content-Length: 104785958
Content-Type: application/octet-stream
Accept-Ranges: bytes
關鍵頭:Range (請求)、Content-Range (響應)、Accept-Ranges: bytes (伺服器支援聲明)。
public class ResumeDownloader {
public static void download(String urlStr, String destPath, long startPos) throws IOException {
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("User-Agent", "Java-ResumeDownloader/1.0");
conn.setRequestProperty("Range", "bytes=" + startPos + "-");
try (InputStream in = conn.getInputStream();
RandomAccessFile raf = new RandomAccessFile(destPath, "rw")) {
raf.seek(startPos); // 定位寫入位置
byte[] buffer = new byte[8192];
int len;
while ((len = in.read(buffer)) != -1) {
raf.write(buffer, 0, len);
}
}
}
}
RandomAccessFile 線程安全寫入、位置管理。startPos/endPos 到 .info 文件,支援崩潰恢復。
// 文件長度 L,分片數 N
long chunkSize = L / N;
for (int i = 0; i < N; i++) {
long start = i * chunkSize;
long end = (i == N - 1) ? L : start + chunkSize; // 最後一片包含餘數
// 創建 FileSplitterFetch(start, end)
}
// 寫入 .info 文件 (DataOutputStream)
output.writeInt(splitters.length);
for (Splitter s : splitters) {
output.writeLong(s.getCurrentPos());
output.writeLong(s.getEndPos());
}
// 讀取恢復
int count = input.readInt();
for (int i = 0; i < count; i++) {
splitters[i].setCurrentPos(input.readLong());
splitters[i].setEndPos(input.readLong());
}
BlockingQueue。netfox/ ├── SiteFileFetch.java // 主控線程 ├── FileSplitterFetch.java // 工作線程 ├── FileAccess.java // RandomAccessFile 封裝 ├── SiteInfoBean.java // 配置 Bean ├── ProgressPersistence.java // 進度持久化 ├── Util.java // 日誌、睡眠工具 └── TestMain.java // 測試入口
| 問題 | 解決方案 |
|---|---|
| 伺服器不支援 Range | 檢測響應碼 200 且無 Accept-Ranges → 降級單線程下載 |
| 網絡波動導致讀取 -1 | 捕獲 IOException,指數退避重試 (最多 3 次),記錄斷點 |
| 大文件 (GB 級) 內存壓力 | 流式讀寫、緩衝區 8KB-64KB、避免載入整個文件 |
| 多線程寫入同一文件衝突 | RandomAccessFile seek 定位 + synchronized 寫入鎖 / 分片互不重疊天然無衝突 |
| 進度文件損壞/丟失 | 校驗和 (CRC32) 驗證、備份舊版本、啟動時完整性檢查 |
| 用戶暫停/取消 | volatile boolean stopFlag,工作線程定期檢查並安全退出 |
// OkHttp 斷點續傳 (自動處理 Range、重試、連接池)
Request request = new Request.Builder()
.url(url)
.header("Range", "bytes=" + downloaded + "-")
.build();
try (Response response = client.newCall(request).execute();
InputStream in = response.body().byteStream();
RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
raf.seek(downloaded);
byte[] buffer = new byte[8192];
int len;
while ((len = in.read(buffer)) != -1) raf.write(buffer, 0, len);
}
斷點續傳核心在於 HTTP Range 標準 + RandomAccessFile 隨機寫入 + 進度持久化。單線程易實現,生產級引擎需解決 分片策略、線程池協作、斷點恢復、異常重試、伺服器兼容性、用戶控制 等工程化問題。理解底層協議與 I/O 模型,方能構建健壯的下載引擎;現代項目建議直接使用成熟 HTTP 客戶端庫 (OkHttp、HttpClient 5) 組裝業務邏輯,避免重複造輪子。