2011-08-18 130 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 - 真相的来源:)。 –

相关问题