断点续传的原理剖析与实例讲解

本文深入解析断点续传的原理,通过HTTP请求的Range头实现文件的分段下载。使用Java的HttpURLConnection设置Range头,结合RandomAccessFile类实现文件的续传与保存,提供了一个多线程断点续传的完整示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

断点续传的原理剖析与实例讲解

 

本文所要讲的是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. }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值