断点续传下载原理实现

需求背景

  • 动态创建的文件下载的时候希望浏览器显示下载进度
  • 动态创建的文件希望能够分段下载

HTTP断点续传报文

要实现HTTP断点续传必须要简单了解以下几个报文。

  • Accept-Ranges 告诉客户端(浏览器..)服务器端支持断点续传 服务器端返回
  • Range 客户端告诉服务器端从指定的的位置/范围(这里值字节数)下载资源 客户端发出
  • Content-Range 服务器端告诉客户端响应的数据信息,在整个返回体中本部分的字节位置 服务器端返回
  • ETag 资源标识 非必须 服务器端返回
  • Last-Modified 资源最后一次更新的时间 非必须 服务器端返回

Range 的范围格式

  1. 表示0-499个字节范围:Range: bytes=0-499
  2. 表示最后500个字节范围:Range: bytes=-500
  3. 表示500字节开始到结束范围:Range: bytes=500-
  4. 表示第一个和最后一个字节:Range: bytes=0-0,-1
  5. 表示同时指定几个范围:Range: bytes=500-600,601-999

Content-Range 的数据格式

Content-Range: bytes 0-499/22036 :表示返回0-499字节范围数据 资源一共22036个字节

原理

  1. 客户端发起请求 设置Range指定开始字节数或结束字节数 如果是从0开始也可以不用设置。
  2. 服务器端检查到客户端Range头 解析开始字节数以及结束字节数 并返回报文头 Accept-Ranges表示支持断点续传,Content-Range记录该次向客户端写入流的位置信息,然后再写入流到客户端。
  3. 服务端可以使用ETag Last-Modified 标记一下资源是否被修改。作一些验证工作,如果验证不通过则返回错误,非必须项。

java实现

 OutputStream os=null;
    InputStream inputStream =null;
    File zipFile=null;
    try{
        long zipStart=System.currentTimeMillis();
        zipFile=createFile();//动态根据业务创建文件
        if(logger.isInfoEnabled()){
            logger.info(String.format("压缩ZIP 花费时间 %s(s) ",
        (System.currentTimeMillis()-zipStart)/1000));
        }
        if (zipFile.exists()) {
            long downloadStart=System.currentTimeMillis();
            inputStream= new BufferedInputStream(new FileInputStream(zipFile));
            response.reset();
            os=new BufferedOutputStream(response.getOutputStream());
            String userAgent = request.getHeader("USER-AGENT");
            String fileName=zipFile.getName();
            if (null != userAgent && -1 != userAgent.indexOf("MSIE")) {
                fileName = URLEncoder.encode(fileName, "UTF8");
            } else if (null != userAgent && -1 != userAgent.indexOf("Mozilla")) {
                fileName = new String(fileName.getBytes("utf-8"), "ISO-8859-1");
            }
            response.setHeader("Accept-Ranges", "bytes");
            response.setHeader("Content-Disposition",
        "attachment;filename="+ fileName);
            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
            long pos = 0, fileSize=zipFile.length(),
    last=fileSize-1;
            response.setHeader("ETag",zipFile.getName().
         concat(Objects.toString(fileSize))
                    .concat("_").concat(Objects.toString(zipFile.lastModified())));
            response.setDateHeader("Last-Modified",zipFile.lastModified());
            response.setDateHeader("Expires",
            System.currentTimeMillis()+1000*60*60*24);
            if (null != request.getHeader("Range")) {
                response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
                try {
                    // 暂时只处理这2种range格式 1、RANGE: bytes=111- 2、Range: bytes=0-499
                    String numRang = request.getHeader("Range")
            .replaceAll("bytes=", "");
                    String[] strRange = numRang.split("-");
                    if (strRange.length == 2) {
                        pos = Long.parseLong(strRange[0].trim());
                        last = Long.parseLong(strRange[1].trim());
                    } else {
                        pos = Long.parseLong(numRang.replaceAll("-", "").trim());
                    }
                } catch (NumberFormatException e) {
                    logger.error(request.getHeader("Range") + " error");
                    pos = 0;
                }
            }
            long rangLength = last - pos + 1;
            String contentRange = new StringBuffer("bytes ").
            append(String.valueOf(pos)).
            append("-").append(last).append("/").
            append(String.valueOf(fileSize)).toString();
            response.setHeader("Content-Range", contentRange);
            response.addHeader("Content-Length",Objects.toString(rangLength));
            if(pos>0){
                inputStream.skip(pos);
            }
            byte[] buffer = new byte[1024*512];//每次以512KB 0.5MB的流量下载
            int length = 0,sendTotal=0;
            while (sendTotal < rangLength && length!=-1) {
                length = inputStream.read(buffer, 0,
        ((rangLength - sendTotal) <= buffer.length ?
                        ((int) (rangLength - sendTotal)) : buffer.length));
                sendTotal = sendTotal + length;
                os.write(buffer, 0, length);
            }
            if(os!=null){
                os.flush();
            }
            if(logger.isInfoEnabled()){
                logger.info(String.format("下载 花费时间 %s(s) ",
        (System.currentTimeMillis()-downloadStart)/1000));
            }
        }
    }catch (Exception e){
        if(StringUtils.endsWithIgnoreCase(e.getMessage(),"Broken pipe")){
            logger.error("用户取消下载");
        }
        logger.error(e.getMessage(),e);
    }finally {
        if(os!=null){
            try{
                os.close();
            }catch (Exception e){}
        }
        if(inputStream!=null){
            try{
                IOUtils.closeQuietly(inputStream);
            }catch (Exception e){}
        }
    }
}

比如google浏览器下载的时候就能看到下载进度以及暂停下载和恢复下载操作,也可以设置Range测试分段下载。

时间: 2024-10-06 12:33:06

断点续传下载原理实现的相关文章

Android多线程断点续传下载原理及实现

这段时间看了看工作室的工具库的下载组件,发现其存在一些问题: 1.下载核心逻辑有 bug,在暂停下载或下载失败等情况时有概率无法顺利完成下载.2.虽然原来的设计是采用多线程断点续传的设计,但打了一下日志发现其实下载任务都是在同一个线程下串行执行,并没有起到加快下载速度的作用. 考虑到原来的代码并不复杂,因此对这部分下载组件进行了重写.这里记录一下里面的多线程断点续传功能的实现. 请查看完整的PDF版(更多完整项目下载.未完待续.源码.图文知识后续上传github.)可以点击关于我联系我获取完整P

HTTP断点续传下载的原理

frombegintoend原文HTTP断点续传下载的原理 要实现断点续传下载文件,首先要了解断点续传的原理.断点续传其实就是在上一次下载断开的位置开始继续下载,HTTP协议中,可以在请求报文头中加入Range段,来表示客户机希望从何处继续下载. 这是一个普通的下载请求: GET /test.txt HTTP/1.1 Accept:*/* Referer:http://192.168.1.96 Accept-Language:zh-cn Accept-Encoding:gzip,deflate

Java--使用多线程下载,断点续传技术原理(RandomAccessFile)

一.基础知识 1.什么是线程?什么是进程?它们之间的关系? 可以参考之前的一篇文章:java核心知识点学习----并发和并行的区别,进程和线程的区别,如何创建线程和线程的四种状态,什么是线程计时器 简单说一个进程可以由多个线程组成,一个操作系统可以多个进程,它们都是可以同时进行工作的. 2.什么是下载?如何多线程进行下载?如何断点续传? 广义上说,凡是在屏幕上看到的不属于本地计算机上的内容,皆是通过"下载"得来.狭义上人们只认为那些自定义了下载文件的本地磁盘存储位置的操作才是"

Android多线程断点续传下载

这个月接到一个项目,要写一个像360助手一样的对于软件管理的APP:其中,遇到了一个问题:多线程断点下载 这个 ,由于之前没有写过这方面的应用功能.所以,不免要自学了.然后就在各个昂站上收索并整理了一下.跟大家分享一下,也能加深我在这方面的理解. 什么是多线程下载? 多线程下载其实就是迅雷,BT一些下载原理,通过多个线程同时和服务器连接,那么你就可以榨取到较高的带宽了,大致做法是将文件切割成N块,每块交给单独一个线程去下载,各自下载完成后将文件块组合成一个文件,程序上要完成做切割和组装的小算法

多线程断点下载原理

上传到服务器原理 商议客户端将文件长度Length.文件名Name.Id文件id以协议的形式发送到服务器.服务器判断Id是否为空,不为空时表示是断点上传,从存储断点上传文件的数据库中根据文件Id查询文件保存的Path并将Path返回,根据Path从上次的历史文件中读取上传的断点位置(断点位置记录在临时文件的*.log文件里的Properties部分了) 如果Id为空,则为初次上传,生成id,将id和path添加到数据库里 然后向客户端发送协议头Id(?包括记录断点位置的变量position,=0

08_多线程下载原理

通过安卓的项目向服务端提交参数.用了三种方式,一种是httpUrlConnection,一种是httpClient,还有一种是使用开源项目去提交参数.掌握多线程下载和断点续传的原理.多线程下载可以把一个文件分成多份去下载.使用多线程下载如果你的带宽比较大,可能会突破对线程流量的限制.在服务端可以对每一个下载的线程的流量/网速/带宽设置一个限制.比如一个线程最多就是100KB/s,如果只是一个线程下载就是100KB/S,你要是开5个线程就是500KB/S.当然了服务端肯定也可以对一个ip连接过来的

Android实现网络多线程断点续传下载

本示例介绍在Android平台下通过HTTP协议实现断点续传下载. 我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多线程断点需要什么功能? 1.多线程下载, 2.支持断点. 使用多线程的好处:使用多线程下载会提升文件下载的速度.那么多线程下载文件的过程是: (1)首先获得下载文件的长度,然后设置本地文件的长度. HttpURLConnection.getContentLength();//获取下载文件的长度 Ra

PHP断点续传的原理与实现

PHP断点续传的原理与实现 断点续传主要是HTTP协议中的Content-Range报头.其理解如下: Content-Range:响应资源的范围.可以在多次请求中标记请求的资源范围,在连接断开重新连接时,客户端只请求该资源未被下载的部分,而不是重新请求整个资源,实现了断点续传.迅雷就是基于这个原理,使用多线程分段读取网络上的资源,最后合并.关于PHP使用多线程实现断点续传稍后讨论.本文只实现简单的断点续传. 代码实现: 先定义一个函数  getRange() 这个函数用来处理  header中

Android Netroid解析之——断点续传下载及问题修正

提到Netroid也许非常多人不知道这个框架,但我假设说Volley想必没有人不知道吧. Netroid是一个基于Volley实现的Android Http库.提供运行网络请求.缓存返回结果.批量图片载入.大文件断点下载的常见Http交互功能,关于网络请求,图片载入没什么好说的,Volley已经有非常多人解析过了,这里来说一下大文件断点下载. 关于大文件断点下载,网上也有非常多实现的demo,为什么要单单说Netroid呢?由于Netroid断点续传不依赖数据库,我在网上看到过非常多的断点续传的