2011-08-18 175 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個客戶端,並且他們都希望同時向服務器發送數據,則其他客戶端將會通過,而其他客戶端則會失敗。或者,如果一個客戶端的連接速度較慢並且佔用套接字,則所有其他通信都將停止。

不是有一些方法能夠同時接收來自所有客戶端的數據,並在隊列中等待處理時,它已經完成下載添加接收到的數據?

+0

無恥的插件:http://jonathan.dickinsons.co.za/blog/2011/02/net-sockets-and-you/ - 短暫涉及異步循環;幷包含一個真正的實現(你不應該像使用@Jalal建議的那樣使用'ThreadPool')。 –

回答

15

所以這裏是一個答案,這將讓你開始 - 這是比較初級的水平比我blog post

.Net有圍繞一開始*和結束*呼叫圍繞異步模式。例如 - BeginReceiveEndReceive。他們幾乎總是有他們的非異步對手(在這種情況下Receive);並達到完全相同的目標。

要記住的最重要的事情是,插座的人做更多的不僅僅是撥打電話異步 - 他們揭露一些所謂的IOCP(IO完成端口,Linux的/單聲道有兩個,但我忘了名字),這是非常重要的在服務器上使用; IOCP所做的關鍵是你的應用程序在等待數據時不消耗線程。

如何使用開始/結束模式

每個*開頭的方法將會有確切的2個論據comparisson到它的非異步副本。第一個是AsyncCallback,第二個是對象。這兩個含義是什麼,「這是一個當你完成時調用的方法」,「這裏是我需要的一些數據。」被調用的方法總是具有相同的簽名,在此方法內,您可以調用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. 
} 

這裏會發生什麼事是淨開始從插座等待數據,一旦它得到它調用EndReceiveBuffer,並通過「自定義數據」通過(在這種情況下buffer)給它的數據通過state.AsyncResult。當您撥打EndReceive時,它會將您接收到的數據的長度返回給您(或者在失敗的情況下拋出異常)。對於套接字

更好的模式

這種形式會給你中央的錯誤處理 - 它可以在任何地方用在異步模式包裝了一個類似於流的「東西」(如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

我做什麼通常使用線程池多個線程。 對於每個新連接,我都在池中的一個線程中運行連接處理(您的情況 - 您在using子句中執行的所有操作)。

通過,既然你允許多個同時接受連接,並且還限制資源('線等),你分配用於處理傳入的連接數你實現這兩個性能。

你有一個很好的例子here

好運

+0

再次與線程每客戶端的東西。這是非常糟糕的做法。 –

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,則最好關閉以使用BlockingCollection而不是Queue

+0

已經在使用BlockingCollection。我使用的是同步方法,因爲我有一個專用線程來接收這些文件。在上面的示例中,如果兩個客戶端同時連接,server.AcceptTcpClient是否同時接受或將排隊等待下一個可用的TcpListener(在HandleTcp之後)? 同樣值得注意的是,如果您使用.Net 4,則應使用Task庫而不是ThreadPool。 – Dylan

+0

它會接受這兩個,這就是爲什麼我們在這裏使用'Thread',因爲它會讀取另一個線程中的第一個數據,所以它不會阻塞當前線程,所以'ThreadPoo.Queu..'將把句柄返回給調用者線程立即同時創建一個新的線程來處理客戶端。 –

+0

-1使用ThreadPool。在.net 3.5中,你應該使用'Begin/End-Receive'。在.Net 4.0上,您可以使用新的事件模型或TPL。你的代碼根本不使用IOCP;這是一個不好的建議。 –

1

你應該使用異步socket編程來實現這一點。看看MSDN提供的example

+1

+1引用MSDN - 真相的來源:)。 –

相關問題