簡體   English   中英

接受多個 tcp 客戶端的最佳方式?

[英]Best way to accept multiple tcp clients?

我有一個客戶端/服務器基礎設施。 目前他們使用 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 個客戶端,並且他們都想同時向服務器發送數據,則其中一個會通過,而所有其他客戶端都將失敗。或者,如果一個客戶端的連接速度很慢並占用了套接字,則所有其他通信都將停止.

有沒有什么辦法可以同時從所有客戶端接收數據,並在下載完成后將接收到的數據添加到隊列中進行處理?

所以這里有一個可以幫助您入門的答案 - 這比我的博客文章更初級。

.Net 有一個圍繞 Begin* 和 End* 調用的異步模式。 例如 - BeginReceiveEndReceive 它們幾乎總是有它們的非異步對應物(在本例中為Receive ); 並實現完全相同的目標。

要記住的最重要的事情是套接字的作用不僅僅是使調用異步 - 它們公開了稱為 IOCP(IO 完成端口,Linux/Mono 有這兩個但我忘記了名稱)的東西,這對於在服務器; IOCP 所做的關鍵是您的應用程序在等待數據時不消耗線程。

如何使用開始/結束模式

每個 Begin* 方法與它的非異步對應方法相比,將有 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.
}

這里發生的是凈將立即開始,因為它得到的數據調用等待數據從插座, EndReceiveBuffer並通過“自定義數據”(在這種情況下buffer經)將其state.AsyncResult 當您調用EndReceive它會返回接收到的數據的長度(如果出現故障則拋出異常)。

更好的套接字模式

這種形式將為您提供中央錯誤處理——它可以在異步模式包裝類似流的“事物”的任何地方使用(例如,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() ...
}

每個客戶端連接都應該由您的服務器類跟蹤(這樣您可以在服務器關閉時干凈地斷開它們,以及搜索/查找它們)。

您應該使用異步讀取數據的方法,例如:

// 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

您應該使用異步套接字編程來實現這一點。 看一下 MSDN 提供的示例

我通常做的是使用具有多個線程的線程池。 在每個新連接上,我都在池中的一個線程中運行連接處理(在您的情況下 - 您在 using 子句中所做的一切)。

通過這種方式,您可以同時實現兩個性能,因為您允許多個同時接受的連接,並且還限制了為處理傳入連接分配的資源(線程等)數量。

在這里有一個很好的例子

祝你好運

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM