在ASP.NET中流式传输文件的最佳方法

35
什么是使用ASP.NET进行流式传输文件的最佳方法?
似乎有多种方法可以实现这一目标,我目前正在使用Response.TransmitFile()方法在HTTP处理程序中发送文件到浏览器。其中包括从Web根目录外部发送FLV文件到嵌入式Flash视频播放器。
然而,这似乎不是一种可靠的方法。特别是在Internet Explorer(7)中存在一个奇怪的问题,在浏览一两个视频后,浏览器就会挂起。单击任何链接等都没有效果,使网站重新运行的唯一方法是关闭浏览器并重新打开它。
这在其他浏览器中也会发生,但频率要低得多。根据一些基本测试,我怀疑这与文件流传输方式有关...或许连接没有正确关闭或类似的原因。
尝试了几种不同的方法后,我发现以下方法对我有效:
Response.WriteFile(path);
Response.Flush();
Response.Close();
Response.End();

这种方法绕过了上述问题,现在浏览视频不再导致Internet Explorer挂起。

然而,我理解Response.WriteFile()会先将文件加载到内存中,鉴于某些流式传输的文件可能非常大,这似乎并不是一个理想的解决方案。

我很想听听其他开发人员如何在ASP.NET中流式传输大文件,尤其是流式传输FLV视频文件。


这是我使用的一种方法,它添加了可恢复下载功能,如果流式传输视频将非常有用:https://dev59.com/IG035IYBdhLWcg3wbPY5#6475414 - Michael
4个回答

51

我会将事物移到“aspx”管道之外。具体来说,我将编写一个运行处理程序(ashx或通过配置映射),它只做最小的工作,并仅按块写入响应。处理程序将像通常一样从查询字符串/表单中接受输入,找到要流式传输的对象,并使用循环中的适度大小的本地缓冲区流式传输数据。下面是一个简单(不完整)的示例:

public void ProcessRequest(HttpContext context) {
    // read input etx
    context.Response.Buffer = false;
    context.Response.ContentType = "text/plain";
    string path = @"c:\somefile.txt";
    FileInfo file = new FileInfo(path);
    int len = (int)file.Length, bytes;
    context.Response.AppendHeader("content-length", len.ToString());
    byte[] buffer = new byte[1024];
    Stream outStream = context.Response.OutputStream;
    using(Stream stream = File.OpenRead(path)) {
        while (len > 0 && (bytes =
            stream.Read(buffer, 0, buffer.Length)) > 0)
        {
            outStream.Write(buffer, 0, bytes);
            len -= bytes;
        }
    }
}

1
没有理由的负评对任何人都没有帮助。我认为你的回答很好。+1 让你回到平衡状态。 - Guy
我不是那个给你点踩的人,但我想指出最初的问题确实说明了该代码正在一个http处理程序中使用...无论如何,还是感谢你的建议。 - Mun
1
在更改文件名称之后,添加以下行以更改文件名: context.Response.AppendHeader("content-Disposition", "attachment;filename="+filename); - MaciejLisCK
@MarcGravell应该在他的回复中解释为什么Response.Buffer是您的Response.Flush的石头。 - JJS
@FaizanMubasher 视频流通常比静态内容更复杂... - Marc Gravell
显示剩余4条评论

10
请参考下面的翻译:

请查看以下文章《在ASP.NET中追踪和恢复大文件下载》,该文章会比简单地打开一个流并输出所有位元更深入地介绍。

HTTP协议支持分段字节请求和可恢复下载,并且许多流式客户端(如视频播放器或Adobe pdf)都可以并且会尝试将它们切成块,节省带宽并为您的用户提供更好的体验。

虽然不太容易,但这是值得投入时间的。


8
尝试将文件作为流打开,然后使用Response.OutputStream.Write()。例如:

编辑: 我犯了错误,忘记了Write需要一个字节缓冲区。已经修复。

byte [] buffer = new byte[1<<16] // 64kb
int bytesRead = 0;
using(var file = File.OpenRead(path))
{
   while((bytesRead = file.Read(buffer, 0, buffer.Length)) != 0)
   {
        Response.OutputStream.Write(buffer, 0, bytesRead);
   }
}
Response.Flush();
Response.Close();
Response.End();

编辑2:你尝试过这个吗?它应该可以工作。


谢谢,但我不认为那会起作用...Response.OutputStream.Write不接受Stream参数(至少在ASP.NET 2.0中不是这样),只接受字节数组,这需要将文件先加载到内存中,因此并不比WriteFile解决方案更好。 - Mun
2
关于字节数组--您不必将整个流加载到内存中。我所使用的代码在流式传输期间最多使用64kb的内存(加上少量开销)。 - Randolpho
如何将流响应分配给HTML视频控件? - Faizan Mubasher

3

在尝试了许多不同的组合后,包括各种回答中发布的代码,似乎在调用TransmitFile之前设置Response.Buffer = true就可以解决问题,Web应用程序现在在Internet Explorer中响应更快。

在这种特殊情况下,SWF扩展名也映射到ASP.NET,并且我们在Web应用程序中使用自定义处理程序从磁盘读取文件,然后使用Response.TransmitFile()将它们发送到浏览器。我们有一个基于Flash的视频播放器来播放视频文件,这些文件也是SWF文件,我认为所有这些活动都通过处理程序而没有缓冲可能会导致在IE中发生奇怪的事情。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接