用Java实现HTTP断点续传功能副附详细的源代码Word下载.docx
《用Java实现HTTP断点续传功能副附详细的源代码Word下载.docx》由会员分享,可在线阅读,更多相关《用Java实现HTTP断点续传功能副附详细的源代码Word下载.docx(17页珍藏版)》请在冰豆网上搜索。
Server=Microsoft-IIS/5.0
Last-Modified=Mon,30Apr200112:
所谓断点续传,也就是要从文件已经下载的地方开始继续下载。
所以在客户端浏览器传给
Web服务器的时候要多加一条信息--从哪里开始(客户端提供一个文件偏移)。
下面是用自己编的一个"
浏览器"
来传递请求信息给Web服务器,要求从2000070字节开始。
GET/down.zipHTTP/1.0
NetFox
RANGE:
bytes=2000070-
text/html,image/gif,image/jpeg,*;
q=.2,*/*;
q=.2
仔细看一下就会发现多了一行RANGE:
这一行的意思就是告诉服务器down.zip这个文件从2000070字节开始传,前面的字节不用传了。
服务器收到这个请求以后,返回的信息如下:
206
Content-Range=bytes2000070-106786027/106786028
55:
20GMT
和前面服务器返回的信息比较一下,就会发现增加了一行:
返回的代码也改为206了,而不再是200了。
………………………………………………………………………………………………
206PartialContent
服务器已经成功处理了部分GET请求。
类似于FlashGet或者迅雷这类的HTTP下载工具都是使用此类响应实现断点续传或者将一个大文档分解为多个下载段同时下载。
该请求必须包含Range头信息来指示客户端希望得到的内容范围,并且可能包含If-Range来作为请求条件。
响应必须包含如下的头部域:
Content-Range用以指示本次响应中返回的内容的范围;
如果是Content-Type为multipart/byteranges的多段下载,则每一multipart段中都应包含Content-Range域用以指示本段的内容范围。
假如响应中包含Content-Length,那么它的数值必须匹配它返回的内容范围的真实字节数。
Date
ETag和/或Content-Location,假如同样的请求本应该返回200响应。
Expires,Cache-Control,和/或Vary,假如其值可能与之前相同变量的其他响应对应的值不同的话。
假如本响应请求使用了If-Range强缓存验证,那么本次响应不应该包含其他实体头;
假如本响应的请求使用了If-Range弱缓存验证,那么本次响应禁止包含其他实体头;
这避免了缓存的实体内容和更新了的实体头信息之间的不一致。
否则,本响应就应当包含所有本应该返回200响应中应当返回的所有实体头部域。
假如ETag或Last-Modified头部不能精确匹配的话,则客户端缓存应禁止将206响应返回的内容与之前任何缓存过的内容组合在一起。
任何不支持Range以及Content-Range头的缓存都禁止缓存206响应返回的内容。
………………………………………………………………………………………………………
知道了以上原理,就可以进行断点续传的编程了。
(二)Java实现断点续传的关键几点
(1)用什么方法实现提交RANGE:
bytes=2000070-。
当然用最原始的Socket是肯定能完成的,不过那样太费事了,其实Java的net包中提供了这种功能。
代码如下:
URLurl=newURL("
HttpURLConnectionhttpConnection=(HttpURLConnection)url.openConnection();
//设置User-Agent
httpConnection.setRequestProperty("
User-Agent"
"
NetFox"
);
//设置断点续传的开始位置
RANGE"
bytes=2000070"
//获得输入流
InputStreaminput=httpConnection.getInputStream();
从输入流中取出的字节流就是down.zip文件从2000070开始的字节流。
大家看,其实断点续传用Java实现起来还是很简单的吧。
接下来要做的事就是怎么保存获得的流到文件中去了。
保存文件采用的方法。
我采用的是IO包中的RandAccessFile类。
操作相当简单,假设从2000070处开始保存文件,代码如下:
RandomAccessoSavedFile=newRandomAccessFile("
down.zip"
rw"
longnPos=2000070;
//定位文件指针到nPos位置
oSavedFile.seek(nPos);
byte[]b=newbyte[1024];
intnRead;
//从输入流中读入字节流,然后写到文件中
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
*/
packageNetFox;
importjava.io.*;
import.*;
publicclassSiteFileFetchextendsThread{
SiteInfoBeansiteInfoBean=null;
//文件信息Bean
long[]nStartPos;
//开始位置
long[]nEndPos;
//结束位置
FileSplitterFetch[]fileSplitterFetch;
//子线程对象
longnFileLength;
//文件长度
booleanbFirst=true;
//是否第一次取文件
booleanbStop=false;
//停止标志
FiletmpFile;
//文件下载的临时信息
DataOutputStreamoutput;
//输出到文件的输出流
publicSiteFileFetch(SiteInfoBeanbean)throwsIOException
siteInfoBean=bean;
//tmpFile=File.createTempFile("
zhong"
1111"
newFile(bean.getSFilePath()));
tmpFile=newFile(bean.getSFilePath()+File.separator+bean.getSFileName()+"
.info"
if(tmpFile.exists())
bFirst=false;
read_nPos();
else
nStartPos=newlong[bean.getNSplitter()];
nEndPos=newlong[bean.getNSplitter()];
publicvoidrun()
//获得文件长度
//分割文件
//实例FileSplitterFetch
//启动FileSplitterFetch线程
//等待子线程返回
try{
if(bFirst)
nFileLength=getFileSize();
if(nFileLength==-1)
System.err.println("
FileLengthisnotknown!
"
elseif(nFileLength==-2)
Fileisnotaccess!
for(inti=0;
i&
lt;
nStartPos.length;
i++)
nStartPos[i]=(long)(i*(nFileLength/nStartPos.length));
nEndPos.length-1;
nEndPos[i]=nStartPos[i+1];
nEndPos[nEndPos.length-1]=nFileLength;
//启动子线程
fileSplitterFetch=newFileSplitterFetch[nStartPos.length];
fileSplitterFetch[i]=newFileSplitterFetch(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]=newFileSplitterFetch(siteInfoBean.getSSiteURL(),
siteInfoBean.getSFilePath()+File.separator+siteInfoBean.getSFileName(),nPos[nPos.length-1],nFileLength,nPos.length-1);
//Utility.log("
+(nPos.length-1)+"
+nPos[nPos.length-1]+"
nEndPos="
+nFileLength);
//fileSplitterFetch[nPos.length-1].start();
//等待子线程结束
//intcount=0;
//是否结束while循环
booleanbreakWhile=false;
while(!
bStop)
write_nPos();
Utility.sleep(500);
breakWhile=true;
if(!
fileSplitterFetch[i].bDownOver)
breakWhile=false;
break;
if(breakWhile)
//count++;
//if(count&
gt;
4)
//siteStop();
文件下载结束!
catch(Exceptione){e.printStackTrace();
publiclonggetFileSize()
intnFileLength=-1;
URLurl=newURL(siteInfoBean.getSSiteURL());
HttpURLConnectionhttpConnection=(HttpURLConnection)url.openConnection();
intresponseCode=httpConnection.getResponseCode();
if(responseCode&
=400)
processErrorCode(responseCode);
return-2;
//-2representaccessiserror
StringsHeader;
for(inti=1;
;
//DataInputStreamin=newDataInputStream(httpConnection.getInputStream());
//Utility.log(in.readLine());
sHeader=httpConnection.getHeaderFieldKey(i);
if(sHeader!
=null)
if(sHeader.equals("
Content-Length"
))
nFileLength=Integer.parseInt(httpConnection.getHeaderField(sHeader));
catch(IOExceptione){e.printStackTrace();
Utility.log(nFileLength);
returnnFileLength;
//保存下载信息(文件指针位置)
privatevoidwrite_nPos()
output=newDataOutputStream(newFileOutputStream(tmpFile));
output.writeInt(nStartPos.length);
//output.writeLong(nPos[i]);
output.writeLong(fileSplitterFetch[i].nStartPos);
output.writeLong(fileSplitterFetch[i].nEndPos);
output.close();
//读取保存的下载信息(文件指针位置)
privatevoidread_nPos()
DataInputStreaminput=newDataInputStream(newFileInputStream(tmpFile));
intnCount=input.readInt();
nStartPos=newlong[nCount];
nEndPos=newlong[nCount];
nStartPos[i]=input.readLong();
nEndPos[i]=input.readLong();
input.close();
privatevoidprocessErrorCode(intnErrorCode)
ErrorCode:
"
+nErrorCode);
//停止文件下载
publicvoidsiteStop()
bStop=true;
fileSplitterFetch[i].splitterStop();
**FileSplitterFetch.java
publicclassFileSplitterFetchextendsThread{
StringsURL;
//FileURL
longnStartPos;
//FileSnippetStartPosition
longnEndPos;
//FileSnippetEndPosition
intnThreadID;
//Thread'
sID
booleanbDownOver=false;
//Downingisover
//Stopidentical
FileAccessIfileAccessI=null;
//FileAccessinterface
publicFileSplitterFetch(StringsURL,StringsName,longnStart,longnEnd,intid)throwsIOException
this.sURL=sURL;
this.nStartPos=nStart;
this.nEndPos=nEnd;
nThreadID=id;
fileAccessI=newFileAccessI(sName,nStartPos);
while(nStartPos&
nEndPos&
&
!
URLurl=newURL(sURL);
StringsProperty="
bytes="
+nStartPos+"
-"
sProperty);
Utility.log(sProperty);
//logResponseHead(httpConnection);
while((nRead=input.read(b,0,1024))&
0&
nStartPos&
nStartPos+=fileAccessI.write(b,0,nRead);
//if(nThreadID==1)
nStartPos="
+nStartPos+"
+nEndPos);
+nThreadID+"
isover!
bDownOver=true;
//nPos=fileAccessI.write(b,0,nRead);
//打印回应的头信息
publicvoidlogResponseHead(HttpURLConnectioncon)
Stringheader=con.getHeaderFieldKey(i);
if(header!
//responseHeaders.put(header,httpConnection.getHeaderField(header));
Utility.log(header+"
:
+con