2011-08-18 7 views
5

私はクライアント/サーバーインフラストラクチャを持っています。現在、TcpClientとTcpListenerを使用して、すべてのクライアントとサーバーの間で受信データを送信します。複数のtcpクライアントを受け入れる最良の方法は?

私が現在行っているのは、データを受信すると(自分のスレッド上で)、ソケットを解放して新しいデータを受け取るためにオープンするために別のスレッドが処理するキューに入れられます。

   // Enter the listening loop. 
       while (true) 
       { 
        Debug.WriteLine("Waiting for a connection... "); 

        // Perform a blocking call to accept requests. 
        using (client = server.AcceptTcpClient()) 
        { 
         data = new List<byte>(); 

         // Get a stream object for reading and writing 
         using (NetworkStream stream = client.GetStream()) 
         { 
          // Loop to receive all the data sent by the client. 
          int length; 

          while ((length = stream.Read(bytes, 0, bytes.Length)) != 0) 
          { 
           var copy = new byte[length]; 
           Array.Copy(bytes, 0, copy, 0, length); 
           data.AddRange(copy); 
          } 
         } 
        } 

        receivedQueue.Add(data); 
       } 

しかし、私はこれを行うより良い方法があるかどうかを知りたいと思っていました。たとえば、10のクライアントがあり、同時にすべてのデータをサーバーに送信したい場合は、すべてのサーバーが失敗しますが、1つのクライアントの接続が遅く、ソケットが不安定な場合は、他のすべての通信が停止します。

同時にすべてのクライアントからデータを受信し、受信したデータをキューに追加してダウンロードが完了したら処理する方法はありませんか?

+0

恥知らずのプラグイン:http://jonathan.dickinsons.co.za/blog/2011/02/net-sockets-and-you/ - 非同期ループを短く触れます。実際の実装が含まれています(@Jalalのように 'ThreadPool'を使うべきではありません)。 –

答えて

15

ここでは、私のblog postよりも初心者レベルの、あなたが始められる答えがあります。

.NetにはBegin *およびEnd *コールを中心とした非同期パターンがあります。たとえば、 - BeginReceiveEndReceiveです。彼らはほとんどいつも非同期の対応を持っています(この場合はReceive)。まったく同じ目標を達成することができます。

覚えておくべき最も重要なことは、ソケットのものは単に非同期呼び出しを行うだけではなく、IOCP(IO Completion Ports、Linux/Monoはこれらの2つを持っていますが、私は名前を忘れています)サーバー上で使用する。 IOCPの要点は、アプリケーションがデータを待つ間にスレッドを消費しないことです。開始/終了パターン

の使用方法

すべての作業を開始*それは非非同期の対応だと方法がcomparissonに正確に2以上の引数を持つことになります。最初はAsyncCallback、2番目はオブジェクトです。これらの2つの意味は、「ここでは、完了したら電話する方法」と「その方法の中に必要なデータがあります」です。呼び出されるメソッドは常に同じシグネチャを持ちます。このメソッドの内部では、同期させた場合の結果を得るためにEnd *メソッドを呼び出します。ですから、例えば:ここに何が起こる

private void BeginReceiveBuffer() 
{ 
    _socket.BeginReceive(buffer, 0, buffer.Length, BufferEndReceive, buffer); 
} 

private void EndReceiveBuffer(IAsyncResult state) 
{ 
    var buffer = (byte[])state.AsyncState; // This is the last parameter. 
    var length = _socket.EndReceive(state); // This is the return value of the method call. 
    DataReceived(buffer, 0, length); // Do something with the data. 
} 

.NETは、すぐにそれがEndReceiveBufferを呼び出し、それに(この場合はbufferで)「カスタムデータ」を通過するデータを取得して、ソケットからデータを待って開始しますvia state.AsyncResultEndReceiveに電話すると、受信したデータの長さが返されます(何か失敗した場合は例外がスローされます)。ソケット

ため

ベター・パターンこのフォームはあなたの中央エラー処理を与える - 非同期パターンはストリーム様「のことを」ラップところそれはどこでも使用することができます(たとえば、TCPは、それが送信された順番に到着したがって、Streamオブジェクトと見なすことができます)。複数の接続

を受け入れ

private Socket _socket; 
private ArraySegment<byte> _buffer; 
public void StartReceive() 
{ 
    ReceiveAsyncLoop(null); 
} 

// Note that this method is not guaranteed (in fact 
// unlikely) to remain on a single thread across 
// async invocations. 
private void ReceiveAsyncLoop(IAsyncResult result) 
{ 
    try 
    { 
     // This only gets called once - via StartReceive() 
     if (result != null) 
     { 
      int numberOfBytesRead = _socket.EndReceive(result); 
      if(numberOfBytesRead == 0) 
      { 
       OnDisconnected(null); // 'null' being the exception. The client disconnected normally in this case. 
       return; 
      } 

      var newSegment = new ArraySegment<byte>(_buffer.Array, _buffer.Offset, numberOfBytesRead); 
      // This method needs its own error handling. Don't let it throw exceptions unless you 
      // want to disconnect the client. 
      OnDataReceived(newSegment); 
     } 

     // Because of this method call, it's as though we are creating a 'while' loop. 
     // However this is called an async loop, but you can see it the same way. 
     _socket.BeginReceive(_buffer.Array, _buffer.Offset, _buffer.Count, SocketFlags.None, ReceiveAsyncLoop, null); 
    } 
    catch (Exception ex) 
    { 
     // Socket error handling here. 
    } 
} 

あなたは、一般的にやっていることはあなたのソケットなどを含むクラス(だけでなく、あなたの非同期ループ)を書いて、各クライアントのために作成されます。だから、例えば:

public class InboundConnection 
{ 
    private Socket _socket; 
    private ArraySegment<byte> _buffer; 

    public InboundConnection(Socket clientSocket) 
    { 
     _socket = clientSocket; 
     _buffer = new ArraySegment<byte>(new byte[4096], 0, 4096); 
     StartReceive(); // Start the read async loop. 
    } 

    private void StartReceive() ... 
    private void ReceiveAsyncLoop() ... 
    private void OnDataReceived() ... 
} 

(サーバーがシャットダウンしたときにきれいにそれらを切り離すことができるように、だけでなく、検索は/それらを見て)各クライアント接続は、サーバーのクラスによって追跡されなければなりません。

+0

+1すべての素晴らしい素材をありがとう。 – Dylan

+0

私はあなたも同じようにクライアントを受け入れることができることを忘れていました。 BeginAcceptTcpClient。非同期ループも同じように設定できます。 –

+1

ブログの投稿リンクが死んでいます。しかし、ここはarchive.orgにあります:https://web.archive.org/web/20121127003207/http://jonathan.dickinsons.co.za/blog/2011/02/net-sockets-and-you –

0

私は通常、複数のスレッドを持つスレッドプールを使用しています。 私は新しい接続ごとに、プールからのスレッドの1つで接続処理(あなたの場合はusing節で行うこと)を実行しています。

これにより、同時に複数の接続が許可され、着信接続を処理するために割り当てるリソース(スレッドなど)の数も制限されるため、両方のパフォーマンスが向上します。

あなたがデータを読み出す非同期メソッド、例を使用する必要がありますhere

グッドラック

+0

もう一度、Thread-Per-Clientのことを考えてください。これは本当に悪い習慣です。 –

1

素敵な例を持っている:

// Enter the listening loop. 
while (true) 
{ 
    Debug.WriteLine("Waiting for a connection... "); 

    client = server.AcceptTcpClient(); 

    ThreadPool.QueueUserWorkItem(new WaitCallback(HandleTcp), client); 
} 

private void HandleTcp(object tcpClientObject) 
{ 
    TcpClient client = (TcpClient)tcpClientObject; 
    // Perform a blocking call to accept requests. 

    data = new List<byte>(); 

    // Get a stream object for reading and writing 
    using (NetworkStream stream = client.GetStream()) 
    { 
     // Loop to receive all the data sent by the client. 
     int length; 

     while ((length = stream.Read(bytes, 0, bytes.Length)) != 0) 
     { 
      var copy = new byte[length]; 
      Array.Copy(bytes, 0, copy, 0, length); 
      data.AddRange(copy); 
     } 
    } 

    receivedQueue.Add(data); 
} 

また、あなたはAutoResetEventManualResetEventへの使用を検討すべきです新しいデータがコレクションに追加されたときに通知されるので、データを処理するスレッドはデータが受信されたことを知り、4.0を使用している場合は、0 Queueの代わりにです。

+0

既にBlockingCollectionを使用しています。私はファイルを受け取る専用スレッドを持っているため、同期メソッドを使用しています。上記の例では、2つのクライアントが同時に接続する場合、server.AcceptTcpClientは両方を受け入れるか、またはTcpListenerが次に使用できるようになるまで待機します(HandleTcpの後)。 メモとして、.Net 4を使用している場合は、ThreadPoolの代わりにタスクライブラリを使用する必要があります。 – Dylan

+0

これは両方を受け入れるので、ここで 'Thread'を使用しています。なぜなら、別のスレッドの最初のデータを読み込んで現在のスレッドをブロックしないようにするためです。' ThreadPoo.Queu..'は、呼び出し元のスレッドは即座に同時に、クライアントを処理するための新しいスレッドを作成します。 –

+0

-1 ThreadPoolを使用しています。 .Net 3.5では 'Begin/End-Receive'を使うべきです。 .Net 4.0では、新しいイベントモデル、つまりTPLを使用できます。あなたのコードはIOCPをまったく使用しません。それは貧しい示唆になります。 –

1

これを達成するには、非同期ソケットプログラミングを使用する必要があります。 MSDNが提供するexampleをご覧ください。

+1

+1はMSDNを引用しています - 真実の源:) –

関連する問題