1. 程式人生 > >斷點續傳的原理剖析與例項講解

斷點續傳的原理剖析與例項講解

斷點續傳的原理剖析與例項講解

 

本文所要講的是Android斷點續傳的內容,以例項的形式進行了詳細介紹。

一、斷點續傳的原理

       其實斷點續傳的原理很簡單,就是在http的請求上和一般的下載有所不同而已。

       打個比方,瀏覽器請求伺服器上的一個文時,所發出的請求如下:

       假設伺服器域名為www.jizhuomi.com/android,檔名為down.zip。

 
  1. get /down.zip http/1.1

  2. accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-

  3. excel, application/msword, application/vnd.ms-powerpoint, */*

  4. accept-language: zh-cn

  5. accept-encoding: gzip, deflate

  6. user-agent: mozilla/4.0 (compatible; msie 5.01; windows nt 5.0)

  7. connection: keep-alive


       伺服器收到請求後,按要求尋找請求的檔案,提取檔案的資訊,然後返回給瀏覽器,返回資訊如下:

 
  1. 200

  2. content-length=106786028

  3. accept-ranges=bytes

  4. date=mon, 30 apr 2001 12:56:11 gmt

  5. etag=w/"02ca57e173c11:95b"

  6. content-type=application/octet-stream

  7. server=microsoft-iis/5.0

  8. last-modified=mon, 30 apr 2001 12:56:11 gmt



       所謂斷點續傳,也就是要從檔案已經下載的地方開始繼續下載。所以在客戶端瀏覽器傳給web伺服器的時候要多加一條資訊--從哪裡開始。

       下面是用自己編的一個“瀏覽器”來傳遞請求資訊給web伺服器,要求從2000070位元組開始。

 
  1. get /down.zip http/1.0

  2. user-agent: netfox

  3. range: bytes=2000070-

  4. accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2


       仔細看一下就會發現多了一行 range: bytes=2000070-

       這一行的意思就是告訴伺服器down.zip這個檔案從2000070位元組開始傳,前面的位元組不用傳了。

       伺服器收到這個請求以後,返回的資訊如下:

 
  1. 206

  2. content-length=106786028

  3. content-range=bytes 2000070-106786027/106786028

  4. date=mon, 30 apr 2001 12:55:20 gmt

  5. etag=w/"02ca57e173c11:95b"

  6. content-type=application/octet-stream

  7. server=microsoft-iis/5.0

  8. 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包中提供了這種功能。程式碼如下:

 
  1. Java程式碼

  2. url url = new url("http://www.jizhuomi.com/android/down.zip");

  3. httpurlconnection httpconnection = (httpurlconnection)url.openconnection();

  4. //設定user-agent

  5. httpconnection.setrequestproperty("user-agent","netfox");

  6. //設定斷點續傳的開始位置

  7. httpconnection.setrequestproperty("range","bytes=2000070");

  8. //獲得輸入流

  9. inputstream input = httpconnection.getinputstream();


       從輸入流中取出的位元組流就是down.zip檔案從2000070開始的位元組流。

       大家看,其實斷點續傳用java實現起來還是很簡單的吧。

       接下來要做的事就是怎麼儲存獲得的流到檔案中去了。

       儲存檔案採用的方法:我採用的是io包中的randaccessfile類。

       操作相當簡單,假設從2000070處開始儲存檔案,程式碼如下:

 
  1. Java程式碼

  2. randomaccess osavedfile = new randomaccessfile("down.zip","rw");

  3. long npos = 2000070;

  4. //定位檔案指標到npos位置

  5. osavedfile.seek(npos);

  6. byte[] b = new byte[1024];

  7. int nread;

  8. //從輸入流中讀入位元組流,然後寫到檔案中

  9. while((nread=input.read(b,0,1024)) > 0)

  10. {

  11. osavedfile.write(b,0,nread);

  12. }


       怎麼樣,也很簡單吧。

       接下來要做的就是整合成一個完整的程式了。包括一系列的執行緒控制等等。

       三、斷點續傳核心的實現

       主要用了6個類,包括一個測試類。

       sitefilefetch.java負責整個檔案的抓取,控制內部執行緒(filesplitterfetch類)。

       filesplitterfetch.java負責部分檔案的抓取。

       fileaccess.java負責檔案的儲存。

       siteinfobean.java要抓取的檔案的資訊,如檔案儲存的目錄,名字,抓取檔案的url等。

       utility.java工具類,放一些簡單的方法。

       testmethod.java測試類。

       四、例項原始碼

       下面是源程式:

 
  1. Java程式碼

  2. /*

  3. **sitefilefetch.java

  4. */

  5. package netfox;

  6. import java.io.*;

  7. import java.net.*;

  8.  
  9. public class sitefilefetch extends thread {

  10.  
  11. siteinfobean siteinfobean = null; //檔案資訊bean

  12. long[] nstartpos; //開始位置

  13. long[] nendpos; //結束位置

  14. filesplitterfetch[] filesplitterfetch; //子執行緒物件

  15. long nfilelength; //檔案長度

  16. boolean bfirst = true; //是否第一次取檔案

  17. boolean bstop = false; //停止標誌

  18. file tmpfile; //檔案下載的臨時資訊

  19. dataoutputstream output; //輸出到檔案的輸出流

  20.  
  21. public sitefilefetch(siteinfobean bean) throws ioexception

  22. {

  23. siteinfobean = bean;

  24. //tmpfile = file.createtempfile ("zhong","1111",new file(bean.getsfilepath()));

  25. tmpfile = new file(bean.getsfilepath()+file.separator + bean.getsfilename()+".info");

  26. if(tmpfile.exists ())

  27. {

  28. bfirst = false;

  29. read_npos();

  30. }

  31. else

  32. {

  33. nstartpos = new long[bean.getnsplitter()];

  34. nendpos = new long[bean.getnsplitter()];

  35. }

  36. }

  37.  
  38. public void run()

  39. {

  40. //獲得檔案長度

  41. //分割檔案

  42. //例項filesplitterfetch

  43. //啟動filesplitterfetch執行緒

  44. //等待子執行緒返回

  45. try{

  46. if(bfirst)

  47. {

  48. nfilelength = getfilesize();

  49. if(nfilelength == -1)

  50. {

  51. system.err.println("file length is not known!");

  52. }

  53. else if(nfilelength == -2)

  54. {

  55. system.err.println("file is not access!");

  56. }

  57. else

  58. {

  59. for(int i=0;i<nstartpos.length;i++)

  60. {

  61. nstartpos = (long)(i*(nfilelength/nstartpos.length));

  62. }

  63. for(int i=0;i<nendpos.length-1;i++)

  64. {

  65. nendpos = nstartpos[i+1];

  66. }

  67. nendpos[nendpos.length-1] = nfilelength;

  68. }

  69. }

  70. //啟動子執行緒

  71. filesplitterfetch = new filesplitterfetch[nstartpos.length];

  72. for(int i=0;i<nstartpos.length;i++)

  73. {

  74. filesplitterfetch = new filesplitterfetch(siteinfobean.getssiteurl(),

  75. siteinfobean.getsfilepath() + file.separator + siteinfobean.getsfilename(),

  76. nstartpos,nendpos,i);

  77. utility.log("thread " + i + " , nstartpos = " + nstartpos + ", nendpos = " + nendpos);

  78. filesplitterfetch.start();

  79. }

  80. // filesplitterfetch[npos.length-1] = new filesplitterfetch(siteinfobean.getssiteurl(),

  81. siteinfobean.getsfilepath() + file.separator + siteinfobean.getsfilename(),npos[npos.length-1],nfilelength,npos.length-1);

  82. // utility.log("thread " + (npos.length-1) + " , nstartpos = " + npos[npos.length-1] + ",

  83. nendpos = " + nfilelength);

  84. // filesplitterfetch[npos.length-1].start();

  85.  
  86. //等待子執行緒結束

  87. //int count = 0;

  88. //是否結束while迴圈

  89. boolean breakwhile = false;

  90.  
  91. while(!bstop)

  92. {

  93. write_npos();

  94. utility.sleep(500);

  95. breakwhile = true;

  96.  
  97. for(int i=0;i<nstartpos.length;i++)

  98. {

  99. if(!filesplitterfetch.bdownover)

  100. {

  101. breakwhile = false;

  102. break;

  103. }

  104. }

  105. if(breakwhile)

  106. break;

  107.  
  108. //count++;

  109. //if(count>4)

  110. // sitestop();

  111. }

  112.  
  113. system.err.println("檔案下載結束!");

  114. }

  115. catch(exception e){e.printstacktrace ();}

  116. }

  117.  
  118. //獲得檔案長度

  119. public long getfilesize()

  120. {

  121. int nfilelength = -1;

  122. try{

  123. url url = new url(siteinfobean.getssiteurl());

  124. httpurlconnection httpconnection = (httpurlconnection)url.openconnection ();

  125. httpconnection.setrequestproperty("user-agent","netfox");

  126.  
  127. int responsecode=httpconnection.getresponsecode();

  128. if(responsecode>=400)

  129. {

  130. processerrorcode(responsecode);

  131. return -2; //-2 represent access is error

  132. }

  133.  
  134. string sheader;

  135.  
  136. for(int i=1;;i++)

  137. {

  138. //datainputstream in = new datainputstream(httpconnection.getinputstream ());

  139. //utility.log(in.readline());

  140. sheader=httpconnection.getheaderfieldkey(i);

  141. if(sheader!=null)

  142. {

  143. if(sheader.equals("content-length"))

  144. {

  145. nfilelength = integer.parseint(httpconnection.getheaderfield(sheader));

  146. break;

  147. }

  148. }

  149. else

  150. break;

  151. }

  152. }

  153. catch(ioexception e){e.printstacktrace ();}

  154. catch(exception e){e.printstacktrace ();}

  155.  
  156. utility.log(nfilelength);

  157.  
  158. return nfilelength;

  159. }

  160.  
  161. //儲存下載資訊(檔案指標位置)

  162. private void write_npos()

  163. {

  164. try{

  165. output = new dataoutputstream(new fileoutputstream(tmpfile));

  166. output.writeint(nstartpos.length);

  167. for(int i=0;i<nstartpos.length;i++)

  168. {

  169. // output.writelong(npos);

  170. output.writelong(filesplitterfetch.nstartpos);

  171. output.writelong(filesplitterfetch.nendpos);

  172. }

  173. output.close();

  174. }

  175. catch(ioexception e){e.printstacktrace ();}

  176. catch(exception e){e.printstacktrace ();}

  177. }

  178.  
  179. //讀取儲存的下載資訊(檔案指標位置)

  180. private void read_npos()

  181. {

  182. try{

  183. datainputstream input = new datainputstream(new fileinputstream(tmpfile));

  184. int ncount = input.readint();

  185. nstartpos = new long[ncount];

  186. nendpos = new long[ncount];

  187. for(int i=0;i<nstartpos.length;i++)

  188. {

  189. nstartpos = input.readlong();

  190. nendpos = input.readlong();

  191. }

  192. input.close();

  193. }

  194. catch(ioexception e){e.printstacktrace ();}

  195. catch(exception e){e.printstacktrace ();}

  196. }

  197.  
  198. private void processerrorcode(int nerrorcode)

  199. {

  200. system.err.println("error code : " + nerrorcode);

  201. }

  202.  
  203. //停止檔案下載

  204. public void sitestop()

  205. {

  206. bstop = true;

  207. for(int i=0;i<nstartpos.length;i++)

  208. filesplitterfetch.splitterstop();

  209. }

  210. }

  211. /*

  212. **filesplitterfetch.java

  213. */

  214. package netfox;

  215.  
  216. import java.io.*;

  217. import java.net.*;

  218.  
  219. public class filesplitterfetch extends thread {

  220.  
  221. string surl; //file url

  222. long nstartpos; //file snippet start position

  223. long nendpos; //file snippet end position

  224. int nthreadid; //threads id

  225. boolean bdownover = false; //downing is over

  226. boolean bstop = false; //stop identical

  227. fileaccessi fileaccessi = null; //file access interface

  228.  
  229. public filesplitterfetch(string surl,string sname,long nstart,long nend,int id) throws ioexception

  230. {

  231. this.surl = surl;

  232. this.nstartpos = nstart;

  233. this.nendpos = nend;

  234. nthreadid = id;

  235. fileaccessi = new fileaccessi(sname,nstartpos);

  236. }

  237.  
  238. public void run()

  239. {

  240. while(nstartpos < nendpos && !bstop)

  241. {

  242. try{

  243. url url = new url(surl);

  244. httpurlconnection httpconnection = (httpurlconnection)url.openconnection ();

  245. httpconnection.setrequestproperty("user-agent","netfox");

  246. string sproperty = "bytes="+nstartpos+"-";

  247. httpconnection.setrequestproperty("range",sproperty);

  248. utility.log(sproperty);

  249.  
  250. inputstream input = httpconnection.getinputstream();

  251. //logresponsehead(httpconnection);

  252.  
  253. byte[] b = new byte[1024];

  254. int nread;

  255. while((nread=input.read(b,0,1024)) > 0 && nstartpos < nendpos && !bstop)

  256. {

  257. nstartpos += fileaccessi.write(b,0,nread);

  258. //if(nthreadid == 1)

  259. // utility.log("nstartpos = " + nstartpos + ", nendpos = " + nendpos);

  260. }

  261.  
  262. utility.log("thread " + nthreadid + " is over!");

  263. bdownover = true;

  264. //npos = fileaccessi.write (b,0,nread);

  265. }

  266. catch(exception e){e.printstacktrace ();}

  267. }

  268. }

  269.  
  270. //列印迴應的頭資訊

  271. public void logresponsehead(httpurlconnection con)

  272. {

  273. for(int i=1;;i++)

  274. {

  275. string header=con.getheaderfieldkey(i);

  276. if(header!=null)

  277. //responseheaders.put(header,httpconnection.getheaderfield(header));

  278. utility.log(header+" : "+con.getheaderfield(header));

  279. else

  280. break;

  281. }

  282. }

  283.  
  284. public void splitterstop()

  285. {

  286. bstop = true;

  287. }

  288. }

  289.  
  290. /*

  291. **fileaccess.java

  292. */

  293. package netfox;

  294. import java.io.*;

  295.  
  296. public class fileaccessi implements serializable{

  297.  
  298. randomaccessfile osavedfile;

  299. long npos;

  300.  
  301. public fileaccessi() throws ioexception

  302. {

  303. this("",0);

  304. }

  305.  
  306. public fileaccessi(string sname,long npos) throws ioexception

  307. {

  308. osavedfile = new randomaccessfile(sname,"rw");

  309. this.npos = npos;

  310. osavedfile.seek(npos);

  311. }

  312.  
  313. public synchronized int write(byte[] b,int nstart,int nlen)

  314. {

  315. int n = -1;

  316. try{

  317. osavedfile.write(b,nstart,nlen);

  318. n = nlen;

  319. }

  320. catch(ioexception e)

  321. {

  322. e.printstacktrace ();

  323. }

  324.  
  325. return n;

  326. }

  327. }

  328.  
  329. /*

  330. **siteinfobean.java

  331. */

  332. package netfox;

  333.  
  334. public class siteinfobean {

  335.  
  336. private string ssiteurl; //sites url

  337. private string sfilepath; //saved files path

  338. private string sfilename; //saved files name

  339. private int nsplitter; //count of splited downloading file

  340.  
  341. public siteinfobean()

  342. {

  343. //default value of nsplitter is 5

  344. this("","","",5);

  345. }

  346.  
  347. public siteinfobean(string surl,string spath,string sname,int nspiltter)

  348. {

  349. ssiteurl= surl;

  350. sfilepath = spath;

  351. sfilename = sname;

  352. this.nsplitter = nspiltter;

  353. }

  354.  
  355. public string getssiteurl()

  356. {

  357. return ssiteurl;

  358. }

  359.  
  360. public void setssiteurl(string value)

  361. {

  362. ssiteurl = value;

  363. }

  364.  
  365. public string getsfilepath()

  366. {

  367. return sfilepath;

  368. }

  369.  
  370. public void setsfilepath(string value)

  371. {

  372. sfilepath = value;

  373. }

  374.  
  375. public string getsfilename()

  376. {

  377. return sfilename;

  378. }

  379.  
  380. public void setsfilename(string value)

  381. {

  382. sfilename = value;

  383. }

  384.  
  385. public int getnsplitter()

  386. {

  387. return nsplitter;

  388. }

  389.  
  390. public void setnsplitter(int ncount)

  391. {

  392. nsplitter = ncount;

  393. }

  394. }

  395.  
  396. /*

  397. **utility.java

  398. */

  399. package netfox;

  400.  
  401. public class utility {

  402.  
  403. public utility()

  404. {

  405. }

  406.  
  407. public static void sleep(int nsecond)

  408. {

  409. try{

  410. thread.sleep(nsecond);

  411. }

  412. catch(exception e)

  413. {

  414. e.printstacktrace ();

  415. }

  416. }

  417.  
  418. public static void log(string smsg)

  419. {

  420. system.err.println(smsg);

  421. }

  422.  
  423. public static void log(int smsg)

  424. {

  425. system.err.println(smsg);

  426. }

  427. }

  428.  
  429. /*

  430. **testmethod.java

  431. */

  432. package netfox;

  433.  
  434. public class testmethod {

  435.  
  436. public testmethod()

  437. { ///xx/weblogic60b2_win.exe

  438. try{

  439. siteinfobean bean = new siteinfobean("http://localhost/xx/weblogic60b2_win.exe","l:\\temp","weblogic60b2_win.exe",5);

  440. //siteinfobean bean = new siteinfobean("http://localhost:8080/down.zip","l:\\temp","weblogic60b2_win.exe",5);

  441. sitefilefetch filefetch = new sitefilefetch(bean);

  442. filefetch.start();

  443. }

  444. catch(exception e){e.printstacktrace ();}

  445. }

  446.  
  447. public static void main(string[] args)

  448. {

  449. new testmethod();

  450. }

  451. }