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

时间:2011-08-18 08:13:24

标签: c# networking tcpclient tcplistener

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

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

4 个答案:

答案 0 :(得分:15)

所以这里有一个让你开始的答案 - 比我的blog post更初级。

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

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

如何使用开始/结束模式

每个Begin *方法在comparisson中都会有2个以上的参数非同步。第一个是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的数据并通过'自定义数据'(在本例中为buffer)传递给它通过state.AsyncResult。当你调用EndReceive时,它会返回收到的数据长度(如果出现故障则抛出异常)。

更好的套接字模式

此表单将为您提供集中错误处理 - 它可以在异步模式包含类似流的“事物”的任何地方使用(例如,TCP按照它发送的顺序到达,因此它可以被视为{{1对象)。

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.
    }
}

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

答案 1 :(得分: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,以便处理数据的线程知道何时收到数据,以及您是否正在使用{ {1}}您最好关闭使用4.0而不是BlockingCollection

答案 2 :(得分:1)

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

答案 3 :(得分:0)

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

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

你有一个很好的例子here

祝你好运