Range
请求头,结合响应头Accept-Ranges
、Content-Range
可以实现如下功能:1、断点续传。用于下载文件被中断后,继续下载。可避免已下载的部分重复下载。
2、大文件指定区块下载。如视频、音频拖动播放,直接定位到指定位置下载内容。可避免每次都读取、传输整个文件,从而提升服务器性能。
3、大文件分包批量下载,再合并为完整文件。可提高下载速度。
1、规则要点
Range
表示请求的数据起始位置。响应头Accept-Ranges: bytes
表示支持续传。响应头Content-Range
表示返回的起始位置、总长度。Range
的数字,首尾都包含,长度是:end - begin + 1Range
指定的长度,只是意向下载量,服务端不一定要返回请求的长度。比如:bytes=0-
,表示希望下载整个文件,但服务端可以只返回有限长度的数据块(有利于性能)。但数据起始位置start需按照请求。2、规则参考:https://tools.ietf.org/html/r...
3、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); } } } }