服务热线:13616026886

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

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

用java实现断点续传(http)


  作者: 钟华(zhong_hua@263.net)
  内容:
  (一)断点续传的原理
  (二)java实现断点续传的关键几点
  (三)断点续传内核的实现
  (一)断点续传的原理
  其实断点续传的原理很简单,就是在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[i] = (long)(i*(nfilelength/nstartpos.length));
  }
  for(int i=0;i  {
  nendpos[i] = nstartpos[i+1];
  }
  nendpos[nendpos.length-1] = nfilelength;
  }
  }
  //启动子线程
  filesplitterfetch = new filesplitterfetch[nstartpos.length];
  for(int i=0;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  {
  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 ());
  //utili

扫描关注微信公众号