服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

用java实现http断点续传


  (一)断点续传的原理

  其实断点续传的原理很简单,就是在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://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实现起来还是很简单的吧。接下来要做的事就是怎么保存获得的流到文件中去了。

  保存文件采用的方法

  我采用的是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();
 }
}

扫描关注微信公众号