簡體   English   中英

C#套接字中的並發客戶端問題

[英]Concurrent Clients issue in C# Sockets

我正在開發客戶端服務器應用程序,Windows Server和Linux Client。 我正在用多個並發客戶端測試服務器。 我僅嘗試了來自客戶端的20個並發連接,但我注意到盡管所有20個請求都是相同的,但仍未處理某些請求。 他們進入隊列,由於某種原因,輪到客戶端時關閉了客戶端(客戶端連接超時為5秒)。

然后,我添加了一個Thread.Sleep(1000),以檢查它是否真的是異步的,但后來我意識到,直到超時,它才處理其他請求。 盡管事實

  1. 它是異步的
  2. 在睡覺之前設置了ManualResetEvent。

現在我想知道我在這里缺少什么,因為大多數並發連接會發生這種情況?

public static void StartServer(IPAddress ipAddr, int port)
{
    //IPEndPoint serverEndPoint = new IPEndPoint(ipAddr, port);
    IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Any, port);
    Socket clientListener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    try
    {
        clientListener.Bind(serverEndPoint);
        clientListener.Listen(500);
        Console.WriteLine("-- Server Listening: {0}:{1}",ipAddr,port);
        while (true)
        {
            resetEvent.Reset();
            Console.WriteLine("|| Waiting for connection");
            clientListener.BeginAccept(new AsyncCallback(AcceptConnection), clientListener);
            resetEvent.WaitOne();
        }
    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
    }
}


public static void AcceptConnection(IAsyncResult ar)
{
    // Get the socket that handles the client request.
    Socket listener = (Socket)ar.AsyncState;
    Socket handler = listener.EndAccept(ar);
    // Signal the main thread to continue.
    resetEvent.Set();
    // Create the state object.
    JSStateObject state = new JSStateObject();
    state.workSocket = handler;
    if (handler.Connected)
    {
        Console.WriteLine("** Connected to: {0}", handler.RemoteEndPoint.ToString());
        state.workingDirectory = JSUtilityClass.CreatetTemporaryDirectry();
        try
        {
            Thread.Sleep(1000);
            Receive(state);
        }
        catch (Exception e)
        {
            handler.Shutdown(SocketShutdown.Both);
            handler.Close();
            Console.WriteLine(e.Message);
        }
    }
}

我創建了一個測試,該測試發送了100次連接嘗試,發現一些使速度變慢的事情。

為什么這么慢?

我在AcceptConnection中放置一個斷點以查看調用堆棧,就是這樣

ConsoleApplication1.exe!ConsoleApplication1.Program.AcceptConnection(System.IAsyncResult ar) Line 62    C#
        System.dll!System.Net.LazyAsyncResult.Complete(System.IntPtr userToken) + 0x69 bytes    
        System.dll!System.Net.ContextAwareResult.CaptureOrComplete(ref System.Threading.ExecutionContext cachedContext, bool returnContext) + 0xab bytes    
        System.dll!System.Net.ContextAwareResult.FinishPostingAsyncOp(ref System.Net.CallbackClosure closure) + 0x3c bytes  
        System.dll!System.Net.Sockets.Socket.BeginAccept(System.AsyncCallback callback, object state) + 0xe3 bytes  
        ConsoleApplication1.exe!ConsoleApplication1.Program.StartServer(System.Net.IPAddress ipAddr, int port) Line 48 + 0x32 bytes C#

因此,回調AcceptConnection從與調用BeginAccept相同的線程運行。 我看了看帶有反射器的FinishPostingAsyncOp ,它使用的是異步模式,如果隊列中已經有一個套接字操作等待處理,它將在當前線程上執行,否則,如果沒有任何待處理,它將稍后將在其他線程中處理

SocketAsyncEventArgs sae = new SocketAsyncEventArgs();
sae.Completed += new EventHandler<SocketAsyncEventArgs>(SocketOperation_Completed);
if (!clientListener.AcceptAsync(sae))
    AcceptConnection(clientListener, sae); // operation completed synchronously, process the result
else
    // operation will complete on a IO completion port (different thread) which we'll handle in the Completed event

因此,如您所見,在這種情況下該程序實際上是完全同步的,並且使用1秒的Thread.Sleep至少需要100秒才能接受所有連接,到那時大多數連接將超時。

解決方案

即使BeginAccept方法摘要說

開始異步操作以接受傳入的連接嘗試。

事實證明,故事還有更多

從MSDN http://msdn.microsoft.com/en-AU/library/system.net.sockets.socket.beginaccept.aspx

BeginAccept(Int32,AsyncCallback,Object)開始異步操作以接受傳入的連接嘗試,並接收客戶端應用程序發送的第一個數據塊。

因此,它會在觸發回調之前以較短的超時執行讀取操作。 您可以通過指定receiveSize為0來禁用它。

clientListener.BeginAccept(new AsyncCallback(AcceptConnection), clientListener);

clientListener.BeginAccept(0, new AsyncCallback(AcceptConnection), clientListener);

這樣可以加快速度,如果我們從AcceptConnection刪除Thread.Sleep(1000) ,那么所有連接都將很快被接受。

如果您將Thread.Sleep(1000)留在其中以模擬工作負載或僅用於測試,則您可能希望通過執行以下操作准備服務器以處理此類負載

int minWorkerThreads = 0;
int minCompletionPortThreads = 0;
ThreadPool.GetMinThreads(out minWorkerThreads, out minCompletionPortThreads);
ThreadPool.SetMinThreads(minWorkerThreads, 100);

其中100是您希望可用於處理套接字操作的線程數量。

另一件事,這是個人喜好問題,只是想知道您可能想從AcceptConnection內調用BeginAccept ,因此不需要while循環。 即改變這個

while (true)
{
    resetEvent.Reset();
    Console.WriteLine("|| Waiting for connection");
    clientListener.BeginAccept(new AsyncCallback(AcceptConnection), clientListener);
    resetEvent.WaitOne();
}

對此

Console.WriteLine("|| Waiting for connection");
clientListener.BeginAccept(new AsyncCallback(AcceptConnection), clientListener);

並將另一個BeginAccept放在AcceptConnection

public static void AcceptConnection(IAsyncResult ar)
{
    // Get the socket that handles the client request.
    Socket listener = (Socket)ar.AsyncState;
    // start another listening operation
    listener.BeginAccept(new AsyncCallback(AcceptConnection), listener);
    ... the rest of the method
}

暫無
暫無

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

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