断点续传的原理

其实断点续传的原理很简单,就是在 Http 的请求上和一般的下载有所不同而已。 
打个比方,浏览器请求服务器上的一个文时,所发出的请求如下: 
假设服务器域名为 wwww.sjtu.edu.cn,文件名为 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 实现断点续传的关键几点

  1. (1) 用什么方法实现提交 RANGE: bytes=2000070-。 
    当然用最原始的 Socket 是肯定能完成的,不过那样太费事了,其实 Java 的 net 包中提供了这种功能。代码如下:

    URL url = new URL("http://www.sjtu.edu.cn/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 实现起来还是很简单的吧。 接下来要做的事就是怎么保存获得的流到文件中去了。

  2. 保存文件采用的方法。 
    我采用的是 IO 包中的 RandAccessFile 类。 
    操作相当简单,假设从 2000070 处开始保存文件,代码如下: 
    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 测试类。

下面是源程序:

  1. /*
  2. /*
  3. * SiteFileFetch.java
  4. */
  5. package NetFox;
  6. import java.io.*;
  7. import java.net.*;
  8. public class SiteFileFetch extends Thread {
  9. SiteInfoBean siteInfoBean = null; // 文件信息 Bean
  10. long[] nStartPos; // 开始位置
  11. long[] nEndPos; // 结束位置
  12. FileSplitterFetch[] fileSplitterFetch; // 子线程对象
  13. long nFileLength; // 文件长度
  14. boolean bFirst = true; // 是否第一次取文件
  15. boolean bStop = false; // 停止标志
  16. File tmpFile; // 文件下载的临时信息
  17. DataOutputStream output; // 输出到文件的输出流
  18. public SiteFileFetch(SiteInfoBean bean) throws IOException
  19. {
  20. siteInfoBean = bean;
  21. //tmpFile = File.createTempFile ("zhong","1111",new File(bean.getSFilePath()));
  22. tmpFile = new File(bean.getSFilePath()+File.separator + bean.getSFileName()+".info");
  23. if(tmpFile.exists ())
  24. {
  25. bFirst = false;
  26. read_nPos();
  27. }
  28. else
  29. {
  30. nStartPos = new long[bean.getNSplitter()];
  31. nEndPos = new long[bean.getNSplitter()];
  32. }
  33. }
  34. public void run()
  35. {
  36. // 获得文件长度
  37. // 分割文件
  38. // 实例 FileSplitterFetch
  39. // 启动 FileSplitterFetch 线程
  40. // 等待子线程返回
  41. try{
  42. if(bFirst)
  43. {
  44. nFileLength = getFileSize();
  45. if(nFileLength == -1)
  46. {
  47. System.err.println("File Length is not known!");
  48. }
  49. else if(nFileLength == -2)
  50. {
  51. System.err.println("File is not access!");
  52. }
  53. else
  54. {
  55. for(int i=0;i<nStartPos.length;i++)
  56. {
  57. nStartPos[i] = (long)(i*(nFileLength/nStartPos.length));
  58. }
  59. for(int i=0;i<nEndPos.length-1;i++)
  60. {
  61. nEndPos[i] = nStartPos[i+1];
  62. }
  63. nEndPos[nEndPos.length-1] = nFileLength;
  64. }
  65. }
  66. // 启动子线程
  67. fileSplitterFetch = new FileSplitterFetch[nStartPos.length];
  68. for(int i=0;i<nStartPos.length;i++)
  69. {
  70. fileSplitterFetch[i] = new FileSplitterFetch(siteInfoBean.getSSiteURL(),
  71. siteInfoBean.getSFilePath() + File.separator + siteInfoBean.getSFileName(),
  72. nStartPos[i],nEndPos[i],i);
  73. Utility.log("Thread " + i + " , nStartPos = " + nStartPos[i] + ", nEndPos = "
  74. + nEndPos[i]);
  75. fileSplitterFetch[i].start();
  76. }
  77. // fileSplitterFetch[nPos.length-1] = new FileSplitterFetch(siteInfoBean.getSSiteURL(),
  78. siteInfoBean.getSFilePath() + File.separator
  79. + siteInfoBean.getSFileName(),nPos[nPos.length-1],nFileLength,nPos.length-1);
  80. // Utility.log("Thread " +(nPos.length-1) + ",nStartPos = "+nPos[nPos.length-1]+",
  81. nEndPos = " + nFileLength);
  82. // fileSplitterFetch[nPos.length-1].start();
  83. // 等待子线程结束
  84. //int count = 0;
  85. // 是否结束 while 循环
  86. boolean breakWhile = false;
  87. while(!bStop)
  88. {
  89. write_nPos();
  90. Utility.sleep(500);
  91. breakWhile = true;
  92. for(int i=0;i<nStartPos.length;i++)
  93. {
  94. if(!fileSplitterFetch[i].bDownOver)
  95. {
  96. breakWhile = false;
  97. break;
  98. }
  99. }
  100. if(breakWhile)
  101. break;
  102. //count++;
  103. //if(count>4)
  104. // siteStop();
  105. }
  106. System.err.println("文件下载结束!");
  107. }
  108. catch(Exception e){e.printStackTrace ();}
  109. }
  110. // 获得文件长度
  111. public long getFileSize()
  112. {
  113. int nFileLength = -1;
  114. try{
  115. URL url = new URL(siteInfoBean.getSSiteURL());
  116. HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection ();
  117. httpConnection.setRequestProperty("User-Agent","NetFox");
  118. int responseCode=httpConnection.getResponseCode();
  119. if(responseCode>=400)
  120. {
  121. processErrorCode(responseCode);
  122. return -2; //-2 represent access is error
  123. }
  124. String sHeader;
  125. for(int i=1;;i++)
  126. {
  127. //DataInputStream in = new DataInputStream(httpConnection.getInputStream ());
  128. //Utility.log(in.readLine());
  129. sHeader=httpConnection.getHeaderFieldKey(i);
  130. if(sHeader!=null)
  131. {
  132. if(sHeader.equals("Content-Length"))
  133. {
  134. nFileLength = Integer.parseInt(httpConnection.getHeaderField(sHeader));
  135. break;
  136. }
  137. }
  138. else
  139. break;
  140. }
  141. }
  142. catch(IOException e){e.printStackTrace ();}
  143. catch(Exception e){e.printStackTrace ();}
  144. Utility.log(nFileLength);
  145. return nFileLength;
  146. }
  147. // 保存下载信息(文件指针位置)
  148. private void write_nPos()
  149. {
  150. try{
  151. output = new DataOutputStream(new FileOutputStream(tmpFile));
  152. output.writeInt(nStartPos.length);
  153. for(int i=0;i<nStartPos.length;i++)
  154. {
  155. // output.writeLong(nPos[i]);
  156. output.writeLong(fileSplitterFetch[i].nStartPos);
  157. output.writeLong(fileSplitterFetch[i].nEndPos);
  158. }
  159. output.close();
  160. }
  161. catch(IOException e){e.printStackTrace ();}
  162. catch(Exception e){e.printStackTrace ();}
  163. }
  164. // 读取保存的下载信息(文件指针位置)
  165. private void read_nPos()
  166. {
  167. try{
  168. DataInputStream input = new DataInputStream(new FileInputStream(tmpFile));
  169. int nCount = input.readInt();
  170. nStartPos = new long[nCount];
  171. nEndPos = new long[nCount];
  172. for(int i=0;i<nStartPos.length;i++)
  173. {
  174. nStartPos[i] = input.readLong();
  175. nEndPos[i] = input.readLong();
  176. }
  177. input.close();
  178. }
  179. catch(IOException e){e.printStackTrace ();}
  180. catch(Exception e){e.printStackTrace ();}
  181. }
  182. private void processErrorCode(int nErrorCode)
  183. {
  184. System.err.println("Error Code : " + nErrorCode);
  185. }
  186. // 停止文件下载
  187. public void siteStop()
  188. {
  189. bStop = true;
  190. for(int i=0;i<nStartPos.length;i++)
  191. fileSplitterFetch[i].splitterStop();
  192. }
  193. }
  1. /*
  2. **FileSplitterFetch.java
  3. */
  4. package NetFox;
  5. import java.io.*;
  6. import java.net.*;
  7. public class FileSplitterFetch extends Thread {
  8. String sURL; //File URL
  9. long nStartPos; //File Snippet Start Position
  10. long nEndPos; //File Snippet End Position
  11. int nThreadID; //Thread's ID
  12. boolean bDownOver = false; //Downing is over
  13. boolean bStop = false; //Stop identical
  14. FileAccessI fileAccessI = null; //File Access interface
  15. public FileSplitterFetch(String sURL,String sName,long nStart,long nEnd,int id)
  16. throws IOException
  17. {
  18. this.sURL = sURL;
  19. this.nStartPos = nStart;
  20. this.nEndPos = nEnd;
  21. nThreadID = id;
  22. fileAccessI = new FileAccessI(sName,nStartPos);
  23. }
  24. public void run()
  25. {
  26. while(nStartPos < nEndPos && !bStop)
  27. {
  28. try{
  29. URL url = new URL(sURL);
  30. HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection ();
  31. httpConnection.setRequestProperty("User-Agent","NetFox");
  32. String sProperty = "bytes="+nStartPos+"-";
  33. httpConnection.setRequestProperty("RANGE",sProperty);
  34. Utility.log(sProperty);
  35. InputStream input = httpConnection.getInputStream();
  36. //logResponseHead(httpConnection);
  37. byte[] b = new byte[1024];
  38. int nRead;
  39. while((nRead=input.read(b,0,1024)) > 0 && nStartPos < nEndPos
  40. && !bStop)
  41. {
  42. nStartPos += fileAccessI.write(b,0,nRead);
  43. //if(nThreadID == 1)
  44. // Utility.log("nStartPos = " + nStartPos + ", nEndPos = " + nEndPos);
  45. }
  46. Utility.log("Thread " + nThreadID + " is over!");
  47. bDownOver = true;
  48. //nPos = fileAccessI.write (b,0,nRead);
  49. }
  50. catch(Exception e){e.printStackTrace ();}
  51. }
  52. }
  53. // 打印回应的头信息
  54. public void logResponseHead(HttpURLConnection con)
  55. {
  56. for(int i=1;;i++)
  57. {
  58. String header=con.getHeaderFieldKey(i);
  59. if(header!=null)
  60. //responseHeaders.put(header,httpConnection.getHeaderField(header));
  61. Utility.log(header+" : "+con.getHeaderField(header));
  62. else
  63. break;
  64. }
  65. }
  66. public void splitterStop()
  67. {
  68. bStop = true;
  69. }
  70. }
  71.  
  72. /*
  73. **FileAccess.java
  74. */
  75. package NetFox;
  76. import java.io.*;
  77. public class FileAccessI implements Serializable{
  78. RandomAccessFile oSavedFile;
  79. long nPos;
  80. public FileAccessI() throws IOException
  81. {
  82. this("",0);
  83. }
  84. public FileAccessI(String sName,long nPos) throws IOException
  85. {
  86. oSavedFile = new RandomAccessFile(sName,"rw");
  87. this.nPos = nPos;
  88. oSavedFile.seek(nPos);
  89. }
  90. public synchronized int write(byte[] b,int nStart,int nLen)
  91. {
  92. int n = -1;
  93. try{
  94. oSavedFile.write(b,nStart,nLen);
  95. n = nLen;
  96. }
  97. catch(IOException e)
  98. {
  99. e.printStackTrace ();
  100. }
  101. return n;
  102. }
  103. }
  104.  
  105. /*
  106. **SiteInfoBean.java
  107. */
  108. package NetFox;
  109. public class SiteInfoBean {
  110. private String sSiteURL; //Site's URL
  111. private String sFilePath; //Saved File's Path
  112. private String sFileName; //Saved File's Name
  113. private int nSplitter; //Count of Splited Downloading File
  114. public SiteInfoBean()
  115. {
  116. //default value of nSplitter is 5
  117. this("","","",5);
  118. }
  119. public SiteInfoBean(String sURL,String sPath,String sName,int nSpiltter)
  120. {
  121. sSiteURL= sURL;
  122. sFilePath = sPath;
  123. sFileName = sName;
  124. this.nSplitter = nSpiltter;
  125. }
  126. public String getSSiteURL()
  127. {
  128. return sSiteURL;
  129. }
  130. public void setSSiteURL(String value)
  131. {
  132. sSiteURL = value;
  133. }
  134. public String getSFilePath()
  135. {
  136. return sFilePath;
  137. }
  138. public void setSFilePath(String value)
  139. {
  140. sFilePath = value;
  141. }
  142. public String getSFileName()
  143. {
  144. return sFileName;
  145. }
  146. public void setSFileName(String value)
  147. {
  148. sFileName = value;
  149. }
  150. public int getNSplitter()
  151. {
  152. return nSplitter;
  153. }
  154. public void setNSplitter(int nCount)
  155. {
  156. nSplitter = nCount;
  157. }
  158. }
  159.  
  160. /*
  161. **Utility.java
  162. */
  163. package NetFox;
  164. public class Utility {
  165. public Utility()
  166. {
  167. }
  168. public static void sleep(int nSecond)
  169. {
  170. try{
  171. Thread.sleep(nSecond);
  172. }
  173. catch(Exception e)
  174. {
  175. e.printStackTrace ();
  176. }
  177. }
  178. public static void log(String sMsg)
  179. {
  180. System.err.println(sMsg);
  181. }
  182. public static void log(int sMsg)
  183. {
  184. System.err.println(sMsg);
  185. }
  186. }
  187.  
  188. /*
  189. **TestMethod.java
  190. */
  191. package NetFox;
  192. public class TestMethod {
  193. public TestMethod()
  194. { ///xx/weblogic60b2_win.exe
  195. try{
  196. SiteInfoBean bean = new SiteInfoBean("http://localhost/xx/weblogic60b2_win.exe",
  197. "L:\\temp","weblogic60b2_win.exe",5);
  198. //SiteInfoBean bean = new SiteInfoBean("http://localhost:8080/down.zip","L:\\temp",
  199. "weblogic60b2_win.exe",5);
  200. SiteFileFetch fileFetch = new SiteFileFetch(bean);
  201. fileFetch.start();
  202. }
  203. catch(Exception e){e.printStackTrace ();}
  204. }
  205. public static void main(String[] args)
  206. {
  207. new TestMethod();
  208. }
  209. }

用 Java 实现断点续传 (HTTP)的更多相关文章

  1. java 实现断点续传

    请求头一:>>>>>>>>>>>>>>>>>>>>>>>> ...

  2. 用Java实现断点续传的基本思路和代码

    用Java实现断点续传的基本思路和代码   URL url = new URL(http://www.oschina.net/no-exist.zip); HttpURLConnection http ...

  3. 用 Java 实现断点续传参考 (HTTP)

    断点续传的原理 其实断点续传的原理很简单,就是在 Http 的请求上和一般的下载有所不同而已.        打个比方,浏览器请求服务器上的一个文时,所发出的请求如下:        假设服务器域名为 ...

  4. Java ftp断点续传

    FtpTransFile类 import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundExcept ...

  5. Java实现断点续传

    原理: 断点续传的关键是断点,所以在制定传输协议的时候要设计好,如上图,我自定义了一个交互协议,每次下载请求都会带上下载的起始点,这样就可以支持从断点下载了,其实HTTP里的断点续传也是这个原理,在H ...

  6. java服务器端断点续传

    Servlet Java代码 复制代码 收藏代码 import java.io.BufferedOutputStream; import java.io.File; import java.io.IO ...

  7. java 下载 断点续传

    1 import java.io.BufferedInputStream; 2 import java.io.File; 3 import java.io.FileInputStream; 4 imp ...

  8. java文件断点续传上传下载解决方案

    这里只写后端的代码,基本的思想就是,前端将文件分片,然后每次访问上传接口的时候,向后端传入参数:当前为第几块文件,和分片总数 下面直接贴代码吧,一些难懂的我大部分都加上注释了: 上传文件实体类: 看得 ...

  9. java支持断点续传文件上传和下载组件

    java两台服务器之间,大文件上传(续传),采用了Socket通信机制以及JavaIO流两个技术点,具体思路如下: 实现思路: 1.服:利用ServerSocket搭建服务器,开启相应端口,进行长连接 ...

随机推荐

  1. 学习笔记——Maven实战(五)自动化Web应用集成测试

    自动化集成测试的角色 本专栏的上一篇文章讲述了Maven与持续集成的一些关系及具体实践,我们都知道,自动化测试是持续集成必不可少的一部分,基本上,没有自动化测试的持续集成,都很难称之为真正的持续集成. ...

  2. 从实用主义深入理解c++虚函数

    记得几个月前看过C++虚函数的问题,当时其实就看懂了,最近笔试中遇到了虚函数竟然不太确定,所以还是理解的不深刻,所以想通过这篇文章来巩固下. 装逼一刻: 最近,本人思想发生了巨大的转变,在大学的时候由 ...

  3. 编译到底做了什么(***.c -> ***.o的过程)

     (第一次写博客,好激动的说.......)   我们知道,一个程序由源代码到可执行文件往往由这几步构成: 预处理(Prepressing)-> 编译(Compilation)-> 汇编( ...

  4. Orchard内置特性(以模块来说的)

    本文链接:http://www.cnblogs.com/souther/p/4539169.html 主目录 Orchard中有很多可以直接和多次使用的特性,这些东西在官方的Gallery中可以找到. ...

  5. android之简易新闻客户端

    将一个新闻信息保存到一个XML文件中,并将放在服务器下.通过手机客户端来从服务器下载该文件并解析显示. news.xml <?xml version="1.0" encodi ...

  6. 10、面向对象以及winform的简单运用(isMdicontainer的设置、timer控件进行倒计时的制作)

    IsMdicontainer的设置 这是对于整个窗体的设置,将一个窗体的IsMdicontainer设置为true之后,再打开新窗体便可以让新窗体被父容器包括在内. 操作方法: 1)先建立一个子窗体C ...

  7. 每天一个linux命令(49):ss命令

    ss是Socket Statistics的缩写.顾名思义,ss命令可以用来获取socket统计信息,它可以显示和netstat类似的内容.但ss的优势在于它能够显示更多更详细的有关TCP和连接状态的信 ...

  8. tomcat 配置

    tomcat 安装完成之后,我们可以在器目录先看到有如下结构

  9. 让js的forin循环禁止forin到某个属性的话要怎么做

    //知识点1:for In循环是可以枚举到继承的属性的://知识点2:使用defineProperty让属性无法通过forIn枚举到://知识点3:用definedProperty重新定义一个属性药把 ...

  10. zoj1665 dij变形

    既然输入的是损坏率,那1-x就是剩余的.最后只要剩余的最大. #include<stdio.h> #include<string.h> #define Max 99999999 ...