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

问题描述 投票:6回答:4

我有一个客户机服务器的基础架构,目前他们使用一个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个客户端,他们都想在同一时间向服务器发送数据,其中一个会通过,而其他所有的客户端都会失败.或者,如果一个客户端的连接速度很慢,并且占用了socket,所有其他的通信都会停止。

有没有什么办法能够同时接收所有客户端的数据,并在下载完成后将接收到的数据添加到队列中进行处理?

c# networking tcpclient tcplistener
4个回答
16
投票

因此,这里有一个答案,将让你开始 - 这是更多的初学者水平比我的。博文.

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

最重要的是要记住,socket的不仅仅是让调用async--它们暴露了一种叫做IOCP(IO完成端口,LinuxMono有这两个,但我忘了名字)的东西,这在服务器上使用极为重要;IOCP的核心是你的应用程序在等待数据时不会消耗一个线程。

如何使用BeginEnd模式

每一个Begin*方法都会比它的非async方法多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 它会给你返回接收到的数据的长度(如果失败了,则抛出一个异常)。

更好的套接字模式

这个表单将为你提供集中的错误处理--它可以用在async模式包装一个类似流的 "东西 "的任何地方(例如,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.
    }
}

接受多个连接

一般来说,你要做的是写一个包含你的socket等的类(以及你的async循环),并为每个客户端创建一个类。(以及你的异步循环),然后为每个客户端创建一个类。因此,比如说。

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

每一个客户端连接都应该由你的服务器类来跟踪 (这样你就可以在服务器关闭的时候干净利落地断开连接, 也可以搜索查找它们).


1
投票

你应该使用异步套接字编程来实现这一点。请看一下 例子 由MSDN提供。


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
投票

我通常做的是使用一个有几个线程的线程池.在每一个新的连接,我运行连接处理(在你的情况下 - 你在使用子句中做的一切)在池中的一个线程。

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

你有一个很好的例子 此处

运气好

© www.soinside.com 2019 - 2024. All rights reserved.