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
ETag: "02ca57e173c11:95b"
GET /down.zip HTTP/1.1
Host: www.example.com
User-Agent: NetFox
Range: bytes=2000070- # 從第 2000070 字節開始
HTTP/1.1 206 Partial Content
Content-Length: 104785958
Content-Range: bytes 2000070-106786027/106786028
Content-Type: application/octet-stream
關鍵點:狀態碼 206、Content-Range 標識返回片段範圍、Accept-Ranges: bytes 聲明支持範圍請求。
// 1. 建立連接
URL url = new URL("https://www.example.com/down.zip");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 2. 設置 Range 標頭
conn.setRequestProperty("Range", "bytes=" + startPos + "-");
conn.setRequestProperty("User-Agent", "NetFox");
// 3. 獲取輸入流 (自動處理 206 響應)
InputStream in = conn.getInputStream();
// 4. 隨機寫入文件 (RandomAccessFile)
RandomAccessFile raf = new RandomAccessFile("down.zip", "rw");
raf.seek(startPos); // 定位文件指針
byte[] buffer = new byte[8192];
int len;
while ((len = in.read(buffer)) > 0) {
raf.write(buffer, 0, len);
}
| 類 | 職責 |
|---|---|
| SiteFileFetch | 主控制器:文件信息獲取、分片策略、線程編排、進度持久化 |
| FileSplitterFetch | 工作線程:單分片下載、斷點續傳邏輯、寫入協調 |
| FileAccessI | 文件寫入封裝:RandomAccessFile 線程安全寫入 |
| SiteInfoBean | 配置數據:URL、保存路徑、分片數 |
| Utility | 工具類:睡眠、日誌 |
long fileLength = getFileSize(); // HEAD 請求獲取 Content-Length
int parts = bean.getNSplitter(); // 默認 5 片
long partSize = fileLength / parts;
for (int i = 0; i < parts; i++) {
long start = i * partSize;
long end = (i == parts - 1) ? fileLength : (i + 1) * partSize;
nStartPos[i] = start;
nEndPos[i] = end;
}
while (nStartPos < nEndPos && !bStop) {
HttpURLConnection conn = (HttpURLConnection) new URL(sURL).openConnection();
conn.setRequestProperty("Range", "bytes=" + nStartPos + "-");
try (InputStream in = conn.getInputStream()) {
byte[] buf = new byte[1024];
int read;
while ((read = in.read(buf)) > 0 && nStartPos < nEndPos && !bStop) {
nStartPos += fileAccess.write(buf, 0, read); // 寫入並更新位置
}
}
bDownOver = true;
}
// 保存:主線程定期調用
DataOutputStream out = new DataOutputStream(new FileOutputStream(tmpFile));
out.writeInt(nStartPos.length);
for (int i = 0; i < nStartPos.length; i++) {
out.writeLong(fileSplitterFetch[i].nStartPos);
out.writeLong(fileSplitterFetch[i].nEndPos);
}
// 恢復:構造器中讀取
DataInputStream in = new DataInputStream(new FileInputStream(tmpFile));
int count = in.readInt();
nStartPos = new long[count];
nEndPos = new long[count];
for (int i = 0; i < count; i++) {
nStartPos[i] = in.readLong();
nEndPos[i] = in.readLong();
}
public class FileAccessI implements Serializable {
private final RandomAccessFile raf;
public FileAccessI(String fileName, long pos) throws IOException {
raf = new RandomAccessFile(fileName, "rw");
raf.seek(pos);
}
public synchronized int write(byte[] b, int off, int len) {
raf.write(b, off, len);
return len;
}
}
public class TestMethod {
public static void main(String[] args) {
SiteInfoBean bean = new SiteInfoBean(
"https://localhost/largefile.exe",
"D:/downloads",
"largefile.exe",
5 // 5 線程
);
new SiteFileFetch(bean).start();
}
}