使用 C# HttpListener 提供大文件

Posted

技术标签:

【中文标题】使用 C# HttpListener 提供大文件【英文标题】:Serving large files with C# HttpListener 【发布时间】:2012-11-03 08:25:36 【问题描述】:

我正在尝试使用 HttpListener 来提供静态文件,这适用于小文件。当文件变大时(使用 350 和 600MB 文件进行测试),服务器因以下异常之一而阻塞:HttpListenerExceptionI/O 操作已中止由于线程退出或应用程序请求,或者:HttpListenerException信号量超时期限已过期

需要改变什么来摆脱异常,让它运行稳定/可靠(和快速)?

这里有一些进一步的阐述:这基本上是this earlier question 的后续问题。代码略微扩展以显示效果。内容写入循环使用(希望是合理的)块大小,在我的情况下为 64kB,但更改值除了速度之外没有任何区别(请参阅前面提到的旧问题)。


using( FileStream fs = File.OpenRead( @"C:\test\largefile.exe" ) ) 

    //response is HttpListenerContext.Response...
    response.ContentLength64 = fs.Length;
    response.SendChunked = false;
    response.ContentType = System.Net.Mime.MediaTypeNames.Application.Octet;
    response.AddHeader( "Content-disposition", "attachment; filename=largefile.EXE" );

    byte[] buffer = new byte[ 64 * 1024 ];
    int read;
    using( BinaryWriter bw = new BinaryWriter( response.OutputStream ) ) 
        while( ( read = fs.Read( buffer, 0, buffer.Length ) ) > 0 ) 
            Thread.Sleep( 200 ); //take this out and it will not run
            bw.Write( buffer, 0, read );
            bw.Flush(); //seems to have no effect
        

        bw.Close();
    

    response.StatusCode = ( int )HttpStatusCode.OK;
    response.StatusDescription = "OK";
    response.OutputStream.Close();

我正在浏览器中尝试下载,也尝试在使用 HttpWebRequest 的 C# 程序中下载,这没有区别。

根据我的研究,我认为 HttpListener 并不能真正将内容刷新到客户端,或者至少以它自己的速度这样做。我也省略了 BinaryWriter 并直接写入流 - 没有区别。在基本流周围引入了 BufferedStream - 没有区别。有趣的是,如果在循环中引入了 Thread.Sleep(200) 或稍大一些,它可以在我的盒子上运行。但是我怀疑它对于真正的解决方案是否足够稳定。 This question 给人的印象是根本没有机会让它正常运行(除了转移到我会求助的 IIS/ASP.NET,但如果可能的话,更可能远离)。

【问题讨论】:

【参考方案1】:

您没有向我们展示如何初始化 HttpListener 的另一个关键部分。因此,我使用下面的代码尝试了您的代码,并且成功了

HttpListener listener = new HttpListener();
listener.Prefixes.Add("http://*:8080/");
listener.Start();
Task.Factory.StartNew(() =>

    while (true)
    
        HttpListenerContext context = listener.GetContext();
        Task.Factory.StartNew((ctx) =>
        
            WriteFile((HttpListenerContext)ctx, @"C:\LargeFile.zip");
        , context,TaskCreationOptions.LongRunning);
    
,TaskCreationOptions.LongRunning);

WriteFile 是您删除 Thread.Sleep( 200 ); 的代码。

如果你想看它的完整代码。


void WriteFile(HttpListenerContext ctx, string path)

    var response = ctx.Response;
    using (FileStream fs = File.OpenRead(path))
    
        string filename = Path.GetFileName(path);
        //response is HttpListenerContext.Response...
        response.ContentLength64 = fs.Length;
        response.SendChunked = false;
        response.ContentType = System.Net.Mime.MediaTypeNames.Application.Octet;
        response.AddHeader("Content-disposition", "attachment; filename=" + filename);

        byte[] buffer = new byte[64 * 1024];
        int read;
        using (BinaryWriter bw = new BinaryWriter(response.OutputStream))
        
            while ((read = fs.Read(buffer, 0, buffer.Length)) > 0)
            
                bw.Write(buffer, 0, read);
                bw.Flush(); //seems to have no effect
            

            bw.Close();
        

        response.StatusCode = (int)HttpStatusCode.OK;
        response.StatusDescription = "OK";
        response.OutputStream.Close();
    

【讨论】:

噢!我不得不承认我已经陷入了一种痴迷,即 HttpListener 可能存在一些问题,而不是只是想清楚。所以@L.B 谢谢你让我回到正确的轨道上。基本上我正在以相同的方式实例化 HttpListener ,并且我确实使用您的代码得到了错误。当然,我已经在我们环境中的不同 PC 上进行了尝试,并且看到了相同的效果,但是在干净的 VM 机器上,您的代码和我的原始实现都可以完美运行。我还没有找出原因是什么,我怀疑杀毒软件......所以再次感谢您的帮助! @L.B.:你能说明使用额外的 BinaryWriter 有什么好处吗? 与问题相比,这里的最大区别是什么?能否总结一下问题和解决方法?

以上是关于使用 C# HttpListener 提供大文件的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C# 中的随机端口上创建 HttpListener 类?

C# 中 HttpListener 和 Griffin.WebServer 的替代方案

使用 C# HttpListener 处理多个请求

使用 C# HttpListener 时无法设置 MIME 类型

C# 关于HttpListener输入流无法读取的问题

C#使用HTTPListener异步实现HTTP服务端,请求执行两次