提供使用 HttpListener 下载的文件时性能不佳

本文关键字:文件 性能 下载 HttpListener | 更新日期: 2023-09-27 18:33:29

我正在尝试使用HttpListener创建一个简单的 Web 服务器C#并提供文件以供下载。我看到传输速率非常糟糕,尤其是与从共享复制同一文件相比。HttpListener是否知道这一点,可以做些什么来改进它?

以下是我就此问题所做的研究的一些附加信息。本地连接时下载速率提高了很多,但在这种情况下,复制文件几乎是立即完成的,因此很难衡量差异率。但是,当远程连接(LAN环境,计算机彼此相邻(时,传输时间大约是从共享复制简单文件的时间的 25 倍。可用的网络带宽似乎没有用于加快速度。

我发现了一些关于HttpListener的其他问题和讨论,这些问题和讨论似乎表明了类似的问题,请参阅此处:

HttpListener vs 本机性能

HttpListener 性能优化(但这与下载无关(

MSDN 文档还指出,HttpListener基于允许带宽限制的http.sys。可能是这里发生了一些不必要的带宽限制,还是我的代码有问题?在我测试过的机器(Windows 7和Windows 2008 R2(上,不存在IIS。

在我的示例中,我开始了一个如下所示的HttpListener

  HttpListener listener = new HttpListener();
  listener.Prefixes.Add("http://*:80/");
  listener.Start();

这是我简单文件下载的代码:

  HttpListenerResponse response = null;
  try {
      HttpListenerContext context = listener.GetContext();
      response = context.Response;
      using( FileStream fs = File.OpenRead( @"c:'downloadsample'testfile.pdf" ) ) {
          byte[] buffer = new byte[ 32768 ];
          int read;
          while( ( read = fs.Read( buffer, 0, buffer.Length ) ) > 0 ) {
              response.OutputStream.Write( buffer, 0, read );
          }
      }
  } finally {
      if( response != null )
          response.Close();
  }

(编辑:修复了一些链接...

提供使用 HttpListener 下载的文件时性能不佳

整体

运行的两个测试(提供文件的 C# HttpListener 和 smb 文件复制测试(包含太多变量,无法得出有关 HttpListener 与本机代码性能的任何有用结论。

在这种情况下,应

怀疑所有其他代码会导致性能问题,并应从测试用例中删除。

不幸的是,该问题提供文件的实现不是最佳的,因为它将文件中的块读取到托管字节数组中,然后在调用时阻塞以将该块写入内核。 它将文件的字节复制到托管数组中,然后从托管数组中复制回来(在此过程中不添加任何值(。 使用 .Net 4.5,您可以在文件流和输出流之间调用 CopyToAsync,这将使您摆脱弄清楚如何并行执行此操作的业务。

结论

下面的测试表明,HttpListener 在发回字节方面与 IIS Express 8.0 返回文件的速度一样快。 在这篇文章中,我用虚拟机上的服务器在俗气的802.11n网络上测试了这一点,并且仍然使用HttpListener和IIS Express达到100+ Mbps。

在原始帖子中唯一需要更改的是它如何读取文件以将其中继回客户端。

如果你想通过HTTP提供文件,你可能应该使用现有的Web服务器来处理HTTP端的事情和文件打开/缓存/中继。 你会发现很难击败现有的Web服务器,特别是当你把gzipping动态响应带入图片中时(在这种情况下,天真的方法在发送任何响应之前会意外地gzip整个响应,这浪费了本可用于发送字节的时间(。

更好的测试,仅隔离 HttpListener 的性能

我创建了一个返回 10 MB 整数字符串(在启动时生成一次(的测试,以允许测试 HttpListener 在预先给定整个块时返回数据的速度(这类似于使用 CopyToAsync 时它可以做的事情(。

测试设置

客户端计算机:MacBook Air 2013年中,1.7 GHz Core i7服务器计算机:iMac 2011年中,3.4 GHz Core i7 - Windows 8.1托管在VMWare Fusion 6.0中,桥接网络网络:802.11n通过机场至尊(位于8英尺外(下载客户端:Mac OS X 上的 curl

测试结果

IIS Express 8.0 被配置为提供 18 MB 的文件,HttpListenerSpeed 程序被设置为返回 10 MB 和 100 MB 的响应。 测试结果基本相同。

IIS Express 8.0 结果

Harolds-MacBook-Air:~ huntharo$ curl -L http://iMacWin81.local:8000/TortoiseSVN-1.8.2.24708-x64-svn-1.8.3.msi > /dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 18.4M  100 18.4M    0     0  13.1M      0  0:00:01  0:00:01 --:--:-- 13.1M
Harolds-MacBook-Air:~ huntharo$ curl -L http://iMacWin81.local:8000/TortoiseSVN-1.8.2.24708-x64-svn-1.8.3.msi > /dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 18.4M  100 18.4M    0     0  13.0M      0  0:00:01  0:00:01 --:--:-- 13.1M
Harolds-MacBook-Air:~ huntharo$ curl -L http://iMacWin81.local:8000/TortoiseSVN-1.8.2.24708-x64-svn-1.8.3.msi > /dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 18.4M  100 18.4M    0     0  9688k      0  0:00:01  0:00:01 --:--:-- 9737k

HttpListenerSpeed 结果

Harolds-MacBook-Air:~ huntharo$ curl -L http://iMacWin81.local:8080/garbage > /dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 18.4M  100 18.4M    0     0  12.6M      0  0:00:01  0:00:01 --:--:-- 13.1M
Harolds-MacBook-Air:~ huntharo$ curl -L http://iMacWin81.local:8080/garbage > /dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 18.4M  100 18.4M    0     0  13.1M      0  0:00:01  0:00:01 --:--:-- 13.1M
Harolds-MacBook-Air:~ huntharo$ curl -L http://iMacWin81.local:8080/garbage > /dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 18.4M  100 18.4M    0     0  13.2M      0  0:00:01  0:00:01 --:--:-- 13.2M

HttpListenerSpeed Code

using System;
using System.Threading.Tasks;
using System.Net;
using System.Threading;
namespace HttpListenerSpeed
{
    class Program
    {
        static void Main(string[] args)
        {
            var listener = new Listener();
            Console.WriteLine("Press Enter to exit");
            Console.ReadLine();
            listener.Shutdown();
        }
    }
    internal class Listener
    {
        private const int RequestDispatchThreadCount = 4;
        private readonly HttpListener _httpListener = new HttpListener();
        private readonly Thread[] _requestThreads;
        private readonly byte[] _garbage;
        internal Listener()
        {
            _garbage = CreateGarbage();
            _httpListener.Prefixes.Add("http://*:8080/");
            _httpListener.Start();
            _requestThreads = new Thread[RequestDispatchThreadCount];
            for (int i = 0; i < _requestThreads.Length; i++)
            {
                _requestThreads[i] = new Thread(RequestDispatchThread);
                _requestThreads[i].Start();
            }
        }
        private static byte[] CreateGarbage()
        {
            int[] numbers = new int[2150000];
            for (int i = 0; i < numbers.Length; i++)
            {
                numbers[i] = 1000000 + i;
            }
            Shuffle(numbers);
            return System.Text.Encoding.UTF8.GetBytes(string.Join<int>(", ", numbers));
        }
        private static void Shuffle<T>(T[] array)
        {
            Random random = new Random();
            for (int i = array.Length; i > 1; i--)
            {
                // Pick random element to swap.
                int j = random.Next(i); // 0 <= j <= i-1
                // Swap.
                T tmp = array[j];
                array[j] = array[i - 1];
                array[i - 1] = tmp;
            }
        }
        private void RequestDispatchThread()
        {
            while (_httpListener.IsListening)
            {
                string url = string.Empty;
                try
                {
                    // Yeah, this blocks, but that's the whole point of this thread
                    // Note: the number of threads that are dispatching requets in no way limits the number of "open" requests that we can have
                    var context = _httpListener.GetContext();
                    // For this demo we only support GET
                    if (context.Request.HttpMethod != "GET")
                    {
                        context.Response.StatusCode = (int)HttpStatusCode.NotFound;
                        context.Response.Close();
                    }
                    // Don't care what the URL is... you're getting a bunch of garbage, and you better like it!
                    context.Response.StatusCode = (int)HttpStatusCode.OK;
                    context.Response.ContentLength64 = _garbage.Length;
                    context.Response.OutputStream.BeginWrite(_garbage, 0, _garbage.Length, result =>
                    {
                        context.Response.OutputStream.EndWrite(result);
                        context.Response.Close();
                    }, context);
                }
                catch (System.Net.HttpListenerException e)
                {
                    // Bail out - this happens on shutdown
                    return;
                }
                catch (Exception e)
                {
                    Console.WriteLine("Unexpected exception: {0}", e.Message);
                }
            }
        }
        internal void Shutdown()
        {
            if (!_httpListener.IsListening)
            {
                return;
            }
            // Stop the listener
            _httpListener.Stop();
            //  Wait for all the request threads to stop
            for (int i = 0; i < _requestThreads.Length; i++)
            {
                var thread = _requestThreads[i];
                if (thread != null) thread.Join();
                _requestThreads[i] = null;
            }
        }
    }
}