本文为原创,如需转载,请注明作者和出处,谢谢!
源代码下载:download.rar
在前面的文章曾讨论了HTTP消息头的三个和断点继传有关的字段。一个是请求消息的字段Range,另两个是响应消息字段Accept-Ranges和Content-Range。其中Accept-Ranges用来断定Web服务器是否支持断点继传功能。在这里为了演示如何实现断点继传功能,假设Web服务器支持这个功能;因此,我们只使用Range和Content-Range来完成一个断点继传工具的开发。
l 要实现一个什么样的断点续传工具?
这个断点续工具是一个单线程的下载工具。它通过参数传入一个文本文件。这个文件的格式如下:
<!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
-->http://www.ishare.cc/d/1174254-2/106.jpgd:/ok1.jpg8192
http://www.ishare.cc/d/1174292-2/156.jpgd:/ok2.jpg12345
http://www.ishare.cc/d/1174277-2/147.jpgd:/ok3.jpg3456
这个文本文件的每一行是一个下载项,这个下载项分为三部分:
使用至少一个空格来分隔这三部分。这个下载工具逐个下载这些文件,在这些文件全部下载完后程序退出。
l 断点续传的工作原理
“断点续传”顾名思义,就是一个文件下载了一部分后,由于服务器或客户端的原因,当前的网络连接中断了。在中断网络连接后,用户还可以再次建立网络连接来继续下载这个文件还没有下完的部分。
要想实现单线程断点续传,必须在客户断保存两个数据。
1. 已经下载的字节数。
2. 下载文件的URL。
一但重新建立网络连接后,就可以利用这两个数据接着未下载完的文件继续下载。在本下载工具中第一种数据就是文件已经下载的字节数,而第二个数据在上述的下载文件中保存。
在继续下载时检测已经下载的字节数,假设已经下载了3000个字节,那么HTTP请求消息头的Range字段被设为如下形式:
<!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
-->Range:bytes=3000-
HTTP响应消息头的Content-Range字段被设为如下的形式:
<!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
-->Content-Range:bytes3000-10000/10001
l 实现断点续传下载工具
一个断点续传下载程序可按如下几步实现:
1. 输入要下载文件的URL和要保存的本地文件名,并通过Socket类连接到这个URL
所指的服务器上。
2. 在客户端根据下载文件的URL和这个本地文件生成HTTP请求消息。在生成请求
消息时分为两种情况:
(1)第一次下载这个文件,按正常情况生成请求消息,也就是说生成不包含Range
字段的请求消息。
(2)以前下载过,这次是接着下载这个文件。这就进入了断点续传程序。在这种情况生成的HTTP请求消息中必须包含Range字段。由于是单线程下载,因此,这个已经下载了一部分的文件的大小就是Range的值。假设当前文件的大小是1234个字节,那么将Range设成如下的值:
<!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
-->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的值为:
<!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
-->Content-Range:bytes1000-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;
publicinterfaceDownloadEvent
{
voidpercent(longn);//下载进度
voidstate(Strings);//连接过程中的状态切换
voidviewHttpHeaders(Strings);//枚举每一个响应消息字段
}
从上面的代码可以看出,DownloadEvent接口中有三个事件方法。在以后的主函数中将实现这个接口,来向控制台输出相应的信息。下面给出了HttpDownload类的主体框架代码:
<!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
-->001package download;
002
003importjava.net.*;
004importjava.io.*;
005importjava.util.*;
006
007publicclassHttpDownload
008{
009privateHashMaphttpHeaders=newHashMap();
010privateStringstateCode;
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方法都会用到。上述的八个方法的实现都会在后面给出。
<!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
-->001privatevoidgenerateHttpRequest(OutputStreamout,Stringhost,
002Stringpath,longstartPos)throwsIOException
003{
004OutputStreamWriterwriter=newOutputStreamWriter(out);
005writer.write("GET"+path+"HTTP/1.1/r/n");
006writer.write("Host:"+host+"/r/n");
007writer.write("Accept:*/*/r/n");
008writer.write("User-Agent:MyFirstHttpDownload/r/n");
009if(startPos>0)//如果是断点续传,加入Range字段
010writer.write("Range:bytes="+String.valueOf(startPos)+"-/r/n");
011writer.write("Connection:close/r/n/r/n");
012writer.flush();
013}
这个方法有四个参数:
1. OutputStream out
使用Socket对象的getOutputStream方法得到的输出流。
2. String host
下载文件所在的服务器的域名或IP。
3. String path
下载文件在服务器上的路径,也就跟在GET方法后面的部分。
4. long startPos
从文件的startPos位置开始下载。如果startPos为0,则不生成Range字段。
<!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
-->001privatevoidanalyzeHttpHeader(InputStreaminputStream,DownloadEventde)
002throwsException
003{
004Strings="";
005byteb=-1;
006while(true)
007{
008b=(byte)inputStream.read();
009if(b=='/r')
010{
011b=(byte)inputStream.read();
012if(b=='/n')
013{
014if(s.equals(""))
015break;
016de.viewHttpHeaders(s);
017addHeaderToMap(s);
018s="";
019}
020}
021else
022s+=(char)b;
023}
024}
025
026privatevoidanalyzeFirstLine(Strings)
027{
028String[]ss=s.split("[]+");
029if(ss.length>1)
030stateCode=ss[1];
031}
032privatevoidaddHeaderToMap(Strings)
033{
034intindex=s.indexOf(":");
035if(index>0)
036httpHeaders.put(s.substring(0,index),s.substring(index+1).trim());
037else
038analyzeFirstLine(s);
039}
第001 ? 024行:analyzeHttpHeader方法的实现。这个方法有两个参数。其中inputStream是用Socket对象的getInputStream方法得到的输入流。这个方法是直接使用字节流来分析的HTTP响应头(主要是因为下载的文件不一定是文本文件;因此,都统一使用字节流来分析和下载),每两个""r"n"之间的就是一个字段和字段值对。在016行调用了DownloadEvent接口的viewHttpHeaders事件方法来枚举每一个响应头字段。
第026 ? 031行:analyzeFirstLine方法的实现。这个方法的功能是分析响应消息头的第一行,并从中得到状态码后,将其保存在stateCode变量中。这个方法的参数s就是响应消息头的第一行。
第032 ? 039行:addHeaderToMap方法的实现。这个方法的功能是将每一个响应请求消息字段和字段值加到在HttpDownload类中定义的httpHeaders哈希映射中。在第034行查找每一行消息头是否包含":",如果包含":",这一行必是消息头的第一行。因此,在第038行调用了analyzeFirstLine方法从第一行得到响应状态码。
<!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
-->001privateStringgetHeader(Stringheader)
002{
003return(String)httpHeaders.get(header);
004}
005privateintgetIntHeader(Stringheader)
006{
007returnInteger.parseInt(getHeader(header));
008}
这两个方法将会在getFileSize和download中被调用。它们的功能是从响应消息中根据字段字得到相应的字段值。getHeader得到字符串形式的字段值,而getIntHeader得到整数型的字段值。
<!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
-->001publiclonggetFileSize()
002{
003longlength=-1;
004try
005{
006length=getIntHeader("Content-Length");
007String[]ss=getHeader("Content-Range").split("[/]");
008if(ss.length>1)
009length=Integer.parseInt(ss[1]);
010else
011length=-1;
012}
013catch(Exceptione)
014{
015}
016returnlength;
017}
getFileSize方法的功能是得到下载文件的实际大小。首先在006行通过Content-Length得到了当前响应消息的实体内容大小。然后在009行得到了Content-Range字段值所描述的文件的实际大小("""后面的值)。如果Content-Range字段不存在,则文件的实际大小就是Content-Length字段的值。如果Content-Length字段也不存在,则返回-1,表示文件实际大小无法确定。
<!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
-->001publicvoiddownload(DownloadEventde,Stringurl,StringlocalFN,
002intcacheSize)throwsException
003{
004Filefile=newFile(localFN);
005longfinishedSize=0;
006longfileSize=0;//localFN所指的文件的实际大小
007FileOutputStreamfileOut=newFileOutputStream(localFN,true);
008URLmyUrl=newURL(url);
009Socketsocket=newSocket();
010byte[]buffer=newbyte[cacheSize];//下载数据的缓冲
011
012if(file.exists())
013finishedSize=file.length();
014
015//得到要下载的Web资源的端口号,未提供,默认是80
016intport=(myUrl.getPort()==-1)?80:myUrl.getPort();
017de.state("正在连接"+myUrl.getHost()+":"+String.valueOf(port));
018socket.connect(newInetSocketAddress(myUrl.getHost(),port),20000);
019de.state("连接成功!");
020
021//产生HTTP请求消息
022generateHttpRequest(socket.getOutputStream(),myUrl.getHost(),myUrl
023.getPath(),finishedSize);
024
025InputStreaminputStream=socket.getInputStream();
026//分析HTTP响应消息头
027analyzeHttpHeader(inputStream,de);
028fileSize=getFileSize();//得到下载文件的实际大小
029if(finishedSize>=fileSize)
030return;
031else
032{
033if(finishedSize>0&&stateCode.equals("200"))
034return;
035}
036if(stateCode.charAt(0)!='2')
037thrownewException("不支持的响应码");
038intn=0;
039longm=finishedSize;
040while((n=inputStream.read(buffer))!=-1)
041{
042fileOut.write(buffer,0,n);
043m+=n;
044if(fileSize!=-1)
045{
046de.percent(m*100/fileSize);
047}
048}
049fileOut.close();
050socket.close();
051}
download方法是断点续传工具的核心方法。它有四个参数:
1. DownloadEvent de
用于处理下载事件的接口。
2. String url
要下载文件的URL。
3. String localFN
要保存的本地文件名,可以用这个文件的大小来确定已经下载了多少个字节。
4. int cacheSize
下载数据的缓冲区。也就是一次从服务器下载多个字节。这个值不宜太小,因为,频繁地从服务器下载数据,会降低网络的利用率。一般可以将这个值设为8192(8K)。
为了分析下载文件的url,在008行使用了URL类,这个类在以后还会介绍,在这里只要知道使用这个类可以将使用各种协议的url(包括HTTP和FTP协议)的各个部分分解,以便单独使用其中的一部分。
第029行:根据文件的实际大小和已经下载的字节数(finishedSize)来判断是否文件是否已经下载完成。当文件的实际大小无法确定时,也就是fileSize返回-1时,不能下载。
第033行:如果文件已经下载了一部分,并且返回的状态码仍是200(应该是206),则表明服务器并不支持断点续传。当然,这可以根据另一个字段Accept-Ranges来判断。
第036行:由于本程序未考虑重定向(状态码是3xx)的情况,因此,在使用download时,不要下载返回3xx状态码的Web资源。
第040 ? 048行:开始下载文件。第046行调用DownloadEvent的percent方法来返回下载进度。
<!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
-->001package download;
002
003importjava.io.*;
004
005classNewProgressimplementsDownloadEvent
006{
007privatelongoldPercent=-1;
008publicvoidpercent(longn)
009{
010if(n>oldPercent)
011{
012System.out.print("["+String.valueOf(n)+"%]");
013oldPercent=n;
014}
015}
016publicvoidstate(Strings)
017{
018System.out.println(s);
019}
020publicvoidviewHttpHeaders(Strings)
021{
022System.out.println(s);
023}
024}
025
026publicclassMain
027{
028publicstaticvoidmain(String[]args)throwsException
029{
030
031DownloadEventprogress=newNewProgress();
032if(args.length<1)
033{
034System.out.println("用法:javaclass下载文件名");
035return;
036}
037FileInputStreamfis=newFileInputStream(args[0]);
038BufferedReaderfileReader=newBufferedReader(newInputStreamReader(
039fis));
040Strings="";
041String[]ss;
042while((s=fileReader.readLine())!=null)
043{
044try
045{
046ss=s.split("[]+");
047if(ss.length>2)
048{
049System.out.println("/r/n---------------------------");
050System.out.println("正在下载:"+ss[0]);
051System.out.println("文件保存位置:"+ss[1]);
052System.out.println("下载缓冲区大小:"+ss[2]);
053System.out.println("---------------------------");
054HttpDownloadhttpDownload=newHttpDownload();
055httpDownload.download(newNewProgress(),ss[0],ss[1],
056Integer.parseInt(ss[2]));
057}
058}
059catch(Exceptione)
060{
061System.out.println(e.getMessage());
062}
063}
064fileReader.close();
065}
066}
第005 ? 024行:实现DownloadEvent接口的NewDownloadEvent类。用于在Main函数里接收相应事件传递的数据。
第026 ? 065 行:下载工具的Main方法。在这个Main方法里,打开下载资源列表文件,逐行下载相应的Web资源。
测试
假设download.txt在当前目录中,内容如下:
<!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
-->http://files.cnblogs.com/nokiaguy/HttpSimulator.rarHttpSimulator.rar8192
http://files.cnblogs.com/nokiaguy/designpatterns.rardesignpatterns.rar4096
http://files.cnblogs.com/nokiaguy/download.rar download.rar 8192
这两个URL是在本机的Web服务器(如IIS)的虚拟目录中的两个文件,将它们下载在D盘根目录。
运行下面的命令:
<!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
-->javadownload.Maindownload.txt
运行的结果如图1所示。
图1
分享到:
相关推荐
这是java实现多线程断点续传下载功能的源代码,对于学习java网络编程、多线程等基础知识的同学有一定的参考作用,希望能够这个资源能够对你们有帮助
Java多线程与线程安全实践-基于Http协议的断点续传.rar 是一个Java毕业设计项目,旨在探讨如何在Java中实现多线程和线程安全,以及如何基于Http协议实现断点续传功能。该项目提供了一个完整的源代码包,可以作为学习...
基于EJB的真实世界模型,附源代码,部分功能需JSP配合完成。 J2ME优化压缩PNG文件 4个目标文件 内容索引:JAVA源码,综合应用,J2me游戏,PNG,图形处理 这是个J2ME控制台程序,它能剔除PNG文件中的非关键数据段,...
基于EJB的真实世界模型,附源代码,部分功能需JSP配合完成。 J2ME优化压缩PNG文件 4个目标文件 内容索引:JAVA源码,综合应用,J2me游戏,PNG,图形处理 这是个J2ME控制台程序,它能剔除PNG文件中的非关键数据段,...
它旨在提供一个全面、深入的学习和研究工具,适用于本科课程设计、毕业设计以及任何希望深入学习Java编程的学习者。 详细内容: 源代码——提供了一套完整、经过良好注释的Java源代码,实现了多线程与线程安全实践...
基于EJB的真实世界模型,附源代码,部分功能需JSP配合完成。 J2ME优化压缩PNG文件 4个目标文件 内容索引:JAVA源码,综合应用,J2me游戏,PNG,图形处理 这是个J2ME控制台程序,它能剔除PNG文件中的非关键数据段...
Java程序由Java源代码编写,经过编译后生成Java字节码文件,然后在Java虚拟机上运行。 Java程序通常包括一个或多个类,每个类都包含了属性和方法。Java程序的入口点是一个特殊的类,它包含了一个名为main的方法,这...
基于EJB的真实世界模型,附源代码,部分功能需JSP配合完成。 J2ME优化压缩PNG文件 4个目标文件 内容索引:JAVA源码,综合应用,J2me游戏,PNG,图形处理 这是个J2ME控制台程序,它能剔除PNG文件中的非关键数据段...
基于EJB的真实世界模型,附源代码,部分功能需JSP配合完成。 J2ME优化压缩PNG文件 4个目标文件 内容索引:JAVA源码,综合应用,J2me游戏,PNG,图形处理 这是个J2ME控制台程序,它能剔除PNG文件中的非关键数据段,...
基于EJB的真实世界模型,附源代码,部分功能需JSP配合完成。 J2ME优化压缩PNG文件 4个目标文件 内容索引:JAVA源码,综合应用,J2me游戏,PNG,图形处理 这是个J2ME控制台程序,它能剔除PNG文件中的非关键数据段...
日历表格面板 [ConfigLine.java] 控制条类 [RoundBox.java] 限定选择控件 [MonthMaker.java] 月份表算法类 [Pallet.java] 调色板,统一配色类 Java扫雷源码 Java生成自定义控件源代码 2个目标文件 Java实现HTTP连接...
随着计算机专业的普及,越来越多的大学生选择了该热门专业,毕业时的毕业设计需要完整的源码以及论文。此资源是已毕业前辈的毕业作品,包含项目的源码、毕业设计...3、Java多线程与线程安全实践-基于Http协议的断点续传
日历表格面板 [ConfigLine.java] 控制条类 [RoundBox.java] 限定选择控件 [MonthMaker.java] 月份表算法类 [Pallet.java] 调色板,统一配色类 Java扫雷源码 Java生成自定义控件源代码 2个目标文件 Java实现HTTP连接...
任务概述 本项任务要开发一款P2P文件传输软件,该软件可以在局域网和互连上使用,具有文件传输,断点续传,多线程连接等功能。
第1篇 Java编程基础 第1章 Java开发环境的搭建(教学视频:9分钟) 2 1.1 理解Java 2 1.2 搭建Java所需环境 3 1.2.1 下载JDK 3 1.2.2 安装JDK 4 1.2.3 配置环境 5 1.2.4 测试JDK配置...
包括编译器、构建工具(如Make、Gradle、Maven)等,用于将源代码转换为可执行文件或库,并进行资源打包、优化等处理。 调试与测试: 集成调试器允许开发者逐行执行代码,设置断点、查看变量值、跟踪调用堆栈等...
(2) 参考C版源代码,遵循编译器的基本结构,应用面向对象软件设计方法重新实现。不应仅对C版代码作简单的翻译。 (3) 提供简单的断点、单步调试功能,用户能实时指定并查看某个变量的值 (4) 包括测试例子 直接运行jar...
该资料是《Visual C++ 2010入门经典(第5版)》的源代码及课后练习答案 对应的书籍资料见: Visual C++ 2010入门经典(第5版) 基本信息 原书名: Ivor Horton's Beginning Visual C++ 2010 原出版社: Wrox 作者: ...