您的位置:首页 > 其它

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

2016-04-20 09:21 387 查看
本文所要讲的是Android断点续传的内容,以实例的形式进行了详细介绍。

一、断点续传的原理

其实断点续传的原理很简单,就是在http的请求上和一般的下载有所不同而已。

打个比方,浏览器请求服务器上的一个文时,所发出的请求如下:

假设服务器域名为www.jizhuomi.com/android,文件名为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实现断点续传的关键几点

用什么方法实现提交range: bytes=2000070-?

当然用最原始的socket是肯定能完成的,不过那样太费事了,其实java的net包中提供了这种功能。代码如下:

Java代码

url url = new url("http://www.jizhuomi.com/android/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处开始保存文件,代码如下:

Java代码

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测试类。

四、实例源码

下面是源程序:

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 = (long)(i*(nfilelength/nstartpos.length));

}

for(int i=0;i<nendpos.length-1;i++)

{

nendpos = nstartpos[i+1];
}

nendpos[nendpos.length-1] = nfilelength;
}

}
//启动子线程

filesplitterfetch = new filesplitterfetch[nstartpos.length];

for(int i=0;i<nstartpos.length;i++)

{
filesplitterfetch = new filesplitterfetch(siteinfobean.getssiteurl(),

siteinfobean.getsfilepath() + file.separator + siteinfobean.getsfilename(),
nstartpos,nendpos,i);

utility.log("thread " + i + " , nstartpos = " + nstartpos + ", nendpos = " + nendpos);

filesplitterfetch.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.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);
output.writelong(filesplitterfetch.nstartpos);

output.writelong(filesplitterfetch.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 = input.readlong();

nendpos = 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.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; //threads 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; //sites url
private string sfilepath; //saved files path

private string sfilename; //saved files 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();

}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: