服务端基于Http的Range头规则实现断点续传或分段下载(C#)

Http的Range请求头,结合响应头Accept-RangesContent-Range可以实现如下功能:

1、断点续传。用于下载文件被中断后,继续下载。可避免已下载的部分重复下载。
2、大文件指定区块下载。如视频、音频拖动播放,直接定位到指定位置下载内容。可避免每次都读取、传输整个文件,从而提升服务端性能。
3、大文件分包批量下载,再合并为完整文件。可提高下载速度。

Http的Range相关说明:

1、Header格式:

  • 请求头:Range: bytes=START-END
  • 响应头:Accept-Ranges: bytes
  • 响应头:Content-Range: bytes=START-END

2、规则要点

  • 请求头Range表示请求的数据起始位置。响应头Accept-Ranges: bytes表示支持续传。响应头Content-Range表示返回的起始位置、总长度。
  • 请求头Range的数字,首尾都包含,长度是:end - begin + 1
  • 请求头Range指定的长度,只是意向下载量,服务端不一定要返回请求的长度。比如:bytes=0-,表示希望下载整个文件,但服务端可以只返回有限长度的数据块(有利于性能)。但数据起始位置start需按照请求。

3、规则参考:https://tools.ietf.org/html/r...
4、Range参数解析

Http "Range" header:
    The first 500 bytes (byte offsets 0-499, inclusive):
        Range: bytes=0-499
    The second 500 bytes (byte offsets 500-999, inclusive):
        Range: bytes=500-999
    Additional examples, assuming a representation of length 10000, The final 500 bytes (byte offsets 9500-9999, inclusive):
        Range: bytes=-500
        Or:
        Range: bytes=9500-

话不多说,直接上代码:

using System;
using System.Configuration;
using System.IO;
using System.Net;

// 需要续传的下载页面,从这个类继承即可
public class DownloadPage : System.Web.UI.Page
{
    static int HttpRangeSize = 3 * 1024 * 1024; //断点续传的最大区块长度, 默认3M

    protected void WriteFile(string fn)
    {
        using (var fs = File.OpenRead(fn))
        {
            WriteStream(fs);
        }
    }
    protected void WriteStream(Stream fs)
    {
        var range = (Request.Headers["Range"] ?? "").Trim().ToLower();
        //请求包含了Range要求,并且源数据流支持定位(如FileStream、MemoryStream,网络流一般不支持),才进行续传
        if (fs.CanSeek && range.StartsWith("bytes") && range.Contains("-"))
        {
            int start = -1, end = -1;
            var rgs = range.Substring(6).Split('-');
            int.TryParse(rgs[0], out start);
            int.TryParse(rgs[1], out end);
            if (rgs[0] == "") // bytes=-500
            {
                start = (int)fs.Length - end;
                end = (int)fs.Length - 1;
            }
            if (rgs[1] == "") // bytes=9500-
            {
                end = (int)fs.Length - 1;
            }
            WriteRangeStream(fs, start, end);
        }
        else
        {
            WriteAllStream(fs);
        }
    }
    private void WriteAllStream(Stream fs)
    {
        using (fs)
        {
            int l;
            byte[] buffer = new byte[40960];
            while ((l = fs.Read(buffer, 0, buffer.Length)) > 0)
            {
                Response.OutputStream.Write(buffer, 0, l);
            }
        }
    }
    //断点续传的附件下载,如视频
    private void WriteRangeStream(Stream fs, int start, int end)
    {
        using (fs)
        {
            int rangeLen = end - start + 1;
            if (rangeLen > 0)
            {
                if (rangeLen > HttpRangeSize) //每段默认限制最大3M
                {
                    rangeLen = HttpRangeSize;
                    end = start + HttpRangeSize - 1;
                }
            }
            else
            {
                throw new Exception("Range error");
            }

            long size = -1;
            try
            {
                size = fs.Length;
            }
            catch { }
            //如果是整个文件,返回200。否则返回206
            var status = start == 0 && end + 1 >= size ? HttpStatusCode.OK : HttpStatusCode.PartialContent;
            Response.StatusCode = (int)status;
            Response.AddHeader("Accept-Ranges", "bytes");
            //Content-Range形式:【bytes 起始位置-结束位置/总长度】。注意前置的bytes+空格不能少!
            Response.AddHeader("Content-Range", string.Format("bytes {0}-{1}/{2}", start, end, size == -1 ? "*" : size.ToString()));
            //Content-Length非必须。.Net也会自动生成。对批量分包下载有帮助
            //Response.AddHeader("Content-Length", rangeLen.ToString());

            int readLen, total = 0;
            byte[] buffer = new byte[40960];
            //流定位到指定位置
            fs.Seek(start, SeekOrigin.Begin);
            while (total < rangeLen && (readLen = fs.Read(buffer, 0, buffer.Length)) > 0)
            {
                total += readLen;
                if (total > rangeLen) //超出的长度,截取
                {
                    readLen -= (total - rangeLen);
                    total = rangeLen;
                }
                Response.OutputStream.Write(buffer, 0, readLen);
            }
        }
    }
}

前后端研究
前后端研究

A front-end enthusiast!

489 声望
28 粉丝
0 条评论
推荐阅读
苹果IOS解析ISO8601格式的日期(结尾带不带Z)导致时区不对的问题
ISO8601格式下,结尾不带Z的日期,应为当前时区的时间,但苹果IOS下浏览器new Date()时解析有bug,把它解析为了UTC时间,导致在中国相差8小时。比如:2022-09-19T13:00:00 对应 Mon Sep 19 2022 13:00:00 GMT+080...

okfine阅读 341

从零搭建 Node.js 企业级 Web 服务器(十五):总结与展望
总结截止到本章 “从零搭建 Node.js 企业级 Web 服务器” 主题共计 16 章内容就更新完毕了,回顾第零章曾写道:搭建一个 Node.js 企业级 Web 服务器并非难事,只是必须做好几个关键事项这几件必须做好的关键事项就...

乌柏木60阅读 6k评论 16

从零搭建 Node.js 企业级 Web 服务器(一):接口与分层
分层规范从本章起,正式进入企业级 Web 服务器核心内容。通常,一块完整的业务逻辑是由视图层、控制层、服务层、模型层共同定义与实现的,如下图:从上至下,抽象层次逐渐加深。从下至上,业务细节逐渐清晰。视图...

乌柏木41阅读 7.1k评论 6

从零搭建 Node.js 企业级 Web 服务器(二):校验
校验就是对输入条件的约束,避免无效的输入引起异常。Web 系统的用户输入主要为编辑与提交各类表单,一方面校验要做在编辑表单字段与提交的时候,另一方面接收表单的接口也要做足校验行为,通过前后端共同控制输...

乌柏木32阅读 6.1k评论 9

从零搭建 Node.js 企业级 Web 服务器(五):数据库访问
回顾 从零搭建 Node.js 企业级 Web 服务器(一):接口与分层,一块完整的业务逻辑是由视图层、控制层、服务层、模型层共同定义与实现的,控制层与服务层实现了业务处理过程,模型层定义了业务实体并以 对象-关系...

乌柏木32阅读 4.5k评论 9

从零搭建 Node.js 企业级 Web 服务器(十三):断点调试与性能分析
Node.js 官方提供了断点调试机制,出于安全性考虑默认为关闭状态,可以通过 node 参数 --inspect 或 --inspect-brk 开启,配合 IDE 能够非常方便地调试代码,本章就上一章已完成的项目 licg9999/nodejs-server-ex...

乌柏木29阅读 3.8k评论 9

从零搭建 Node.js 企业级 Web 服务器(八):网络安全
计算机网络依据 TCP/IP 协议栈分为了物理层、网络层、传输层、应用层,通常基础设施供应商会解决好前三层的网络安全问题,需要开发者自行解决应用层的网络安全问题,本章将着重表述应用层常见的网络安全问题及处...

乌柏木31阅读 5.7k评论 1

A front-end enthusiast!

489 声望
28 粉丝
宣传栏