Плохая производительность при предложении файлов для загрузки с HttpListener

Я пытаюсь создать простой веб-сервер, используяHttpListener вC# и предлагать файлы для скачивания. Я вижу очень плохую скорость передачи данных, особенно по сравнению с копированием того же файла из общего ресурса. Это известноHttpListener и что можно сделать, чтобы улучшить его?

Вот некоторая дополнительная информация об исследованиях, которые я провел по этой проблеме. Скорость загрузки значительно улучшается при локальном подключении, но в этом случае копирование файла выполняется практически мгновенно, поэтому сложно измерить разницу. При удаленном подключении LAN окружающая среда, машины находятся рядом друг с другом) однако время передачи примерно в 25 раз больше времени простой копии файла из общего ресурса. Кажется, доступная пропускная способность сети не используется для ускорения этого процесса.

Я нашел несколько других вопросов и обсуждений оHttpListener которые указывают на похожие проблемы, см. здесь:

HttpListener против собственной производительности

HttpListener Оптимизация производительности (это не касается загрузок)

MSDN документы также утверждают, чтоHttpListener основан наhttp.sys который позволяет регулировать пропускную способность. Может быть, здесь происходит какое-то нежелательное ограничение полосы пропускания или что-то не так с моим кодом? На машинах, на которых я тестировал (Windows 7 и Windows 2008 R2), IIS отсутствовал.

В моем примере я начинаюHttpListener вот так

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

Вот код для моей простой загрузки файла:

<code>  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();
  }
</code>

(редактировать: исправлены некоторые ссылки ...)

Ответы на вопрос(1)

В цело

обслуживающий файл и тест копирования файла smb) содержат слишком много переменных, чтобы сделать какие-либо полезные выводы о производительности HttpListener по сравнению с собственным кодом.

Весь другой код в таких случаях должен вызывать проблемы с производительностью и должен быть удален из контрольного примера.

К сожалению, реализация вопроса об обслуживании файла неоптимальна, поскольку она считывает кусок из файла в управляемый байтовый массив, а затем блокирует вызов для записи этого блока в ядро. Это копирование байтов файла в управляемый массив и обратно из управляемого массива (не добавляя никакого значения в процессе). В .Net 4.5 вы могли бы вызывать CopyToAsync между файловым потоком и выходным потоком, что избавило бы вас от необходимости выяснять, как сделать это параллельно.

Выводы

Тест ниже показывает, что HttpListener так же быстро отправляет байты, как IIS Express 8.0, возвращая файл. В этом посте я проверил это в нездоровой сети 802.11n с серверами на виртуальной машине и по-прежнему достигал скорости 100+ Мбит / с как с HttpListener, так и с IIS Expres

Единственное, что нужно изменить в исходном сообщении, - это как он читает файл, чтобы передать его обратно клиенту.

Если вы хотите передавать файлы через HTTP, вам, вероятно, следует использовать существующий веб-сервер, который обрабатывает как HTTP-часть, так и открытие / кэширование / ретрансляцию файлов. Вам будет трудно превзойти существующие веб-серверы, особенно когда вы добавляете динамические ответы gzip на картинку (в этом случае наивный подход случайным образом gzips весь ответ перед отправкой любого из них, что тратит время, которое могло бы быть использовано для отправка байтов).

Лучший тест для изоляции производительности HttpListener

Я создал тест, который возвращает строку целых чисел размером 10 МБ (сгенерированную один раз при запуске), чтобы позволить проверить скорость, с которой HttpListener может возвращать данные, когда ему передается весь блок заранее (что аналогично тому, что он мог делать при использовании). CopyToAsync).

Испытательная установк

Client Computer: MacBook Air, середина 2013 года, сервер Core i7 с тактовой частотой 1,7 ГГц, компьютер: iMac, середина 2011 года, Core i7 с частотой 3,4 ГГц, Windows 8.1, размещенный в VMWare Fusion 6.0, мостовая сеть: 802.11n через Airport Extreme (расположен в 8 футах) Скачать клиент: curl на Mac OS X

Результаты тест

IIS Express 8.0 был настроен для обслуживания файла размером 18 МБ, а программа HttpListenerSpeed была настроена на получение ответов 10 МБ и 100 МБ. Результаты теста были практически идентичны.

езультаты @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 Results
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;
            }
        }
    }
}

ВАШ ОТВЕТ НА ВОПРОС