源代码下载:点此下载
在前面的文章曾讨论了HTTP消息头的三个和断点继传有关的字段。一个是请求消息的字段Range,另两个是响应消息字段Accept-Ranges和Content-Range。其中Accept-Ranges用来断定Web服务器是否支持断点继传功能。在这里为了演示如何实现断点继传功能,假设Web服务器支持这个功能;因此,我们只使用Range和Content-Range来完成一个断点继传工具的开发。
l 要实现一个什么样的断点续传工具?
这个断点续工具是一个单线程的下载工具。它通过参数传入一个文本文件。这个文件的格式如下:
http://www.ishare.cc/d/1174254-2/106.jpg d:ok1.jpg 8192
http://www.ishare.cc/d/1174292-2/156.jpg d:ok2.jpg 12345
http://www.ishare.cc/d/1174277-2/147.jpg d:ok3.jpg 3456
这个文本文件的每一行是一个下载项,这个下载项分为三部分:
要下载的Web资源的URL。
要保存的本地文件名。
下载的缓冲区大小(单位是字节)。
使用至少一个空格来分隔这三部分。这个下载工具逐个下载这些文件,在这些文件全部下载完后程序退出。
l 断点续传的工作原理
“断点续传”顾名思义,就是一个文件下载了一部分后,由于服务器或客户端的原因,当前的网络连接中断了。在中断网络连接后,用户还可以再次建立网络连接来继续下载这个文件还没有下完的部分。
要想实现单线程断点续传,必须在客户断保存两个数据。
1. 已经下载的字节数。
2. 下载文件的URL。
一但重新建立网络连接后,就可以利用这两个数据接着未下载完的文件继续下载。在本下载工具中第一种数据就是文件已经下载的字节数,而第二个数据在上述的下载文件中保存。
在继续下载时检测已经下载的字节数,假设已经下载了3000个字节,那么HTTP请求消息头的Range字段被设为如下形式:
Range: bytes=3000-
HTTP响应消息头的Content-Range字段被设为如下的形式:
Content-Range: bytes 3000-10000/10001
l 实现断点续传下载工具
一个断点续传下载程序可按如下几步实现:
1. 输入要下载文件的URL和要保存的本地文件名,并通过Socket类连接到这个URL
所指的服务器上。
2. 在客户端根据下载文件的URL和这个本地文件生成HTTP请求消息。在生成请求
消息时分为两种情况:
(1)第一次下载这个文件,按正常情况生成请求消息,也就是说生成不包含Range
字段的请求消息。
(2)以前下载过,这次是接着下载这个文件。这就进入了断点续传程序。在这种情况生成的HTTP请求消息中必须包含Range字段。由于是单线程下载,因此,这个已经下载了一部分的文件的大小就是Range的值。假设当前文件的大小是1234个字节,那么将Range设成如下的值:
Range:bytes=1234-
3. 向服务器发送HTTP请求消息。
4. 接收服务器返回的HTTP响应消息。
5. 处理HTTP响应消息。在本程序中需要从响应消息中得到下载文件的总字节数。如
果是第一次下载,也就是说响应消息中不包含Content-Range字段时,这个总字节数也就是Content-Length字段的值。如果响应消息中不包含Content-Length字段,则这个总字节数无法确定。这就是为什么使用下载工具下载一些文件时没有文件大小和下载进度的原因。如果响应消息中包含Content-Range字段,总字节数就是Content-Range:bytes m-n/k中的k,如Content-Range的值为:
Content-Range:bytes 1000-5000/5001
则总字节数为5001。由于本程序使用的Range值类型是得到从某个字节开始往后的所有字节,因此,当前的响应消息中的Content-Range总是能返回还有多少个字节未下载。如上面的例子未下载的字节数为5000-1000+1=4001。
6. 开始下载文件,并计算下载进度(百分比形式)。如果网络连接断开时,文件仍未下载完,重新执行第一步。也果文件已经下载完,退出程序。
分析以上六个步骤得知,有四个主要的功能需要实现:
1. 生成HTTP请求消息,并将其发送到服务器。这个功能由generateHttpRequest方法来完成。
2. 分析HTTP响应消息头。这个功能由analyzeHttpHeader方法来完成。
3. 得到下载文件的实际大小。这个功能由getFileSize方法来完成。
4. 下载文件。这个功能由download方法来完成。
以上四个方法均被包含在这个断点续传工具的核心类HttpDownload.java中。在给出HttpDownload类的实现之前先给出一个接口DownloadEvent接口,从这个接口的名字就可以看出,它是用来处理下载过程中的事件的。下面是这个接口的实现代码:
package download;
public interface DownloadEvent
{
void percent(long n); // 下载进度
void state(String s); // 连接过程中的状态切换
void viewHttpHeaders(String s); // 枚举每一个响应消息字段
}
从上面的代码可以看出,DownloadEvent接口中有三个事件方法。在以后的主函数中将实现这个接口,来向控制台输出相应的信息。下面给出了HttpDownload类的主体框架代码:
001 package download;
002
003 import java.net.*;
004 import java.io.*;
005 import java.util.*;
006
007 public class HttpDownload
008 {
009 private HashMap httpHeaders = new HashMap();
010 private String stateCode;
011
012 // generateHttpRequest方法
013
014 /* ananlyzeHttpHeader方法
015 *
016 * addHeaderToMap方法
017 *
018 * analyzeFirstLine方法
019 */
020
021 // getFileSize方法
022
023 // download方法
024
025 /* getHeader方法
026 *
027 * getIntHeader方法
028 */
029 }
上面的代码只是HttpDownload类的框架代码,其中的方法并未直正实现。我们可以从中看出第012、014、021和023行就是上述的四个主要的方法。在016和018行的addHeaderToMap和analyzeFirstLine方法将在analyzeHttpHeader方法中用到。而025和027行的getHeader和getIntHeader方法在getFileSize和download方法都会用到。上述的八个方法的实现都会在后面给出。
001 private void generateHttpRequest(OutputStream out, String host,
002 String path, long startPos) throws IOException
003 {
004 OutputStreamWriter writer = new OutputStreamWriter(out);
005 writer.write("GET " + path + " HTTP/1.1
");
006 writer.write("Host: " + host + "
");
007 writer.write("Accept: */*
");
008 writer.write("User-Agent: My First Http Download
");
009 if (startPos > 0) // 如果是断点续传,加入Range字段
010 writer.write("Range: bytes=" + String.valueOf(startPos) + "-
");
011 &