2009-10-08 14 views
7

SocketAsyncEventArgsイベントを使用して非同期ソケットサーバーを作成したいとします。SocketAsyncEventArgsを使用したサーバーの設計

サーバーは、同時に約1000の接続を管理する必要があります。各パケットのロジックを処理する最善の方法は何ですか?

サーバーデザインはthis MSDN exampleに基づいているため、すべてのソケットにはデータを受信するための独自のSocketAsyncEventArgsがあります。

  1. 受信機能内部のロジックのものを行います。 オーバーヘッドは作成されませんが、ロジックが完了する前に次のReceiveAsync()呼び出しが行われないため、ソケットから新しいデータを読み取ることができません。クライアントが大量のデータを送信し、ロジック処理が重い場合、システムはそれをどのように処理しますか(バッファがいっぱいになるためにパケットが失われます)?また、すべてのクライアントが同時にデータを送信した場合、1000個のスレッドが存在するか、または内部制限があり、別のスレッドが実行を完了する前に新しいスレッドを開始できませんか?

  2. キューを使用します。 受信機能は非常に短く実行速度は速いですが、キューのためにまともなオーバーヘッドがあります。問題は、サーバーの負荷が高い状態でワーカースレッドが十分に高速でない場合、キューがいっぱいになる可能性があるため、パケットを強制的に破棄する必要があるかもしれません。プロデューサ/コンシューマの問題も発生します。これは、おそらく多くのロックを含むキュー全体を遅くする可能性があります。

それでは、受信機能のロジック、ワーカースレッドのロジック、または今まで私が見逃していたものは何でしょうか。

データ送信に関するもう1つのクエスト。

SocketAsyncEventArgsをソケット(Receiveイベントのアナログ)に接続し、バッファシステムを使用していくつかの小さなパケットを1回送信するほうがよいでしょうか?別のもの)、またはすべてのパケットに異なるSocketAsyncEventArgsを使用し、それらを再利用するためにプールに格納しますか?

答えて

10

効果的に非同期ソケットを実装するには、各ソケットに1つ以上のSocketAsyncEventArgsが必要です。各SocketAsyncEventArgsのbyte []バッファにも問題があります。要するに、マネージネイティブのトランジション(送信/受信)が発生するたびにバイトバッファが固定されます。必要に応じてSocketAsyncEventArgsとバイトバッファを割り当てると、フラグメンテーションや固定されたメモリをコンパクトにすることができないため、多くのクライアントでOutOfMemoryExceptionsを実行できます。

これを処理する最善の方法は、アプリケーションが最初に起動されたときに多数のバイトとSocketAsyncEventArgsを割り当てるSocketBufferPoolクラスを作成することです。この方法で固定されたメモリは連続しています。次に、必要に応じてプールからバッファを単に再利用します。

実際には、リソースの配布を管理するために、SocketAsyncEventArgsとSocketBufferPoolクラスの周りにラッパークラスを作成するのが最適です。例として

は、ここでBeginReceiveメソッドのコードである:

private void BeginReceive(Socket socket) 
    { 
     Contract.Requires(socket != null, "socket"); 

     SocketEventArgs e = SocketBufferPool.Instance.Alloc(); 
     e.Socket = socket; 
     e.Completed += new EventHandler<SocketEventArgs>(this.HandleIOCompleted); 

     if (!socket.ReceiveAsync(e.AsyncEventArgs)) { 
      this.HandleIOCompleted(null, e); 
     } 
    } 

およびここでHandleIOCompleted方法であって、上記のコードは引き上げるTcpSocketクラスに含まれる

private void HandleIOCompleted(object sender, SocketEventArgs e) 
    { 
     e.Completed -= this.HandleIOCompleted; 
     bool closed = false; 

     lock (this.sequenceLock) { 
      e.SequenceNumber = this.sequenceNumber++; 
     } 

     switch (e.LastOperation) { 
      case SocketAsyncOperation.Send: 
      case SocketAsyncOperation.SendPackets: 
      case SocketAsyncOperation.SendTo: 
       if (e.SocketError == SocketError.Success) { 
        this.OnDataSent(e); 
       } 
       break; 
      case SocketAsyncOperation.Receive: 
      case SocketAsyncOperation.ReceiveFrom: 
      case SocketAsyncOperation.ReceiveMessageFrom: 
       if ((e.BytesTransferred > 0) && (e.SocketError == SocketError.Success)) { 
        this.BeginReceive(e.Socket); 
        if (this.ReceiveTimeout > 0) { 
         this.SetReceiveTimeout(e.Socket); 
        } 
       } else { 
        closed = true; 
       } 

       if (e.SocketError == SocketError.Success) { 
        this.OnDataReceived(e); 
       } 
       break; 
      case SocketAsyncOperation.Disconnect: 
       closed = true; 
       break; 
      case SocketAsyncOperation.Accept: 
      case SocketAsyncOperation.Connect: 
      case SocketAsyncOperation.None: 
       break; 
     } 

     if (closed) { 
      this.HandleSocketClosed(e.Socket); 
     } 

     SocketBufferPool.Instance.Free(e); 
    } 

DataReceived & DataSentイベント。注目すべきは、SocketAsyncOperation.ReceiveMessageFrom:block;ソケットにエラーがない場合、すぐに別のBeginReceive()が起動し、プールから別のSocketEventArgsを割り当てます。

もう1つ重要なのは、HandleIOCompleteメソッドで設定されたSocketEventArgs SequenceNumberプロパティです。非同期要求はキューに入れられた順に完了しますが、他のスレッド競合条件の影響を受けます。コードがBeginReceiveを呼び出す前にDataReceivedイベントを呼び出すため、BeginReceiveを呼び出した後でイベントをラーニングする前に元のIOCPを処理するスレッドがブロックされ、DataReceivedイベントを最初に発生させる新しいスレッドで2番目の非同期受信が完了する可能性があります。これは非常にまれなケースですが、SequenceNumberプロパティを使用すると、データが正しい順序で処理されるようにすることができます。

その他の注意すべき領域は、非同期送信です。多くの場合、非同期送信要求は同期的に完了します(呼び出しが同期した場合、SendAsyncはfalseを返します)、パフォーマンスが大幅に低下することがあります。 IOCPで返される非同期コールの追加オーバーヘッドは、実際には単純に同期呼び出しを使用する場合よりもパフォーマンスが悪化する可能性があります。非同期呼び出しでは、2つのカーネル呼び出しとヒープ割り当てが必要ですが、同期呼び出しはスタック上で発生します。

これはあなたのコードでは、 ビル

-1

を助け、あなたがこれを行う希望:

if (!socket.ReceiveAsync(e.AsyncEventArgs)) { 
    this.HandleIOCompleted(null, e); 
} 

しかし、それを行うとエラーになります。同期が完了したときにコールバックが呼び出されない理由は、そのようなアクションがスタックをいっぱいにする可能性があるためです。

各ReceiveAsyncが常に同期的に戻るとします。 HandleIOCompletedがしばらくしていた場合、同じスタックレベルで同期的に返された結果を処理することができます。それが同期して戻ってこなかったら、あなたはその間を壊します。 しかし、あなたがやることによって、スタックに新しいアイテムが作成されてしまいます。もしあなたが不運なことがあれば、スタックオーバーフローの例外が発生します。

関連する問題