全体
実行された 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;
}
}
}
}