繁体   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