斷點續傳的原理剖析與例項講解
斷點續傳的原理剖析與例項講解
本文所要講的是Android斷點續傳的內容,以例項的形式進行了詳細介紹。
一、斷點續傳的原理
其實斷點續傳的原理很簡單,就是在http的請求上和一般的下載有所不同而已。
打個比方,瀏覽器請求伺服器上的一個文時,所發出的請求如下:
假設伺服器域名為www.jizhuomi.com/android,檔名為down.zip。
-
get /down.zip http/1.1
-
accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-
-
excel, application/msword, application/vnd.ms-powerpoint, */*
-
accept-language: zh-cn
-
accept-encoding: gzip, deflate
-
user-agent: mozilla/4.0 (compatible; msie 5.01; windows nt 5.0)
-
connection: keep-alive
伺服器收到請求後,按要求尋找請求的檔案,提取檔案的資訊,然後返回給瀏覽器,返回資訊如下:
-
200
-
content-length=106786028
-
accept-ranges=bytes
-
date=mon, 30 apr 2001 12:56:11 gmt
-
etag=w/"02ca57e173c11:95b"
-
content-type=application/octet-stream
-
server=microsoft-iis/5.0
-
last-modified=mon, 30 apr 2001 12:56:11 gmt
所謂斷點續傳,也就是要從檔案已經下載的地方開始繼續下載。所以在客戶端瀏覽器傳給web伺服器的時候要多加一條資訊--從哪裡開始。
下面是用自己編的一個“瀏覽器”來傳遞請求資訊給web伺服器,要求從2000070位元組開始。
-
get /down.zip http/1.0
-
user-agent: netfox
-
range: bytes=2000070-
-
accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
仔細看一下就會發現多了一行 range: bytes=2000070-
這一行的意思就是告訴伺服器down.zip這個檔案從2000070位元組開始傳,前面的位元組不用傳了。
伺服器收到這個請求以後,返回的資訊如下:
-
206
-
content-length=106786028
-
content-range=bytes 2000070-106786027/106786028
-
date=mon, 30 apr 2001 12:55:20 gmt
-
etag=w/"02ca57e173c11:95b"
-
content-type=application/octet-stream
-
server=microsoft-iis/5.0
-
last-modified=mon, 30 apr 2001 12:55:20 gmt
和前面伺服器返回的資訊比較一下,就會發現增加了一行:
content-range=bytes 2000070-106786027/106786028
返回的程式碼也改為206了,而不再是200了。
知道了以上原理,就可以進行斷點續傳的程式設計了。
二、java實現斷點續傳的關鍵幾點
用什麼方法實現提交range: bytes=2000070-?
當然用最原始的socket是肯定能完成的,不過那樣太費事了,其實java的net包中提供了這種功能。程式碼如下:
-
Java程式碼
-
url url = new url("http://www.jizhuomi.com/android/down.zip");
-
httpurlconnection httpconnection = (httpurlconnection)url.openconnection();
-
//設定user-agent
-
httpconnection.setrequestproperty("user-agent","netfox");
-
//設定斷點續傳的開始位置
-
httpconnection.setrequestproperty("range","bytes=2000070");
-
//獲得輸入流
-
inputstream input = httpconnection.getinputstream();
從輸入流中取出的位元組流就是down.zip檔案從2000070開始的位元組流。
大家看,其實斷點續傳用java實現起來還是很簡單的吧。
接下來要做的事就是怎麼儲存獲得的流到檔案中去了。
儲存檔案採用的方法:我採用的是io包中的randaccessfile類。
操作相當簡單,假設從2000070處開始儲存檔案,程式碼如下:
-
Java程式碼
-
randomaccess osavedfile = new randomaccessfile("down.zip","rw");
-
long npos = 2000070;
-
//定位檔案指標到npos位置
-
osavedfile.seek(npos);
-
byte[] b = new byte[1024];
-
int nread;
-
//從輸入流中讀入位元組流,然後寫到檔案中
-
while((nread=input.read(b,0,1024)) > 0)
-
{
-
osavedfile.write(b,0,nread);
-
}
怎麼樣,也很簡單吧。
接下來要做的就是整合成一個完整的程式了。包括一系列的執行緒控制等等。
三、斷點續傳核心的實現
主要用了6個類,包括一個測試類。
sitefilefetch.java負責整個檔案的抓取,控制內部執行緒(filesplitterfetch類)。
filesplitterfetch.java負責部分檔案的抓取。
fileaccess.java負責檔案的儲存。
siteinfobean.java要抓取的檔案的資訊,如檔案儲存的目錄,名字,抓取檔案的url等。
utility.java工具類,放一些簡單的方法。
testmethod.java測試類。
四、例項原始碼
下面是源程式:
-
Java程式碼
-
/*
-
**sitefilefetch.java
-
*/
-
package netfox;
-
import java.io.*;
-
import java.net.*;
-
public class sitefilefetch extends thread {
-
siteinfobean siteinfobean = null; //檔案資訊bean
-
long[] nstartpos; //開始位置
-
long[] nendpos; //結束位置
-
filesplitterfetch[] filesplitterfetch; //子執行緒物件
-
long nfilelength; //檔案長度
-
boolean bfirst = true; //是否第一次取檔案
-
boolean bstop = false; //停止標誌
-
file tmpfile; //檔案下載的臨時資訊
-
dataoutputstream output; //輸出到檔案的輸出流
-
public sitefilefetch(siteinfobean bean) throws ioexception
-
{
-
siteinfobean = bean;
-
//tmpfile = file.createtempfile ("zhong","1111",new file(bean.getsfilepath()));
-
tmpfile = new file(bean.getsfilepath()+file.separator + bean.getsfilename()+".info");
-
if(tmpfile.exists ())
-
{
-
bfirst = false;
-
read_npos();
-
}
-
else
-
{
-
nstartpos = new long[bean.getnsplitter()];
-
nendpos = new long[bean.getnsplitter()];
-
}
-
}
-
public void run()
-
{
-
//獲得檔案長度
-
//分割檔案
-
//例項filesplitterfetch
-
//啟動filesplitterfetch執行緒
-
//等待子執行緒返回
-
try{
-
if(bfirst)
-
{
-
nfilelength = getfilesize();
-
if(nfilelength == -1)
-
{
-
system.err.println("file length is not known!");
-
}
-
else if(nfilelength == -2)
-
{
-
system.err.println("file is not access!");
-
}
-
else
-
{
-
for(int i=0;i<nstartpos.length;i++)
-
{
-
nstartpos = (long)(i*(nfilelength/nstartpos.length));
-
}
-
for(int i=0;i<nendpos.length-1;i++)
-
{
-
nendpos = nstartpos[i+1];
-
}
-
nendpos[nendpos.length-1] = nfilelength;
-
}
-
}
-
//啟動子執行緒
-
filesplitterfetch = new filesplitterfetch[nstartpos.length];
-
for(int i=0;i<nstartpos.length;i++)
-
{
-
filesplitterfetch = new filesplitterfetch(siteinfobean.getssiteurl(),
-
siteinfobean.getsfilepath() + file.separator + siteinfobean.getsfilename(),
-
nstartpos,nendpos,i);
-
utility.log("thread " + i + " , nstartpos = " + nstartpos + ", nendpos = " + nendpos);
-
filesplitterfetch.start();
-
}
-
// filesplitterfetch[npos.length-1] = new filesplitterfetch(siteinfobean.getssiteurl(),
-
siteinfobean.getsfilepath() + file.separator + siteinfobean.getsfilename(),npos[npos.length-1],nfilelength,npos.length-1);
-
// utility.log("thread " + (npos.length-1) + " , nstartpos = " + npos[npos.length-1] + ",
-
nendpos = " + nfilelength);
-
// filesplitterfetch[npos.length-1].start();
-
//等待子執行緒結束
-
//int count = 0;
-
//是否結束while迴圈
-
boolean breakwhile = false;
-
while(!bstop)
-
{
-
write_npos();
-
utility.sleep(500);
-
breakwhile = true;
-
for(int i=0;i<nstartpos.length;i++)
-
{
-
if(!filesplitterfetch.bdownover)
-
{
-
breakwhile = false;
-
break;
-
}
-
}
-
if(breakwhile)
-
break;
-
//count++;
-
//if(count>4)
-
// sitestop();
-
}
-
system.err.println("檔案下載結束!");
-
}
-
catch(exception e){e.printstacktrace ();}
-
}
-
//獲得檔案長度
-
public long getfilesize()
-
{
-
int nfilelength = -1;
-
try{
-
url url = new url(siteinfobean.getssiteurl());
-
httpurlconnection httpconnection = (httpurlconnection)url.openconnection ();
-
httpconnection.setrequestproperty("user-agent","netfox");
-
int responsecode=httpconnection.getresponsecode();
-
if(responsecode>=400)
-
{
-
processerrorcode(responsecode);
-
return -2; //-2 represent access is error
-
}
-
string sheader;
-
for(int i=1;;i++)
-
{
-
//datainputstream in = new datainputstream(httpconnection.getinputstream ());
-
//utility.log(in.readline());
-
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;
-
}
-
//儲存下載資訊(檔案指標位置)
-
private void write_npos()
-
{
-
try{
-
output = new dataoutputstream(new fileoutputstream(tmpfile));
-
output.writeint(nstartpos.length);
-
for(int i=0;i<nstartpos.length;i++)
-
{
-
// output.writelong(npos);
-
output.writelong(filesplitterfetch.nstartpos);
-
output.writelong(filesplitterfetch.nendpos);
-
}
-
output.close();
-
}
-
catch(ioexception e){e.printstacktrace ();}
-
catch(exception e){e.printstacktrace ();}
-
}
-
//讀取儲存的下載資訊(檔案指標位置)
-
private void read_npos()
-
{
-
try{
-
datainputstream input = new datainputstream(new fileinputstream(tmpfile));
-
int ncount = input.readint();
-
nstartpos = new long[ncount];
-
nendpos = new long[ncount];
-
for(int i=0;i<nstartpos.length;i++)
-
{
-
nstartpos = input.readlong();
-
nendpos = input.readlong();
-
}
-
input.close();
-
}
-
catch(ioexception e){e.printstacktrace ();}
-
catch(exception e){e.printstacktrace ();}
-
}
-
private void processerrorcode(int nerrorcode)
-
{
-
system.err.println("error code : " + nerrorcode);
-
}
-
//停止檔案下載
-
public void sitestop()
-
{
-
bstop = true;
-
for(int i=0;i<nstartpos.length;i++)
-
filesplitterfetch.splitterstop();
-
}
-
}
-
/*
-
**filesplitterfetch.java
-
*/
-
package netfox;
-
import java.io.*;
-
import java.net.*;
-
public class filesplitterfetch extends thread {
-
string surl; //file url
-
long nstartpos; //file snippet start position
-
long nendpos; //file snippet end position
-
int nthreadid; //threads id
-
boolean bdownover = false; //downing is over
-
boolean bstop = false; //stop identical
-
fileaccessi fileaccessi = null; //file access interface
-
public filesplitterfetch(string surl,string sname,long nstart,long nend,int id) throws ioexception
-
{
-
this.surl = surl;
-
this.nstartpos = nstart;
-
this.nendpos = nend;
-
nthreadid = id;
-
fileaccessi = new fileaccessi(sname,nstartpos);
-
}
-
public void run()
-
{
-
while(nstartpos < nendpos && !bstop)
-
{
-
try{
-
url url = new url(surl);
-
httpurlconnection httpconnection = (httpurlconnection)url.openconnection ();
-
httpconnection.setrequestproperty("user-agent","netfox");
-
string sproperty = "bytes="+nstartpos+"-";
-
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 && nstartpos < nendpos && !bstop)
-
{
-
nstartpos += fileaccessi.write(b,0,nread);
-
//if(nthreadid == 1)
-
// utility.log("nstartpos = " + nstartpos + ", nendpos = " + nendpos);
-
}
-
utility.log("thread " + nthreadid + " is over!");
-
bdownover = true;
-
//npos = fileaccessi.write (b,0,nread);
-
}
-
catch(exception e){e.printstacktrace ();}
-
}
-
}
-
//列印迴應的頭資訊
-
public void logresponsehead(httpurlconnection con)
-
{
-
for(int i=1;;i++)
-
{
-
string header=con.getheaderfieldkey(i);
-
if(header!=null)
-
//responseheaders.put(header,httpconnection.getheaderfield(header));
-
utility.log(header+" : "+con.getheaderfield(header));
-
else
-
break;
-
}
-
}
-
public void splitterstop()
-
{
-
bstop = true;
-
}
-
}
-
/*
-
**fileaccess.java
-
*/
-
package netfox;
-
import java.io.*;
-
public class fileaccessi implements serializable{
-
randomaccessfile osavedfile;
-
long npos;
-
public fileaccessi() throws ioexception
-
{
-
this("",0);
-
}
-
public fileaccessi(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;
-
}
-
}
-
/*
-
**siteinfobean.java
-
*/
-
package netfox;
-
public class siteinfobean {
-
private string ssiteurl; //sites url
-
private string sfilepath; //saved files path
-
private string sfilename; //saved files name
-
private int nsplitter; //count of splited downloading file
-
public siteinfobean()
-
{
-
//default value of nsplitter is 5
-
this("","","",5);
-
}
-
public siteinfobean(string surl,string spath,string sname,int nspiltter)
-
{
-
ssiteurl= surl;
-
sfilepath = spath;
-
sfilename = sname;
-
this.nsplitter = nspiltter;
-
}
-
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;
-
}
-
public int getnsplitter()
-
{
-
return nsplitter;
-
}
-
public void setnsplitter(int ncount)
-
{
-
nsplitter = ncount;
-
}
-
}
-
/*
-
**utility.java
-
*/
-
package netfox;
-
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);
-
}
-
}
-
/*
-
**testmethod.java
-
*/
-
package netfox;
-
public class testmethod {
-
public testmethod()
-
{ ///xx/weblogic60b2_win.exe
-
try{
-
siteinfobean bean = new siteinfobean("http://localhost/xx/weblogic60b2_win.exe","l:\\temp","weblogic60b2_win.exe",5);
-
//siteinfobean bean = new siteinfobean("http://localhost:8080/down.zip","l:\\temp","weblogic60b2_win.exe",5);
-
sitefilefetch filefetch = new sitefilefetch(bean);
-
filefetch.start();
-
}
-
catch(exception e){e.printstacktrace ();}
-
}
-
public static void main(string[] args)
-
{
-
new testmethod();
-
}
-
}