2012-12-26 15 views
9

TCPサーバーは、SocketAsyncEventArgsを使用して開発され、Windowsサービスと同じ非同期メソッドです。20 SocketAsyncEventArgsで1秒あたりの受信数

ThreadPool.SetMaxThreads(15000, 30000); 
ThreadPool.SetMinThreads(10000, 20000); 

そして、両方とも真(戻り値が記録されます)を返します。 2000〜3000のクライアントがこのサーバーにメッセージを送信し始め、接続を受け入れるようになります(接続数がカウントされ、予想通りです - 接続プールがあります)。サーバープロセスのスレッド数は〜2050〜3050に増加します。ここまでは順調ですね!

ReceivedAsyncがtrueを返すか、SocketAsyncEventArgsのCompletedイベントの後に呼び出されるReceivedメソッドがあります。

ここで問題が始まります:どのくらいのクライアントが接続され、どれくらいのメッセージを送信しても、Receivedは最大20回も呼び出されます。クライアントの数が増えると、この数(20)は〜10に減少します。

環境:TCPサーバーとクライアントが同じマシン上でシミュレートされています。 2台のCPUと4GBのRAMを搭載し、もう1台は8コアのCPUと12GBのRAMを搭載した2台のマシンでこのコードをテストしました。データが失われることはありませんが(まだ)、受信操作ごとに1つ以上のメッセージが受信されることがあります。それはいいです。しかし、受信動作の数をどのように増やすことができますか?

実装に関するその他の注意事項:コードは大きく、さまざまなロジックが含まれています。全体的な説明は次のようになります。新しい接続を受け入れるための単一のSocketAsyncEventArgsがあります。それは素晴らしい作品です。新しい受け入れられた接続ごとに、データを受け取るための新しいSocketAsyncEventArgsを作成します。私はこの1つ(受信用に作成されたSocketAsyncEventArgs)をプールに入れます。これは再利用されませんが、UserTokenは接続の追跡に使用されています。切断された接続や、7分間データを送信していない接続が閉じられて破棄されます(SocketAsyncEventArgsのAcceptSocketはシャットダウン(両方)され、閉じられ、破棄され、SocketAsyncEventArgsオブジェクト自体も同様です)。ここではこれらのタスクを実行しますが、他のすべてのロジック、ロギングおよびエラーチェックや他の何かが、それは単純明快にするために削除され須藤クラスが(多分、問題のあるコードを発見することが容易である)である:

class Sudo 
{ 
    Socket _listener; 
    int _port = 8797; 

    public Sudo() 
    { 
     var ipEndPoint = new IPEndPoint(IPAddress.Any, _port); 
     _listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 
     _listener.Bind(ipEndPoint); 

     _listener.Listen(100); 

     Accept(null); 
    } 

    void Accept(SocketAsyncEventArgs acceptEventArg) 
    { 
     if (acceptEventArg == null) 
     { 
      acceptEventArg = new SocketAsyncEventArgs(); 
      acceptEventArg.Completed += AcceptCompleted; 
     } 
     else acceptEventArg.AcceptSocket = null; 

     bool willRaiseEvent = _listener.AcceptAsync(acceptEventArg); ; 

     if (!willRaiseEvent) Accepted(acceptEventArg); 
    } 

    void AcceptCompleted(object sender, SocketAsyncEventArgs e) 
    { 
     Accepted(e); 
    } 

    void Accepted(SocketAsyncEventArgs e) 
    { 
     var acceptSocket = e.AcceptSocket; 
     var readEventArgs = CreateArg(acceptSocket); 

     var willRaiseEvent = acceptSocket.ReceiveAsync(readEventArgs); 

     Accept(e); 

     if (!willRaiseEvent) Received(readEventArgs); 
    } 

    SocketAsyncEventArgs CreateArg(Socket acceptSocket) 
    { 
     var arg = new SocketAsyncEventArgs(); 
     arg.Completed += IOCompleted; 

     var buffer = new byte[64 * 1024]; 
     arg.SetBuffer(buffer, 0, buffer.Length); 

     arg.AcceptSocket = acceptSocket; 

     arg.SocketFlags = SocketFlags.None; 

     return arg; 
    } 

    void IOCompleted(object sender, SocketAsyncEventArgs e) 
    { 
     switch (e.LastOperation) 
     { 
      case SocketAsyncOperation.Receive: 
       Received(e); 
       break; 
      default: break; 
     } 
    } 

    void Received(SocketAsyncEventArgs e) 
    { 
     if (e.SocketError != SocketError.Success || e.BytesTransferred == 0 || e.Buffer == null || e.Buffer.Length == 0) 
     { 
      // Kill(e); 
      return; 
     } 

     var bytesList = new List<byte>(); 
     for (var i = 0; i < e.BytesTransferred; i++) bytesList.Add(e.Buffer[i]); 

     var bytes = bytesList.ToArray(); 

     Process(bytes); 

     ReceiveRest(e); 

     Perf.IncOp(); 
    } 

    void ReceiveRest(SocketAsyncEventArgs e) 
    { 
     e.SocketFlags = SocketFlags.None; 
     for (int i = 0; i < e.Buffer.Length; i++) e.Buffer[i] = 0; 
     e.SetBuffer(0, e.Buffer.Length); 

     var willRaiseEvent = e.AcceptSocket.ReceiveAsync(e); 
     if (!willRaiseEvent) Received(e); 
    } 

    void Process(byte[] bytes) { } 
} 
+0

MB RAM?それは私のキャッシュですか?私はあなたに数GBのメモリがあることを願っています。ボトルネックを探すためにResource MonitorまたはProcess Explorerを使用しましたか? – HABO

+0

うーん...あなたのサーバー上の非同期アーキテクチャとは何ですか?どのように聴き方が起こり、接続がどのように受け入れられるかを記述する疑似コードを投稿することができますか? –

+0

@HABOありがとう!私はそれを修正しました。 –

答えて

4

なぜなら、それらのスレッドのそれぞれがコンテキスト切り替えを必要とし、それが比較的高価な操作であるからです。追加するスレッドが増えるほど、実際のコードではなく、コンテキスト切り替えに費やされるCPUの割合が増えます。

この1スレッド/クライアントのボトルネックは、ちょっと変わった方法でヒットしました。サーバ側の非同期のポイントはになります。スレッドの数を減らしてください。クライアントごとにスレッドは1つではありませんが、理想的には、システム内の論理プロセッサごとに1つまたは2つのみです。

あなたが投稿した非同期コードは上手く見えるので、Processメソッドでは非同期で非同期なI/Oをブロックすることが簡単に推測できます。つまりデータベースまたはファイルへのアクセスです。 I/Oブロックを実行すると、.NETスレッドプールがこれを検出して自動的に新しいスレッドをスピンアップします。これは基本的にここではI/OがボトルネックとしてProcessになっています。

非同期パイプラインは、本当にそれから大きな利益を得るために100%非同期である必要があります。ハーフ・インは複雑なコードを書いて、シンプルなシンク・コードと同じくらいうまく動作します。

Processメソッドを純粋に非同期にすることができない場合は、いくつか運が偽っている可能性があります。限られたサイズの小さいスレッドプールで処理するために、キュー内で待機するものがあります。

+0

このWindowsサービスでは、受信したデータをメモリ上で処理していますが、私のプログラムで話しているのは、処理されたデータをMSMQキューに入れる方法だけです。 –

+0

データベースとファイル(と私の場合はMSMQ)について何を指摘していますか?あなたは正しかった。他のプロジェクトで回復可能なオプションを使用している気がするMSMQについて忘れてしまった(例えば、RabbitMQと比較して酷いパフォーマンスがありますが、ここではそれを使用することを余儀なくされています。したがって、メインの非同期ワークフローから他のアクティビティによって引き起こされた問題を適切に指摘するための答えを編集してください。私はそれを答えとしてマークすることができます。再度、感謝します! –

+0

恐ろしい、あなたが答えを見つけてうれしい。そうです、回復可能モードがオンになっていると、MSMQは本当に吸います。 –

関連する問題