java断点续传的原理-程序员宅基地

技术标签: exception  java  string  header  thread  bean  

转载,学习下:
2008/05/06 11:33
其实原理很简单,只是在   http   请求中加一个文件的偏移量而已,当然这还需要   server   支持这个头才行。  
手头上刚好有一篇这样的文档。  
   
(一)断点续传的原理  
其实断点续传的原理很简单,就是在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)用什么方法实现提交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);  
}  
   
怎么样,也很简单吧。  
接下来要做的就是整合成一个完整的程序了。包括一系列的线程控制等等。  
   
   
   
(三)断点续传内核的实现  
主要用了6个类,包括一个测试类。  
SiteFileFetch.java负责整个文件的抓取,控制内部线程(FileSplitterFetch类)。  
FileSplitterFetch.java负责部分文件的抓取。  
FileAccess.java负责文件的存储。  
SiteInfoBean.java要抓取的文件的信息,如文件保存的目录,名字,抓取文件的URL等。  
Utility.java工具类,放一些简单的方法。  
TestMethod.java测试类。  
   
   
下面是源程序:    
/*  
**SiteFileFetch.java  
*/  
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();  
   
   
}  
}  
/*  
**FileSplitterFetch.java  
*/  
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;  
}  
   
   
}  
   
   
/*  
**FileAccess.java  
*/  
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;  
}  
   
   
}  
   
   
/*  
**SiteInfoBean.java  
*/  
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;  
}  
}  
   
   
/*  
**Utility.java  
*/  
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);  
}  
}  
   
   
/*  
**TestMethod.java  
*/  
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();  
}  
}  
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xiaodongvtion/article/details/7957006

智能推荐

mktemp linux,Linux mktemp 命令使用方法-程序员宅基地

文章浏览阅读213次。原标题:Linux mktemp 命令使用方法Linux mktemp命令用于建立暂存文件。mktemp建立的一个暂存文件,供shell 使用。创建临时文件或者目录,这样的创建方式是安全的。此命令的适用范围:RedHat、RHEL、Ubuntu、CentOS、SUSE、openSUSE、Fedora。语法mktemp [-qu][文件名参数]参数:-q  执行时若发生错误,不会显示任何信息。-u ..._xbfi

API接口技术开发1688阿里巴巴alibabaAPI请求获取宝贝详情页数据、原价、销量、主图等参数可支持高并发调用接入演示-程序员宅基地

文章浏览阅读637次,点赞26次,收藏8次。请注意,由于1688的API可能会有调用频率的限制,因此在高并发场景下,你可能需要实现更复杂的逻辑来管理API密钥和访问令牌,以及处理API请求的排队和重试机制。此外,如果1688开放平台有提供特定的SDK,可以使用SDK来简化开发过程。

docker部署nginx_docker 部署nginx-程序员宅基地

文章浏览阅读1.2k次,点赞26次,收藏23次。本文讲述docker如何部署nginx的详细步骤和解释。_docker 部署nginx

PyCharm无法索引cannot find declaration to go to||CTRL+也不起作用(已解决)-程序员宅基地

文章浏览阅读9.9k次,点赞4次,收藏2次。如题所述,见下图问题解决很简单:方案一:file–&amp;gt;close project然后重新导入方案二:file–&amp;gt;settings–&amp;gt;project—&amp;gt;project interpreter绑定解释器方案三:真的是代码写错了,重新写一下,上级目录要带上...2018-12-14 22:15:18写于滨州市博兴县..._pycharm无法索引

十、卷积神经网络知识和二维卷积层计算(3.7学习笔记)_二维卷积后的参数怎么计算-程序员宅基地

文章浏览阅读519次。进入卷积神经网络学习_二维卷积后的参数怎么计算

如何修改Tomcat服务器Server Locations-程序员宅基地

文章浏览阅读1.4k次。首先双击我们集成好的Tomcat服务器 ,进入修改页面,修改Server Locations选项 , 会发现 Server Locations 灰色不可选,如果需要更改设置,则需要移除与Tomcat服务器关联的项目,同时,鼠标右键菜单Clean清除Tomcat服务器的状态,就可..._server locations

随便推点

在数据库层面分析系统性能(原创)-程序员宅基地

文章浏览阅读301次。系统级别信息v$sysstat按照OracleDocument中的描述,v$sysstat存储自数据库实例运行那刻起就开始累计全实例(instance-wide)的资源使用情况。该视图存储下列的统计信息:1&gt;.事件发生次数的统计(如:user commits)2&gt;.数据产生,存取或者操作的total列(如:redo size)3&gt;.如果TIMED_STATISTIC..._v$sysstat字段说明

c++配置libtorch_libtorch c++ param_groups-程序员宅基地

文章浏览阅读675次,点赞8次,收藏14次。CUDA版本最好选与本机一致的版本进行使用,但是我的经验告诉我即使下载的版本和电脑安装的CUDA版本不一致,但只要能兼容也可以使用。比如我下载的LIbTorch的CUDA版本是11.7但是电脑的CUDA版本是12.0,也是可以正常使用的,其中CUDA版本要大于等于LIbTorch的CUDA版本。把libtorch/lib中的所有dll放到libtorch/bin中,然后把libtorch/bin加到环境变量的path中.1、由于找不到xxx.dll,无法继续执行代码,重新安装程序可能会解决此问题。_libtorch c++ param_groups

PHP与ASP.NET:如何选择合适PHP?-程序员宅基地

文章浏览阅读353次。Are you a business owner looking for 您是正在寻找PHP web development services or ASP.Net development services, but unable to decide the right technology for your project? Are you looking for the PHP Web开..._网站 asp asp.net php如何选择

PIC16LC66-04I/SO 8位微控制器 -MCU 中文资料PDF参数-程序员宅基地

文章浏览阅读370次,点赞9次,收藏5次。工厂Lead Time:通常为4周 PIC16LC66-04I/SO是一种低功耗、高性能的8位CMOS微控制器,适用于各种嵌入式应用。PIC16LC66-04I/SO可以通过Microchip的MPLAB X IDE进行编程和调试。在使用该微控制器时,请务必查看其数据手册和参考手册,以了解其引脚定义、功能、应用电路图、电压和使用方法等详细信息。产品类型:8-bit Microcontrollers - MCU。器件名称:PIC16LC66-04I/SO。输入/输出端数量:22 I/O。

以太坊中私钥、公钥、账户地址详解_eth私钥地址分享-程序员宅基地

文章浏览阅读1.6w次,点赞2次,收藏14次。在学习以太坊的过程中,有些描述告诉我:公钥和账户地址有关系。一个以太坊地址就代表着一个以太坊账户,地址是账户的标识。对于外部账户来说,地址表示的是该账户公钥的后20字节(通常会以0x开头,例如,0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826,该地址使用的是16进制表示法2)。自己的疑问:那私钥呢?就是我们创建账户时,输入的密码吗?这也太简单了吧?..._eth私钥地址分享

基于springboot+vue.js的图书管理系统附带文章和源代码设计说明文档ppt-程序员宅基地

文章浏览阅读817次,点赞18次,收藏18次。博主介绍:CSDN特邀作者、985计算机专业毕业、某互联网大厂高级全栈开发程序员、码云/掘金/华为云/阿里云/InfoQ/StackOverflow/github等平台优质作者、专注于Java、小程序、前端、python等技术领域和毕业项目实战,以及程序定制化开发、全栈讲解、就业辅导、面试辅导、简历修改。精彩专栏 推荐订阅2023-2024年最值得选的微信小程序毕业设计选题大全:100个热门选题推荐2023-2024年最值得选的Java毕业设计选题大全:500个热门选题推荐。

推荐文章

热门文章

相关标签