參考文章:https://www.ibm.com/developerworks/cn/java/joy-down/
1.原理介紹
想象一下我們下載一個10G的文件,當下載到9.99G的時候斷網了。。。
斷點續傳支持從文件上次中斷的地方開始傳送數據,而並非是從文件開頭傳送。
體現在HTTP請求和響應上就會有區別:
假設下載文件爲:
http://dl_dir.qq.com/invc/cyclone/QQDownload_Setup_39_708_p1.exe
普通文件下載的請求和響應
斷點續傳時候的請求和響應
這地方需要攔截HttpURLConnection的請求。
2.如何用Java實現
主要利用的是RandomAccessFile類的seek方法。
RandomAccessFile的特點在於任意訪問文件的任意位置,是基於字節訪問的;
可通過getFilePointer()獲取當前指針所在位置 ;
可通過seek()移動指針,這體現了它的任意性;
seek用於設置文件指針位置,設置後會從當前指針的下一位讀取或寫入。
這地方我在IBM文章基礎上簡化並調整了一版,自測了下。搬上來分享一下。
2.1 工具類
package com.laoxu.demo.breaktransfer;
public class Utility {
public Utility()
{
}
public static void sleep(int nSecond)
{
try{
Thread.sleep(nSecond);
}
catch(Exception e)
{
e.printStackTrace ();
}
}
public static void log(String sMsg)
{
System.err.println(sMsg);
}
public static void log(int sMsg)
{
System.err.println(sMsg);
}
}
2.2 文件信息實體
package com.laoxu.demo.breaktransfer;
/**
* 遠程下載文件信息
*/
public class SiteFileInfo {
private String sSiteURL; //文件下載鏈接
private String sFilePath; //保存的文件路徑
private String sFileName; //保存的文件名稱
public SiteFileInfo()
{
this("","","");
}
public SiteFileInfo(String sURL, String sPath, String sName)
{
sSiteURL= sURL;
sFilePath = sPath;
sFileName = sName;
}
public String getSSiteURL()
{
return sSiteURL;
}
public void setSSiteURL(String value)
{
sSiteURL = value;
}
public String getSFilePath()
{
return sFilePath;
}
public void setSFilePath(String value)
{
sFilePath = value;
}
public String getSFileName()
{
return sFileName;
}
public void setSFileName(String value)
{
sFileName = value;
}
}
2.3 寫文件類
package com.laoxu.demo.breaktransfer;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.Serializable;
/**
* 寫文件
*/
public class FileAccess implements Serializable{
RandomAccessFile oSavedFile;
long nPos;
public FileAccess() throws IOException
{
this("",0);
}
public FileAccess(String sName, long nPos) throws IOException
{
oSavedFile = new RandomAccessFile(sName,"rw");
this.nPos = nPos;
oSavedFile.seek(nPos);
}
public synchronized int write(byte[] b,int nStart,int nLen)
{
int n = -1;
try{
oSavedFile.write(b,nStart,nLen);
n = nLen;
}
catch(IOException e)
{
e.printStackTrace ();
}
return n;
}
}
2.4 主程序
package com.laoxu.demo.breaktransfer;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
/**
* @Description: 斷點續傳主程序
* @Author laoxu
* @Date 2019/12/7 15:31
**/
public class SiteFileFetch{
SiteFileInfo fileInfo = null; // 文件信息實體
long startPos; // 開始位置
long endPos; // 結束位置
long fileLength; // 文件長度
File localFile = null; // 保存的文件
String localFilePath; // 本地文件路徑
boolean bFirst = true; // 是否第一次取文件
boolean bStop = false; // 停止標誌
public SiteFileFetch(SiteFileInfo entity){
fileInfo = entity;
localFilePath = entity.getSFilePath()+File.separator+entity.getSFileName();
localFile = new File(localFilePath);
// 判斷本地文件是否存在
if(localFile.exists()){
startPos = localFile.length();
bFirst = false;
}
fileLength = getFileSize();
if(fileLength < 0){
System.err.println("非法文件!");
}else{
endPos = fileLength;
}
}
public void download() {
if(startPos<endPos){
try {
FileAccess fileAccess = new FileAccess(localFilePath,startPos);
// 配置fiddler代理
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8888));
URL url = new URL(fileInfo.getSSiteURL());
HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection(proxy);
httpConnection.setRequestProperty("User-Agent", "LX");
String sProperty = "bytes=" + startPos + "-";
httpConnection.setRequestProperty("RANGE", sProperty);
Utility.log(sProperty);
InputStream input = httpConnection.getInputStream();
//logResponseHead(httpConnection);
byte[] b = new byte[1024];
int nRead;
while ((nRead = input.read(b, 0, 1024)) > 0 && startPos < endPos
&& !bStop) {
startPos += fileAccess.write(b, 0, nRead);
}
} catch (IOException e) {
e.printStackTrace();
}
}else {
System.out.println("文件已存在!");
}
}
// 獲得文件長度
private long getFileSize() {
int nFileLength = -1;
try {
URL url = new URL(fileInfo.getSSiteURL());
HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
httpConnection.setRequestProperty("User-Agent", "Lx");
int responseCode = httpConnection.getResponseCode();
if (responseCode >= 400) {
System.out.println(responseCode);
return -2; //-2 represent access is error
}
String sHeader;
for (int i = 1; ; i++) {
sHeader = httpConnection.getHeaderFieldKey(i);
if (sHeader != null) {
if (sHeader.equals("Content-Length")) {
nFileLength = Integer.parseInt(httpConnection.getHeaderField(sHeader));
break;
}
} else
break;
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
Utility.log(nFileLength);
return nFileLength;
}
}
2.5 測試程序
package com.laoxu.demo.breaktransfer;
public class TestMethod {
public TestMethod() {
try {
SiteFileInfo fileInfo = new SiteFileInfo("http://dl_dir.qq.com/invc/cyclone/QQDownload_Setup_39_708_p1.exe",
"D:\\tmp",
"QQDownload_Setup_39_708_p1.exe");
SiteFileInfo fileInfo2 = new SiteFileInfo("http://www.luohanye.com/astros.json",
"D:\\tmp",
"astros.json");
SiteFileFetch fileFetch = new SiteFileFetch(fileInfo);
fileFetch.download();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new TestMethod();
}
}