简体   繁体   English

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

[英]Best way to accept multiple tcp clients?

I have a client/server infrastructure.我有一个客户端/服务器基础设施。 At present they use a TcpClient and TcpListener to send a receive data between all the clients and server.目前他们使用 TcpClient 和 TcpListener 在所有客户端和服务器之间发送接收数据。

What I currently do is when data is received (on it's own thread), it is put in a queue for another thread to process in order to free the socket so it is ready and open to receive new data.我目前所做的是当接收到数据时(在它自己的线程上),它被放入一个队列中供另一个线程处理以释放套接字,以便它准备好并打开以接收新数据。

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

However I wanted to find out if there is a better way to do this.但是我想知道是否有更好的方法来做到这一点。 For example if there are 10 clients and they all want to send data to the server at the same time, one will get through while all the others will fail.Or if one client has a slow connection and hogs the socket all other communication will halt.例如,如果有 10 个客户端,并且他们都想同时向服务器发送数据,则其中一个会通过,而所有其他客户端都将失败。或者,如果一个客户端的连接速度很慢并占用了套接字,则所有其他通信都将停止.

Is there not some way to be able to receive data from all clients at the same time and add the received data in the queue for processing when it has finished downloading?有没有什么办法可以同时从所有客户端接收数据,并在下载完成后将接收到的数据添加到队列中进行处理?

So here is an answer that will get you started - which is more beginner level than my blog post .所以这里有一个可以帮助您入门的答案 - 这比我的博客文章更初级。

.Net has an async pattern that revolves around a Begin* and End* call. .Net 有一个围绕 Begin* 和 End* 调用的异步模式。 For instance - BeginReceive and EndReceive .例如 - BeginReceiveEndReceive They nearly always have their non-async counterpart (in this case Receive );它们几乎总是有它们的非异步对应物(在本例中为Receive ); and achieve the exact same goal.并实现完全相同的目标。

The most important thing to remember is that the socket ones do more than just make the call async - they expose something called IOCP (IO Completion Ports, Linux/Mono has these two but I forget the name) which is extremely important to use on a server;要记住的最重要的事情是套接字的作用不仅仅是使调用异步 - 它们公开了称为 IOCP(IO 完成端口,Linux/Mono 有这两个但我忘记了名称)的东西,这对于在服务器; the crux of what IOCP does is that your application doesn't consume a thread while it waits for data. IOCP 所做的关键是您的应用程序在等待数据时不消耗线程。

How to Use The Begin/End Pattern如何使用开始/结束模式

Every Begin* method will have exactly 2 more arguments in comparisson to it's non-async counterpart.每个 Begin* 方法与它的非异步对应方法相比,将有 2 个以上的参数。 The first is an AsyncCallback, the second is an object.第一个是 AsyncCallback,第二个是一个对象。 What these two mean is, "here is a method to call when you are done" and "here is some data I need inside that method."这两个的意思是,“这是一个完成后调用的方法”和“这是我需要在该方法中使用的一些数据”。 The method that gets called always has the same signature, inside this method you call the End* counterpart to get what would have been the result if you had done it synchronously.被调用的方法总是具有相同的签名,在这个方法中,你调用 End* 对应的方法来获得如果你同步完成会得到的结果。 So for example:例如:

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

What happens here is .Net starts waiting for data from the socket, as soon as it gets data it calls EndReceiveBuffer and passes through the 'custom data' (in this case buffer ) to it via state.AsyncResult .这里发生的是净将立即开始,因为它得到的数据调用等待数据从插座, EndReceiveBuffer并通过“自定义数据”(在这种情况下buffer经)将其state.AsyncResult When you call EndReceive it will give you back the length of the data that was received (or throw an exception if something failed).当您调用EndReceive它会返回接收到的数据的长度(如果出现故障则抛出异常)。

Better Pattern for Sockets更好的套接字模式

This form will give you central error handling - it can be used anywhere where the async pattern wraps a stream-like 'thing' (eg TCP arrives in the order it was sent, so it could be seen as a Stream object).这种形式将为您提供中央错误处理——它可以在异步模式包装类似流的“事物”的任何地方使用(例如,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.
    }
}

Accepting Multiple Connections接受多个连接

What you generally do is write a class that contains your socket etc. (as well as your async loop) and create one for each client.您通常做的是编写一个包含您的套接字等(以及您的异步循环)的类,并为每个客户端创建一个。 So for instance:所以例如:

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

Each client connection should be tracked by your server class (so that you can disconnect them cleanly when the server shuts down, as well as search/look them up).每个客户端连接都应该由您的服务器类跟踪(这样您可以在服务器关闭时干净地断开它们,以及搜索/查找它们)。

You should use asynchronous method of reading the data, an example is:您应该使用异步读取数据的方法,例如:

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

Also you should consider using AutoResetEvent or ManualResetEvent to be notified when new data is added to the collection so the thread that handle the data will know when data is received, and if you are using 4.0 you better switch off to using BlockingCollection instead of Queue .此外,您应该考虑使用AutoResetEventManualResetEvent在新数据添加到集合时收到通知,以便处理数据的线程知道何时接收到数据,如果您使用4.0您最好关闭使用BlockingCollection而不是Queue

You should use asynchronous socket programming to achieve this.您应该使用异步套接字编程来实现这一点。 Take a look at the example provided by MSDN.看一下 MSDN 提供的示例

What I do usually is using a thread pool with several threads.我通常做的是使用具有多个线程的线程池。 Upon each new connection I'm running the connection handling (in your case - everything you do in the using clause) in one of the threads from the pool.在每个新连接上,我都在池中的一个线程中运行连接处理(在您的情况下 - 您在 using 子句中所做的一切)。

By that you achieve both performance since you're allowing several simultaneously accepted connection and you also limiting the number of resources (threads, etc') you allocate for handling incoming connections.通过这种方式,您可以同时实现两个性能,因为您允许多个同时接受的连接,并且还限制了为处理传入连接分配的资源(线程等)数量。

You have a nice example here在这里有一个很好的例子

Good Luck祝你好运

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM