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

本文关键字:最佳 方式 客户端 tcp | 更新日期: 2023-09-27 18:06:12

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

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

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

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

。Net有一个围绕Begin*和End*调用的异步模式。例如- BeginReceiveEndReceive。它们几乎总是有它们的非异步对应(在本例中是Receive);并达到完全相同的目标。

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

如何使用Begin/End模式

与非异步方法相比,每个Begin*方法都将多出2个参数。第一个是AsyncCallback,第二个是一个对象。这两个的意思是"这是你完成后要调用的方法"answers"这是那个方法中需要的一些数据"被调用的方法总是有相同的签名,在这个方法中,你调用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时,它会给你返回接收到的数据的长度(如果失败则抛出异常)。

更好的socket模式

这个形式会给你集中错误处理——它可以用在任何异步模式包装一个类似流的"东西"的地方(例如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() ...
}

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

应该使用异步套接字编程来实现这一点。看一下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<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

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

这样你可以同时获得两个性能,因为你允许同时接受几个连接,并且你也限制了分配用于处理传入连接的资源(线程等)的数量。

这里有一个很好的例子

好运