1

HttpListenerを使用して簡単なWebサーバーを作成し、C#ダウンロードするファイルを提供しようとしています。特に同じファイルを共有からコピーする場合と比較して、転送速度が非常に悪いことがわかります。これは知られてHttpListenerいますか、それを改善するために何ができるでしょうか?

この問題について私が行った調査に関する追加情報を次に示します。ローカルに接続するとダウンロードレートが大幅に向上しますが、この場合、ファイルのコピーはほぼ瞬時に行われるため、差異の比率を測定することは困難です。ただし、リモートで接続する場合(LAN環境、マシンが隣接している場合)、転送時間は共有からの単純なファイルコピーの約25倍です。利用可能なネットワーク帯域幅は、これを高速化するために使用されていないようです。

私はそれについて他のいくつかの質問と議論HttpListenerが同様の問題を示しているように見えるのを見つけました、ここを見てください:

HttpListenerとネイティブパフォーマンス

HttpListenerパフォーマンスの最適化(ただし、これはダウンロードに関するものではありません)

MSDNのドキュメントには、帯域幅の調整を可能にするものHttpListenerに基づいているとも記載されています。http.sysここで不要な帯域幅調整が行われている可能性がありますか、それともコードに問題がありますか?私がテストしたマシン(Windows7およびWindows2008 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();
  }

(編集:いくつかのリンクを修正しました...)

4

1 に答える 1

2

全体

実行された 2 つのテスト (ファイルを提供する C# HttpListener テストと smb ファイル コピー テスト) には、HttpListener とネイティブ コードのパフォーマンスについて有用な結論を導き出すにはあまりにも多くの変数が含まれています。

このような場合の他のすべてのコードは、パフォーマンスの問題を引き起こしている可能性があり、テスト ケースから削除する必要があります。

残念ながら、ファイルを提供する質問の実装は、ファイルから管理されたバイト配列にチャンクを読み取り、そのブロックをカーネルに書き込むための呼び出しでブロックするため、最適ではありません。ファイルのバイトをマネージド配列にコピーし、マネージド配列から戻します (プロセスに値を追加しません)。.Net 4.5 では、ファイル ストリームと出力ストリームの間で CopyToAsync を呼び出すことができました。

結論

以下のテストは、HttpListener がファイルを返す IIS Express 8.0 と同じくらい高速にバイトを返すことを示しています。この投稿では、VM 上のサーバーを使用して安っぽい 802.11n ネットワークでこれをテストしましたが、HttpListener と IIS Express の両方で 100+ Mbps に達しました。

元の投稿で変更する必要があるのは、ファイルを読み取ってクライアントに中継する方法だけです。

HTTP 経由でファイルを提供する場合は、HTTP 側とファイルのオープン/キャッシュ/リレーの両方を処理する既存の Web サーバーを使用する必要があります。特に gzip 動的応答を画像に取り込む場合は、既存の Web サーバーを打ち負かすのは難しいことがわかります (その場合、単純なアプローチでは、応答全体を誤って gzip してから送信してから、送信に使用できたはずの時間を無駄にします)。送信バイト)。

HttpListener のパフォーマンスのみを分離するためのより良いテスト

10 MB の整数の文字列 (起動時に 1 回生成される) を返すテストを作成して、ブロック全体が前もって与えられたときに HttpListener がデータを返す速度をテストできるようにしました (これは、CopyToAsync を使用したときにできることと似ています) )。

テスト設定

クライアント コンピュータ: MacBook Air mid-2013、1.7 GHz Core i7 サーバー コンピュータ: iMac mid-2011、3.4 GHz Core i7 - Windows 8.1 VMWare Fusion 6.0 でホスト、ブリッジ ネットワーク ネットワーク: Airport Extreme (8 フィート離れた場所) 経由の 802.11n ダウンロードクライアント: 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 コード

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;
            }
        }
    }
}
于 2013-10-05T20:06:56.733 に答える