java实现断点续传的原理

简介: <p style="color:rgb(54,46,43); line-height:26px"><span style="font-family:Microsoft YaHei; font-size:18px">其实断点续传的原理很简单,就是在Http的请求上和一般的下载有所不同而已。 <br> 打个比方,浏览器请求服务器上的一个文时,所发出的请求如下: <br> 假设服务器域名为w

其实断点续传的原理很简单,就是在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了。

服务端代码:

/* 
	   文件名可存为: Download.jsp 
	   HTTP 协议的请求与响应的会话过程可通过使用 FlashGet 下载 Http:// 连接的过程监视: 
	   蓝色部分为: 客户端请求 
	   紫色部分为: 服务器端响应 
	   如图: 
	   http://blog.csdn.net/images/blog_csdn_net/playyuer/30110/o_FlashGet.gif 
	   或参阅,后面的 FlashGet 会话列表: 
	   
	*/ 
	  //你可以使用你服务器上的文件及其路径 
	  String s = "I://SetupRes//Sun//j2re-1_4_2_05-windows-i586-p.exe"; 
	  //String s = "e://tree.mdb"; 

	  //经测试 RandomAccessFile 也可以实现,有兴趣可将注释去掉,并注释掉 FileInputStream 版本的语句 
	  //java.io.RandomAccessFile raf = new java.io.RandomAccessFile(s,"r"); 

	  java.io.File f = new java.io.File(s); 
	  java.io.FileInputStream fis = new java.io.FileInputStream(f); 

	  response.reset(); 

	  response.setHeader("Server", "playyuer@Microshaoft.com"); 

	  //告诉客户端允许断点续传多线程连接下载 
	  //响应的格式是: 
	  //Accept-Ranges: bytes 
	  response.setHeader("Accept-Ranges", "bytes"); 

	  long p = 0; 
	  long l = 0; 
	  //l = raf.length(); 
	  l = f.length(); 

	  //如果是第一次下,还没有断点续传,状态是默认的 200,无需显式设置 
	  //响应的格式是: 
	  //HTTP/1.1 200 OK 

	  if (request.getHeader("Range") != null) //客户端请求的下载的文件块的开始字节 
	  { 
	   //如果是下载文件的范围而不是全部,向客户端声明支持并开始文件块下载 
	   //要设置状态 
	   //响应的格式是: 
	   //HTTP/1.1 206 Partial Content 
	   response.setStatus(javax.servlet.http.HttpServletResponse.SC_PARTIAL_CONTENT);//206 

	   //从请求中得到开始的字节 
	   //请求的格式是: 
	   //Range: bytes=[文件块的开始字节]- 
	   p = Long.parseLong(request.getHeader("Range").replaceAll("bytes=","").replaceAll("-","")); 
	  } 

	  //下载的文件(或块)长度 
	  //响应的格式是: 
	  //Content-Length: [文件的总大小] - [客户端请求的下载的文件块的开始字节] 
	  response.setHeader("Content-Length", new Long(l - p).toString()); 

	  if (p != 0) 
	  { 
	   //不是从最开始下载, 
	   //响应的格式是: 
	   //Content-Range: bytes [文件块的开始字节]-[文件的总大小 - 1]/[文件的总大小] 
	   response.setHeader("Content-Range","bytes " + new Long(p).toString() + "-" + new Long(l -1).toString() + "/" + new Long(l).toString()); 
	  } 

	  //response.setHeader("Connection", "Close"); //如果有此句话不能用 IE 直接下载 

	  //使客户端直接下载 
	  //响应的格式是: 
	  //Content-Type: application/octet-stream 
	  response.setContentType("application/octet-stream"); 

	  //为客户端下载指定默认的下载文件名称 
	  //响应的格式是: 
	  //Content-Disposition: attachment;filename="[文件名]" 
	  //response.setHeader("Content-Disposition", "attachment;filename=/"" + s.substring(s.lastIndexOf("//") + 1) + "/""); //经测试 RandomAccessFile 也可以实现,有兴趣可将注释去掉,并注释掉 FileInputStream 版本的语句 
	  response.setHeader("Content-Disposition", "attachment;filename=/"" + f.getName() + "/""); 

	  //raf.seek(p); 
	  fis.skip(p); 

	  byte[] b = new byte[1024]; 
	  int i; 


	  //while ( (i = raf.read(b)) != -1 ) //经测试 RandomAccessFile 也可以实现,有兴趣可将注释去掉,并注释掉 FileInputStream 版本的语句 
	  while ( (i = fis.read(b)) != -1 ) 
	  { 
	   response.getOutputStream().write(b,0,i); 
	  } 
	  //raf.close();//经测试 RandomAccessFile 也可以实现,有兴趣可将注释去掉,并注释掉 FileInputStream 版本的语句 
	  fis.close(); 

客户端测试代码:

public static void down(String URL,long nPos,String savePathAndFile){
 HttpURLConnection conn =null;
 try{
/*  String content="<?xml version=/"1.0/" encoding=/"utf-8/" ?>"
    +"<xmlRequest>"
    +"<header>04</header>"
    +"<body>"
    +"<user userName=/"222/" password=/"222222/" currentVision=/"20101020165216/" >"
    +"</user>"
    +"</body>"
    +"</xmlRequest>";
*/
   conn = (HttpURLConnection)new URL(URL).openConnection();
/*   conn.setRequestProperty("content-type", "text/html");
   conn.setRequestProperty("User-Agent", "NetFox");// 设置User-Agent
   conn.setRequestProperty("RANGE", "bytes=" + nPos);// 设置断点续传的开始位置
   conn.setRequestMethod("POST"); //设置请求方法为POST, 也可以为GET   
   conn.setDoInput(true);
   conn.setDoOutput(true);   

   OutputStream outStream = conn.getOutputStream();
   PrintWriter out = new PrintWriter(outStream);
   out.print(content);
   out.flush();
   out.close();
*/  
  // 获得输入流
  InputStream input = conn.getInputStream();
  RandomAccessFile oSavedFile = new RandomAccessFile(savePathAndFile,
    "rw");
  // 定位文件指针到nPos位置
  oSavedFile.seek(nPos);
  byte[] b = new byte[1024];
  int nRead;
  // 从输入流中读入字节流,然后写到文件中
  while ((nRead = input.read(b, 0, 1024)) > 0) {
   (oSavedFile).write(b, 0, nRead);
  }
  conn.disconnect();
 } catch (MalformedURLException e) {
  e.printStackTrace();
 } catch (IOException e) {
  e.printStackTrace();
 }
}
public static void main(String[] args) {
 String url = "http://localhost:8181/ssoapp/clientRequest";
 String savePath = "e://";
 String fileName = "4.ppt";
 String fileNam = fileName;
 HttpURLConnection conn = null;
 try {
  
 } catch (Exception e) {
  e.printStackTrace();
 }
 File file = new File(savePath + fileName);

 int i = 0;
 if (file.exists()) {
  // 先看看是否是完整的,完整,换名字,跳出循环,不完整,继续下载
  long localFileSize = file.length();
  System.out.println("已有文件大小为:" + localFileSize);

  if (localFileSize >0) {
   System.out.println("文件续传");
   down(url, localFileSize, savePath + fileName);
  } else {
   System.out.println("文件存在,重新下载");
   down(url, 0, savePath + fileName);
  }
 } else {
  try {
   file.createNewFile();
   System.out.println("下载中");
   down(url, 0, savePath + fileName);
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
}


相关文章
|
2月前
|
存储 Java 关系型数据库
高效连接之道:Java连接池原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。频繁创建和关闭连接会消耗大量资源,导致性能瓶颈。为此,Java连接池技术通过复用连接,实现高效、稳定的数据库连接管理。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接池的基本操作、配置和使用方法,以及在电商应用中的具体应用示例。
83 5
|
3月前
|
存储 算法 Java
Java HashSet:底层工作原理与实现机制
本文介绍了Java中HashSet的工作原理,包括其基于HashMap实现的底层机制。通过示例代码展示了HashSet如何添加元素,并解析了add方法的具体过程,包括计算hash值、处理碰撞及扩容机制。
|
19天前
|
监控 Java API
探索Java NIO:究竟在哪些领域能大显身手?揭秘原理、应用场景与官方示例代码
Java NIO(New IO)自Java SE 1.4引入,提供比传统IO更高效、灵活的操作,支持非阻塞IO和选择器特性,适用于高并发、高吞吐量场景。NIO的核心概念包括通道(Channel)、缓冲区(Buffer)和选择器(Selector),能实现多路复用和异步操作。其应用场景涵盖网络通信、文件操作、进程间通信及数据库操作等。NIO的优势在于提高并发性和性能,简化编程;但学习成本较高,且与传统IO存在不兼容性。尽管如此,NIO在构建高性能框架如Netty、Mina和Jetty中仍广泛应用。
28 3
|
19天前
|
安全 算法 Java
Java CAS原理和应用场景大揭秘:你掌握了吗?
CAS(Compare and Swap)是一种乐观锁机制,通过硬件指令实现原子操作,确保多线程环境下对共享变量的安全访问。它避免了传统互斥锁的性能开销和线程阻塞问题。CAS操作包含三个步骤:获取期望值、比较当前值与期望值是否相等、若相等则更新为新值。CAS广泛应用于高并发场景,如数据库事务、分布式锁、无锁数据结构等,但需注意ABA问题。Java中常用`java.util.concurrent.atomic`包下的类支持CAS操作。
52 2
|
2月前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
2月前
|
Java
Java之CountDownLatch原理浅析
本文介绍了Java并发工具类`CountDownLatch`的使用方法、原理及其与`Thread.join()`的区别。`CountDownLatch`通过构造函数接收一个整数参数作为计数器,调用`countDown`方法减少计数,`await`方法会阻塞当前线程,直到计数为零。文章还详细解析了其内部机制,包括初始化、`countDown`和`await`方法的工作原理,并给出了一个游戏加载场景的示例代码。
Java之CountDownLatch原理浅析
|
2月前
|
Java 索引 容器
Java ArrayList扩容的原理
Java 的 `ArrayList` 是基于数组实现的动态集合。初始时,`ArrayList` 底层创建一个空数组 `elementData`,并设置 `size` 为 0。当首次添加元素时,会调用 `grow` 方法将数组扩容至默认容量 10。之后每次添加元素时,如果当前数组已满,则会再次调用 `grow` 方法进行扩容。扩容规则为:首次扩容至 10,后续扩容至原数组长度的 1.5 倍或根据实际需求扩容。例如,当需要一次性添加 100 个元素时,会直接扩容至 110 而不是 15。
Java ArrayList扩容的原理
|
2月前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
73 2
|
2月前
|
Java 数据格式 索引
使用 Java 字节码工具检查类文件完整性的原理是什么
Java字节码工具通过解析和分析类文件的字节码,检查其结构和内容是否符合Java虚拟机规范,确保类文件的完整性和合法性,防止恶意代码或损坏的类文件影响程序运行。
60 5
|
2月前
|
算法 Java 数据库连接
Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性
本文详细介绍了Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性。连接池通过复用数据库连接,显著提升了应用的性能和稳定性。文章还展示了使用HikariCP连接池的示例代码,帮助读者更好地理解和应用这一技术。
64 1