断点续传原理

(一) 断点续传的原理
其实断点续传的原理很简单,就是在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)用什么方法实现提交RANGE: bytes=2000070-。
当然用最原始的Socket是肯定能完成的,不过那样太费事了,其实Java的net包中提供了这种功能。
代码如下:

URL url = new URL( “http://192.168.100.154:9000/DownLoadSit/apache-tomcat-6.0.18.exe“);
HttpURLConnection httpConnection = (HttpURLConnection) url
.openConnection();
int length = httpConnection.getContentLength(); //获取下载文件的长度
// 设置User-Agent
httpConnection.setRequestProperty(“User-Agent”, “NetFox”);
// 设置断点续传的开始位置
httpConnection.setRequestProperty(“RANGE”, “bytes=0-“);
String fileName = url.getFile(); //取得文件名 // 获得输入流
InputStream input = httpConnection.getInputStream();

从输入流中取出的字节流就是down.zip文件从2000070开始的字节流。
大家看,其实断点续传用Java实现起来还是很简单的吧。
接下来要做的事就是怎么保存获得的流到文件中去了。

保存文件采用的方法。
我采用的是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测试类。

下面是源程序:

  /*   
  **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[i]   =   (long)(i*(nFileLength/nStartPos.length));   
  }   
  for(int   i=0;i<nEndPos.length-1;i++)   
  {   
  nEndPos[i]   =   nStartPos[i+1];   
  }   
  nEndPos[nEndPos.length-1]   =   nFileLength;   
  }   
  }   


  //启动子线程   
  fileSplitterFetch   =   new   FileSplitterFetch[nStartPos.length];   
  for(int   i=0;i<nStartPos.length;i++)   
  {   
  fileSplitterFetch[i]   =   new   FileSplitterFetch(siteInfoBean.getSSiteURL(),   
  siteInfoBean.getSFilePath()   +   File.separator   +   siteInfoBean.getSFileName(),   
  nStartPos[i],nEndPos[i],i);   
  Utility.log("Thread   "   +   i   +   "   ,   nStartPos   =   "   +   nStartPos[i]   +   ",   nEndPos   =   "   +   nEndPos[i]);   
  fileSplitterFetch[i].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[i].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[i]);   
  output.writeLong(fileSplitterFetch[i].nStartPos);   
  output.writeLong(fileSplitterFetch[i].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[i]   =   input.readLong();   
  nEndPos[i]   =   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[i].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;   //Thread's   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;   //Site's   URL   
  private   String   sFilePath;   //Saved   File's   Path   
  private   String   sFileName;   //Saved   File's   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();   
  }   
  }   
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值