最近要做这一块的功能,从网上拉了个demo亲测可用,http协议,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);
}
总体大致思路:
1.先抓取整个文件,进行是否第一次下载的判断。
2.如果不是第一次下载直接读取保存的字节信息(在临时文件中),是第一次则创建每个分片的字节范围。
3.执行整个文件抓取的线程run方法,如果是第一次下载则获取文件总长度进行分片,不是则直接进行第4步。
4.进行单个文件抓取(无论是否第一次下载),采用循环方式执行分片个数相应的线程数量进行下载并且保存文件及其进度,对下载的进度用startPos进行累加(这样暂停的时候就可以记录上次下到了哪个地方),如果下载没有停止,每500ms进行保存一次下载的字节到临时文件夹,直到下载停止。
TestMethod(测试类)
//测试类
public class TestMethod {
public TestMethod()
{
try{
SiteInfoBean bean = new SiteInfoBean("http://fastsoft.onlinedown.net/down/mysql3573.zip",
"E:\\","mysql3573.zip",6);
SiteFileFetch fileFetch = new SiteFileFetch(bean);
fileFetch.start();
}
catch(Exception e){
e.printStackTrace ();
}
}
public static void main(String[] args)
{
new TestMethod();
}
}
SiteBean(存放信息类)
//存放信息
public class SiteInfoBean {
private String sSiteURL; //下载的URL
private String sFilePath; //保存文件的路径
private String sFileName; //保存文件的名称
private int nSplitter; //下载文件拆分为几个部分
public SiteInfoBean()
{
this("","","",6);
}
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;
}
}
SiteFileFetch(整个文件的抓取)
//整个文件的抓取
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; // 文件下载的临时信息
private boolean isLoading;
DataOutputStream output; // 输出到文件的输出流
public SiteFileFetch(SiteInfoBean bean)throws IOException
{
isLoading = true;
//判断是否第一次读取文件
siteInfoBean = bean;
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()
{
try{
// 如果是第一次下载
// 分配文件指针数组的起始结束位置
if(bFirst)
{
nFileLength = getFileSize();
if(-1== nFileLength )
{
isLoading=false;
bStop = true;
System.err.println("File Length is not known!");
}
else if(-2 ==nFileLength)
{
isLoading=false;
bStop = true;
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();
}
// 是否结束 while 循环
boolean breakWhile = false;
//true停止
while(!bStop)
{// 如果下载没有停止,则每隔500ms去保存一次文件指针信息到临时文件
write_nPos();
gatherLoadProgress();
Utility.sleep(500);
breakWhile = true;
for(int i=0;i<nStartPos.length;i++)
{
if(!fileSplitterFetch[i].bDownOver)
{// 只要其中有一个没下载完成,
breakWhile = false;
break;
}
}
if(breakWhile)
break;
}
gatherLoadProgress();
System.err.println("文件下载结束!");
isLoading = false;
}
catch(Exception e){
isLoading = false;
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服务器响应错误
}
//获取返回信息
String sHeader;
for(int i=1;;i++)
{
sHeader=httpConnection.getHeaderFieldKey(i);
if(null !=sHeader)
{
// http恶心的地方,返回头信息第一个居然是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(fileSplitterFetch[i].nStartPos);
output.writeLong(fileSplitterFetch[i].nEndPos);
}
output.close();
}
catch(IOException e){
e.printStackTrace ();
}
catch(Exception e){
e.printStackTrace ();
}
}
/**
* 收集下载进度
*/
private void gatherLoadProgress()
{
nFileLength=getFileSize();
// 剩余的字节数
long laveLength = 0;
try {
for (int i = 0; i < nStartPos.length; i++)
{
laveLength += (fileSplitterFetch[i].nEndPos - fileSplitterFetch[i].nStartPos);
}
int percent = (int)((nFileLength - laveLength)*100/nFileLength);
if(100 == percent)
{
if(null!=tmpFile && tmpFile.exists())
{
//全部下载完成,则删除临时文件,
tmpFile.delete();
}
isLoading = false;
bStop = true;
}
System.out.println("当前下载进度 " + percent + "%");
}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 boolean isLoading()
{
return isLoading;
}
// 停止文件下载
public void siteStop()
{
isLoading = false;
bStop = true;
for(int i=0;i<nStartPos.length;i++)
fileSplitterFetch[i].splitterStop();
}
}
FileSplitterFetch(部分文件的抓取)
//部分文件的抓取
public class FileSplitterFetch extends Thread{
String sURL; //文件路径
long nStartPos; //文件起始位置
long nEndPos; //文件结束位置
int nThreadID; //线程id
boolean bDownOver = false; //下载完毕
boolean bStop = false; //下载停止标志
FileAccessI fileAccessI = null; //文件存储接口
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);
//读取文件 getInputStream把一个html或一个连接过来的文档以 输入流的方法读取
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);
/*System.out.println("nRead:"+nRead+"nStartPos:"+nStartPos);*/
}
Utility.log("Thread " + nThreadID + " is over!");
bDownOver = true;
}
catch(Exception e){
e.printStackTrace ();
}
}
}
// 打印回应的头信息
public void logResponseHead(HttpURLConnection con)
{
for(int i=1;;i++)
{
String header=con.getHeaderFieldKey(i);
if(null!=header)
Utility.log(header+" : "+con.getHeaderField(header));
else
break;
}
}
public void splitterStop()
{
bStop = true;
}
}
FileAccessI(文件的存取)
//文件的存储
public class FileAccessI implements Serializable {
RandomAccessFile oSavedFile;
long nPos;
public FileAccessI() throws IOException
{
this("",0);
}
public FileAccessI(String sName,long nPos) throws IOException
{
//创建随机存储文件流,文件属性由参数File对象指定
//读写,如果使用此模式,如果此文件不存在,则会自动创建。
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;
}
}
Utility(工具类)
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);
}
}
这个网上的demo很常见,具体原文地址我就以我找的时候那个地址填上来了,但是流程可能不熟悉,自己看了以后总结的,包括里面许多注释。有需要可借鉴。