如何在ASP.NET响应中提供大文件?

时间:2012-03-07 11:42:19

标签: asp.net iis download

  

我不是在寻找任何流媒体文件内容的替代品   数据库,确实是我在寻找问题的根源,这是   运行文件直到IIS 6我们在经典模式下运行我们的应用程序,现在我们   将我们的IIS升级到7,我们正在以管道模式运行应用程序池   这个问题开始了。

我有一个处理程序,我必须向客户端请求提供大文件。我面临以下问题,

文件平均大小为4到100 MB,因此我们考虑80MB文件下载情况。

缓冲开启,慢启动

Response.BufferOutput = True;

这导致文件启动非常慢,因为用户下载甚至进度条直到几秒钟才出现,通常是3到20秒,原因是,IIS首先读取整个文件,确定内容长度然后开始文件传输。文件正在视频播放器中播放,并且运行速度非常慢,但iPad仅先下载文件的一小部分,因此它可以快速运行。

缓冲关闭,无内容长度,快速启动,无进展

Reponse.BufferOutput = False;

这导致立即启动,但是最终客户端(Chrome之类的典型浏览器)不知道内容长度,因为IIS也不知道,所以它不显示进度,而是说下载了X KB。

缓冲关闭,手动内容长度,快速启动,进度和协议违规

Response.BufferOutput = False;
Response.AddHeader("Content-Length", file.Length);

这样可以在Chrome等中立即正确下载文件,但在某些情况下,IIS处理程序会导致“远程客户端关闭连接”错误(这种情况非常频繁),而其他WebClient会导致协议违规。 这种情况发生在所有请求的5%到10%,而不是每次请求。

我想发生的事情是,当我们不进行缓冲时,IIS不会发送任何名为100的内容,并且客户端可能断开连接而不期望任何输出。但是,从源读取文件可能需要更长的时间,但在客户端我增加了超时但似乎IIS超时且无法控制。

无论如何我可以强制响应发送100继续并且不让任何人关闭连接吗?

更新

我在Firefox / Chrome中找到了以下标题,此处似乎没有任何协议违规或错误标题。

Access-Control-Allow-Headers:*
Access-Control-Allow-Methods:POST, GET, OPTIONS
Access-Control-Allow-Origin:*
Access-Control-Max-Age:1728000
Cache-Control:private
Content-Disposition:attachment; filename="24.jpg"
Content-Length:22355
Content-Type:image/pjpeg
Date:Wed, 07 Mar 2012 13:40:26 GMT
Server:Microsoft-IIS/7.5
X-AspNet-Version:4.0.30319
X-Powered-By:ASP.NET

更新2

车削回收仍然没有提供多少,但我已将MaxWorkerProcess增加到8,之前我的错误数量减少了。

但平均而言,在一秒内200个请求中,2到10个请求失败..,这几乎每隔一秒发生一次。

更新3

继续5%的请求失败,“服务器提交了协议违规。部分= ResponseStatusLine”,我有另一个程序从使用WebClient的网络服务器下载内容,并且每秒发送4-5次此错误,平均而言,我有5%的请求失败。无论如何都要追踪WebClient失败?

问题重新定义

收到零字节文件

IIS由于某种原因关闭连接,在WebConfig的客户端,我收到0字节的文件,这不是零字节,我们进行SHA1哈希检查,这告诉我们在IIS Web服务器中,没有记录错误。< / p>

  

这是我的错误,它解决了因为我们正在使用Entity Framework,它正在读取脏(未提交的行),因为读取不在事务范围内,将它置于事务范围已解决了这个问题。

提升协议违规例外

WebClient抛出WebException,说“服务器提交了协议违规.Section = ResponseStatusLine。

我知道我可以启用不安全的头解析,但这不是重点,当我的HTTP处理程序正在发送正确的头文件时,不知道为什么IIS发送任何额外的内容(在firefox和chrome上检查,没有什么异常),这只发生2%的次数。

更新4

发现sc-win32 64错误,我在某处读到MinBytesPerSecond的WebLimits必须从240更改为0,仍然一切都是相同的。但是我注意到,每当IIS记录64个sc-win32错误时,IIS就会将HTTP状态记录为200,但是有一些错误。现在我无法打开200的Failed Trace Logging,因为它会产生大量文件。

  

上述两个问题都是通过增加MinBytesPerSecond以及禁用Sessions来解决的,我已经添加了详细的答案,总结了每一点。

4 个答案:

答案 0 :(得分:12)

虽然在IIS中传递大文件的正确方法是以下选项,

  1. 在WebLimits中将MinBytesPerSecond设置为零(这肯定有助于提高性能,因为IIS选择关闭持有较小规模传输的KeepAlive连接的客户端)
  2. 将更多工作进程分配给应用程序池,我已设置为8,现在只有在服务器分发较大文件时才应该这样做。这肯定会导致其他网站执行速度变慢,但这将确保更好的交付。我们设置为8,因为这个服务器只有一个网站,它只提供大量文件。
  3. 关闭App Pool Recycling
  4. 关闭会话
  5. 保持缓冲
  6. 在执行以下每个步骤之前,请检查Response.IsClientConnected是否为true,否则放弃并且不发送任何内容。
  7. 发送文件前设置内容长度
  8. 刷新响应
  9. 写入输出流,并定期刷新

答案 1 :(得分:11)

当您使用bufferOutput设置内容长度为false时,失败的可能原因是因为IIS尝试gzip您发送的文件,并通过设置Content-Length IIS无法将其更改回压缩的文件,错误开始(*)。

所以keep the BufferOutput to false, and second disable the gzip from iis for the files you send - 或者为所有文件禁用iis gzip并以编程方式处理gzip部分,不要使用gzip来发送你发送的文件。

出于同样原因的一些类似问题: ASP.NET site sometimes freezing up and/or showing odd text at top of the page while loading, on load balanced servers

HTTP Compression: Some external scripts/CSS not decompressing properly some of the time

(*)为什么不再改变它?因为从您设置标题的那一刻起,您就无法参与,除非您在IIS上启用了此选项,并且认为标题尚未准备好发送到浏览器。

跟进

如果没有gziped,我想到的下一件事是文件被发送,并且由于某种原因连接被延迟,并且超时并关闭。所以你得到“远程主机关闭连接”。

这可以根据原因解决:

  1. 客户端确实关闭了连接
  2. 超时来自页面本身,如果您使用处理程序(可能再次,消息必须是“Page Timed Out”)。
  3. 超时来自空闲等待,页面占用超过执行时间,获取超时并关闭连接。也许在这种情况下,消息是Page Timed Out。
  4. 您发送文件时,池会进行回收。禁用所有池回收!这是我现在能想到的最可能的案例。
  5. 如果它来自IIS,请转到网站属性并确保设置最大的“连接超时”和“启用HTTP保持活动”。

    通过更改web.config页面超时(您可以仅以编程方式为一个特定页面更改它)

    <httpRuntime executionTimeout="43200"
    

    另外看看: http://weblogs.asp.net/aghausman/archive/2009/02/20/prevent-request-timeout-in-asp-net.aspx

    会话锁定

    您需要检查的另一件事是不要在用于发送文件的处理程序上使用会话,因为会话锁定操作直到完成,如果用户需要更长的时间来下载文件,那么一个人可能会抽出时间。

    一些亲戚:

    call aspx page to return an image randomly slow

    Replacing ASP.Net's session entirely

    Response.WriteFile function fails and gives 504 gateway time-out

答案 2 :(得分:5)

我要做的是使用不那么着名的ASP.NET Response.TransmitFile方法,因为它非常快(并且可能使用IIS内核缓存)并负责所有标头内容。它基于Windows非托管TransmitFile API。

但是为了能够使用此API,您需要一个物理文件来传输。所以这里有一个伪c#代码,用于解释如何使用虚构的myCacheFilePath物理文件路径执行此操作。它还支持客户端缓存的可能性。当然,如果您已有一个文件,则无需创建该缓存:

    if (!File.Exists(myCacheFilePath))
    {
        LoadMyCache(...); // saves the file to disk. don't do this if your source is already a physical file (not stored in a db for example).
    }

    // we suppose user-agent (browser) cache is enabled
    // check appropriate If-Modified-Since header
    DateTime ifModifiedSince = DateTime.MaxValue;
    string ifm = context.Request.Headers["If-Modified-Since"];
    if (!string.IsNullOrEmpty(ifm))
    {
        try
        {
            ifModifiedSince = DateTime.Parse(ifm, DateTimeFormatInfo.InvariantInfo);
        }
        catch
        {
            // do nothing
        }

        // file has not changed, just send this information but truncate milliseconds
        if (ifModifiedSince == TruncateMilliseconds(File.GetLastWriteTime(myCacheFilePath)))
        {
            ResponseWriteNotModified(...); // HTTP 304
            return;
        }
    }

    Response.ContentType = contentType; // set your file content type here
    Response.AddHeader("Last-Modified", File.GetLastWriteTimeUtc(myCacheFilePath).ToString("r", DateTimeFormatInfo.InvariantInfo)); // tell the client to cache that file

    // this API uses windows lower levels directly and is not memory/cpu intensive on Windows platform to send one file. It also caches files in the kernel.
    Response.TransmitFile(myCacheFilePath)

答案 3 :(得分:2)

这段代码对我有用。 它立即启动数据流到客户端。 它显示下载过程中的进度。 它不违反HTTP。指定了Content-Length标头,并且未使用chuncked传输编码。

protected void PrepareResponseStream(string clientFileName, HttpContext context, long sourceStreamLength)
{
    context.Response.ClearHeaders();
    context.Response.Clear();

    context.Response.ContentType = "application/pdf";
    context.Response.AddHeader("Content-Disposition", string.Format("filename=\"{0}\"", clientFileName));

    //set cachebility to private to allow IE to download it via HTTPS. Otherwise it might refuse it
    //see reason for HttpCacheability.Private at http://support.microsoft.com/kb/812935
    context.Response.Cache.SetCacheability(HttpCacheability.Private);
    context.Response.Buffer = false;
    context.Response.BufferOutput = false;
    context.Response.AddHeader("Content-Length", sourceStreamLength.ToString    (System.Globalization.CultureInfo.InvariantCulture));
}

protected void WriteDataToOutputStream(Stream sourceStream, long sourceStreamLength, string clientFileName, HttpContext context)
{
    PrepareResponseStream(clientFileName, context, sourceStreamLength);
    const int BlockSize = 4 * 1024 * 1024;
    byte[] buffer = new byte[BlockSize];
    int bytesRead;
    Stream outStream = m_Context.Response.OutputStream;
    while ((bytesRead = sourceStream.Read(buffer, 0, BlockSize)) > 0)
    {
        outStream.Write(buffer, 0, bytesRead);
    }
    outStream.Flush();
}