C# 大文件分块下载
Response
Http 协议中有专门的指令来告知浏览器, 本次响应的是一个需要下载的文件. 格式如下:
Content-Disposition: attachment;filename=filename.ext
以上指令即标记此次响应流是附件,且附件文件名为 filename.ext。
文件下载方式:
protected void Page_Load(object sender, EventArgs e) { Response.WriteFile("Tree.jpg"); Response.Flush(); Response.Close(); } protected void Page_Load(object sender, EventArgs e) { Response.BinaryWrite(File.ReadAllBytes(Server.MapPath("Tree.jpg"))); Response.Flush(); Response.Close(); } protected void Page_Load(object sender, EventArgs e) { int chunkSize = 64; byte[] buffer = new byte[chunkSize]; int offset = 0; int read = 0; using (FileStream fs = File.Open(Server.MapPath("Tree.jpg"), FileMode.Open, FileAccess.Read, FileShare.Read)) { while ((read = fs.Read(buffer, offset, chunkSize)) > 0) { Response.OutputStream.Write(buffer, 0, read); Response.Flush(); } } Response.Close(); }
1.分块下载服务器物理路径下的文件
/// <summary> /// 使用OutputStream.Write分块下载文件 /// </summary> /// <param name="filePath"></param> public void WriteFileBlock(string filePath) { filePath = Server.MapPath(filePath); if (!File.Exists(filePath)) { return; } FileInfo info = new FileInfo(filePath); //指定块大小 long chunkSize = 4096; //建立一个4K的缓冲区 byte[] buffer = new byte[chunkSize]; //剩余的字节数 long dataToRead = 0; FileStream stream = null; try { //打开文件 stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); dataToRead = stream.Length; //添加Http头 HttpContext.Current.Response.ContentType = "application/octet-stream"; HttpContext.Current.Response.AddHeader("Content-Disposition", "attachement;filename=" + Server.UrlEncode(info.FullName)); HttpContext.Current.Response.AddHeader("Content-Length", dataToRead.ToString()); while (dataToRead > 0) { if (HttpContext.Current.Response.IsClientConnected) { int length = stream.Read(buffer, 0, Convert.ToInt32(chunkSize)); HttpContext.Current.Response.OutputStream.Write(buffer, 0, length); HttpContext.Current.Response.Flush(); HttpContext.Current.Response.Clear(); dataToRead -= length; } else { //防止client失去连接 dataToRead = -1; } } } catch (Exception ex) { HttpContext.Current.Response.Write("Error:" + ex.Message); } finally { if (stream != null) { stream.Close(); } HttpContext.Current.Response.Close(); } }
2.使用WebClient下载http文件到客户端。分块下载,防止大文件下载阻塞浏览器。
Response.Flush和Response.BufferOutput
Response.Flush方法用来将缓冲区的数据立即输出到浏览器当中。你可以多次调用Response.Flush 方法,当这样使用时,浏览器将多次接受数据,而不是仅接受一次数据。
Response.BufferOutput是一个布尔值,指示是否缓冲输出并在整个页面在服务器端处理完毕后才发送缓冲区中的数据。true是其默认值。
服务器端是否缓存数据取决于Response.BufferOutput,当你将Response.BufferOutput的值设为true时,数据会缓存到buffer中,并在页面处理完毕后,将buffer中的内容一次性全部发到客户端。如果为false,则不缓冲数据,每执行一个response.write方法,数据就会立即发往客户端,数据的传送次数取决于你使用了多少个response.write方法,在这种情况下,使用response.Flush方法是没有意义的。只用当你将Response.BufferOutput属性的值设为true时,使用response.Flush方法才有意义。这时服务器端会将调用response.Flush方法时之前的所有response.write方法的数据发往客户端。
只要将Response.BufferOutput的值设置为true,一定会发送buffer里的内容,只是早晚、次数的问题,这就取决于Response.Flush方法了。
至于它们的作用,在一个很大很大的网页中,可以使用Response.Flush方法将数据分批发往客户端,这样就可以使浏览器先呈现一些html代码,并逐步完整呈现。这样可使用户减少等待时间。不过你要注意一下,发送的html代码必须是闭合完整的,否则有的浏览器不会立即呈现html,而是等待接受完整的html才呈现。否则使用它就没有效果了。
public void DownloadFile(string fileUri, string programId) { Stream stream = null; try { var fileName = programId + DateTime.Now.ToString("_HHmmssms") + ".ts"; using (var client = new WebClient()) { client.Proxy = null; stream = client.OpenRead(fileUri); Response.Clear(); //Response.BufferOutput = false; Response.ContentType = "video/mpeg"; //通知浏览器下载文件而不是打开 Response.AddHeader("Content-Disposition", "attachment; filename=" + HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8)); int bytesRead = 0; long chunkSize = 4096; //指定块大小 byte[] buffer = new byte[chunkSize];//建立一个4K的缓冲区 while (Response.IsClientConnected && (bytesRead = stream.Read(buffer, 0, Convert.ToInt32(chunkSize))) > 0) { Response.OutputStream.Write(buffer, 0, bytesRead); Response.Flush();//将缓冲的输出数据发送到客户端 Response.Clear(); } } } catch (Exception ex) { throw ex; } finally { if (stream != null) { stream.Close(); } Response.Close(); } }
3.文件断点续传
/// <summary> /// 文件断点续传下载 /// </summary> /// <param name="httpContext"></param> /// <param name="filePath"></param> /// <param name="speed"></param> /// <returns></returns> public static bool DownloadFile(HttpContext httpContext, string filePath, long speed) { bool ret = true; try { switch (httpContext.Request.HttpMethod.ToUpper()) { //support Get and head method case "GET": case "HEAD": break; default: httpContext.Response.StatusCode = 501; return false; } if (!File.Exists(filePath)) { httpContext.Response.StatusCode = 404; return false; } long startBytes = 0; int packSize = 1024 * 10; //read in block,every block 10K bytes string fileName = Path.GetFileName(filePath); FileStream myFile = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); BinaryReader br = new BinaryReader(myFile); long fileLength = myFile.Length; int sleep = (int)Math.Ceiling(1000.0 * packSize / speed);//the number of millisecond string lastUpdateTiemStr = File.GetLastWriteTimeUtc(filePath).ToString("r"); string eTag = HttpUtility.UrlEncode(fileName, Encoding.UTF8) + lastUpdateTiemStr; //validate whether the file is too large if (myFile.Length > Int32.MaxValue) { httpContext.Response.StatusCode = 413; return false; } if (httpContext.Request.Headers["If-Range"] != null) { if (httpContext.Request.Headers["If-Range"].Replace("\"", "") != eTag) { httpContext.Response.StatusCode = 412; return false; } } try { #region -------添加重要响应头、解析请求头、相关验证------------------- httpContext.Response.Clear(); httpContext.Response.BufferOutput = false; //httpContext.Response.AddHeader("Content-MD5", GetMD5Hash(myFile)); httpContext.Response.AddHeader("Accept-Ranges", "bytes"); httpContext.Response.AppendHeader("ETag", "\"" + eTag + "\""); httpContext.Response.AppendHeader("Last-Modified", lastUpdateTiemStr); httpContext.Response.ContentType = "application/octet-stream"; httpContext.Response.AddHeader("Content-Disposition", "attachment;filename=" + HttpUtility.UrlEncode(fileName, Encoding.UTF8).Replace("+", "%20")); httpContext.Response.AddHeader("Content-Length", (fileLength - startBytes).ToString()); httpContext.Response.AddHeader("Connection", "Keep-Alive"); httpContext.Response.ContentEncoding = Encoding.UTF8; if (httpContext.Request.Headers["Range"] != null) { httpContext.Response.StatusCode = 206; string[] range = httpContext.Request.Headers["Range"].Split(new char[] { ‘=‘, ‘-‘ }); startBytes = Convert.ToInt64(range[1]); if (startBytes < 0 || startBytes >= fileLength) { return false; } } if (startBytes > 0) { // 如果是续传请求,告诉客户端本次的开始字节数,总长度,以便客户端将续传数据追加到startBytes位置后 httpContext.Response.AddHeader("Content-Range", string.Format(" bytes {0}-{1}/{2}", startBytes, fileLength - 1, fileLength)); } #endregion #region -------向客户端发送数据块------------------- //send data br.BaseStream.Seek(startBytes, SeekOrigin.Begin); int maxCount = (int)Math.Ceiling((fileLength - startBytes + 0.0) / packSize);//download in block for (int i = 0; i < maxCount && httpContext.Response.IsClientConnected; i++) { httpContext.Response.BinaryWrite(br.ReadBytes(packSize)); httpContext.Response.Flush(); if (sleep > 1) Thread.Sleep(sleep); } #endregion } catch { ret = false; } finally { br.Close(); myFile.Close(); } } catch { ret = false; } return ret; }