接受多个tcp客户端的最佳方式?

我有一个客户端/服务器基础设施。 目前,他们使用TcpClient和TcpListener在所有客户端和服务器之间发送接收数据。

我目前所做的是当收到数据时(在它自己的线程上),它被放入一个队列中供另一个线程处理,以释放套接字,使其准备好并打开以接收新数据。

// 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(); // 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个客户端并且他们都希望同时向服务器发送数据,则一个客户端将通过而其他所有客户端都将失败。或者,如果一个客户端连接速度较慢并且占用套接字,则所有其他通信将停止。

是否有一些方法可以同时从所有客户端接收数据并将接收到的数据添加到队列中以便在下载完成后进行处理?

所以这里有一个让你开始的答案 – 这比我的博客文章更初级。

.Net有一个异步模式,围绕Begin *和End *调用。 例如 – BeginReceiveEndReceive 。 他们几乎总是拥有非异步对应物(在本例中为Receive ); 并实现完全相同的目标。

要记住的最重要的事情是套接字不仅仅是调用异步 – 它们暴露了一些叫IOCP(IO完成端口,Linux / Mono有这两个,但我忘记了名字),这在一个非常重要的服务器; IOCP所做的关键是你的应用程序在等待数据时不会占用一个线程。

如何使用开始/结束模式

每个Begin *方法在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. } 

这里发生的是.Net开始等待来自套接字的数据,只要它获取数据,它就调用EndReceiveBuffer并通过EndReceiveBuffer将“自定义数据”(在本例中为buffer )传递给它。 当您调用EndReceive ,它将返回收到的数据长度(如果出现故障则抛出exception)。

更好的套接字模式

这个表单将为您提供中央error handling – 它可以在异步模式包含类似流的“事物”的任何地方使用(例如,TCP按照它发送的顺序到达,因此它可以被视为Stream对象)。

 private Socket _socket; private ArraySegment _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(_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 _buffer; public InboundConnection(Socket clientSocket) { _socket = clientSocket; _buffer = new ArraySegment(new byte[4096], 0, 4096); StartReceive(); // Start the read async loop. } private void StartReceive() ... private void ReceiveAsyncLoop() ... private void OnDataReceived() ... } 

每个客户端连接都应该由您的服务器类跟踪(这样您可以在服务器关闭时干净地断开它们,以及搜索/查找它们)。

您应该使用异步套接字编程来实现此目的。 看一下MSDN提供的示例 。

您应该使用异步方法读取数据,例如:

 // 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(); // 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

我通常使用的是一个包含多个线程的线程池。 在每个新连接上,我正在池中的一个线程中运行连接处理(在您的情况下 – 您在using子句中执行的所有操作)。

通过这种方式,您可以实现两种性能,因为您允许多个同时接受的连接,并且还限制了为处理传入连接而分配的资源(线程等)的数量。

你有一个很好的例子

祝好运