用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. (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实现起来还是很简单的吧。 接下来要做的事就是怎么保存获得的流到文件中去了。

  2. 保存文件采用的方法。 
    我采用的是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测试类。 
代码来自 http://www.ibm.com/developerworks/cn/java/joy-down/index.html

代码片段(6)[全屏查看所有代码]

1. [代码]SiteFileFetch.java     

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
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();
}
}

2. [代码]FileSplitterFetch.java     

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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 ;
}
}

3. [代码]FileAccess.java     

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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;
}
}

4. [代码]SiteInfoBean.java     

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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;
}
}

5. [代码]Utility.java     

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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);
}
}

6. [代码]TestMethod.java     

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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();
}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值