【问题标题】:TcpListener is queuing connections faster than I can clear themTcpListener 排队连接的速度比我清除它们的速度快
【发布时间】:2011-02-14 06:45:53
【问题描述】:

据我了解,TcpListener 将在您致电 Start() 后排队连接。每次调用AcceptTcpClient(或BeginAcceptTcpClient)时,它都会从队列中取出一项。

如果我们通过一次向它发送 1,000 个连接来对我们的TcpListener 应用程序进行负载测试,队列的构建速度远远快于我们清除它的速度,导致(最终)客户端超时,因为它没有得到响应,因为它的连接仍在队列中。但是,服务器似乎没有太大压力,我们的应用程序没有消耗太多 CPU 时间,并且机器上的其他受监控资源也没有出汗。感觉我们现在的运行效率不够高。

我们调用BeginAcceptTcpListener,然后立即移交给ThreadPool 线程来实际完成工作,然后再次调用BeginAcceptTcpClient。所涉及的工作似乎没有给机器带来任何压力,它基本上只是 3 秒的睡眠,然后是字典查找,然后将 100 字节写入TcpClient 的流。

这是我们正在使用的TcpListener 代码:

    // Thread signal.
    private static ManualResetEvent tcpClientConnected = new ManualResetEvent(false);

    public void DoBeginAcceptTcpClient(TcpListener listener)
    {
        // Set the event to nonsignaled state.
        tcpClientConnected.Reset();

        listener.BeginAcceptTcpClient(
            new AsyncCallback(DoAcceptTcpClientCallback),
            listener);

        // Wait for signal
        tcpClientConnected.WaitOne();
    }

    public void DoAcceptTcpClientCallback(IAsyncResult ar)
    {
        // Get the listener that handles the client request, and the TcpClient
        TcpListener listener = (TcpListener)ar.AsyncState;
        TcpClient client = listener.EndAcceptTcpClient(ar);

        if (inProduction)
            ThreadPool.QueueUserWorkItem(state => HandleTcpRequest(client, serverCertificate));  // With SSL
        else
            ThreadPool.QueueUserWorkItem(state => HandleTcpRequest(client));  // Without SSL

        // Signal the calling thread to continue.
        tcpClientConnected.Set();
    }

    public void Start()
    {
        currentHandledRequests = 0;
        tcpListener = new TcpListener(IPAddress.Any, 10000);
        try
        {
            tcpListener.Start();

            while (true)
                DoBeginAcceptTcpClient(tcpListener);
        }
        catch (SocketException)
        {
            // The TcpListener is shutting down, exit gracefully
            CheckBuffer();
            return;
        }
    }

我假设答案将与使用 Sockets 而不是 TcpListener 或至少使用 TcpListener.AcceptSocket 有关,但我想知道我们将如何去做?

我们的一个想法是调用AcceptTcpClient 并立即将EnqueueTcpClient 调用到多个Queue<TcpClient> 对象之一中。这样,我们可以在单独的线程上轮询这些队列(每个线程一个队列),而不会遇到可能在等待其他 Dequeue 操作时阻塞线程的监视器。然后每个队列线程可以使用ThreadPool.QueueUserWorkItemThreadPool 线程中完成工作,然后继续将其队列中的下一个TcpClient 出队。您会推荐这种方法,还是我们正在使用TcpListener 的问题,并且再多的快速出队也无法解决这个问题?

【问题讨论】:

  • 我认为您可能有一个错误的假设。被丢弃的传入连接不会从队列中丢弃。他们从来没有进入队列,因为队列已经满了。您可以选择指定更大的队列长度。这将在一定程度上缓解问题。真正的问题是,以如此快的速度猛击服务器并不是很现实。为处理 1k 个客户端而构建的服务器通常仍需要以计量的速度进行连接。
  • 用更大的“积压”值试试这个:msdn.microsoft.com/en-us/library/5kh8wf6s(v=VS.100).aspx
  • MSDN 说如果超出队列大小,TcpListener 将抛出 SocketException,我没有看到任何 SocketExceptions(CheckBuffer 方法日志),所以我假设我还没有达到它的大小限制.但这是 1k 连接的好点。
  • 澄清一下,这些 SocketExceptions 是在客户端而不是服务器上抛出的。

标签: c# .net sockets tcpclient tcplistener


【解决方案1】:

我已经编写了一些直接使用套接字的代码,但是我缺乏对 1000 个客户端执行负载测试的方法。您能否尝试测试此代码与您当前的解决方案的比较?我对结果非常感兴趣,因为我现在正在构建一个需要接受大量连接的服务器。

static WaitCallback handleTcpRequest = new WaitCallback(HandleTcpRequest);

static void Main()
{
    var e = new SocketAsyncEventArgs();
    e.Completed += new EventHandler<SocketAsyncEventArgs>(e_Completed);

    var socket = new Socket(
        AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    socket.Bind(new IPEndPoint(IPAddress.Loopback, 8181));
    socket.Listen((int)SocketOptionName.MaxConnections);
    socket.AcceptAsync(e);

    Console.WriteLine("--ready--");
    Console.ReadLine();
    socket.Close();
}

static void e_Completed(object sender, SocketAsyncEventArgs e)
{
    var socket = (Socket)sender;
    ThreadPool.QueueUserWorkItem(handleTcpRequest, e.AcceptSocket);
    e.AcceptSocket = null;
    socket.AcceptAsync(e);
}

static void HandleTcpRequest(object state)
{
    var socket = (Socket)state;
    Thread.Sleep(100); // do work
    socket.Close();
}

【讨论】:

  • +1 这更符合我的做法。简而言之,不要依赖你的主线程来启动每个异步接受操作。第一次从主线程启动异步接受,然后从回调方法本身启动每个后续异步接受。
【解决方案2】:

除非我遗漏了什么,否则您调用的是 BeingAcceptTcpClient,它是异步的,但随后您调用的是 WaitOne() 以等待异步代码完成,这实际上使流程同步。您的代码一次只能接受一个客户。还是我完全疯了?至少,这似乎是很多无用的上下文切换。

【讨论】:

  • 上面的代码来自 MSDN:msdn.microsoft.com/en-us/library/… - 所以你是说我应该在 DoAcceptTcpClientCallback 方法的第一行再次调用 Begin?不是必须先调用 End 再调用 Begin 吗?
  • 你可以在DoAcceptTcpClientCallback的end调用listener.BeginAcceptTcpClient(在你调用listener.EndAcceptTcpClient之后)。 ManualResetEvent 不是必需的。但是 Begin/EndAcceptTcpClient 方法存在一些性能问题,这就是将 AcceptAsync 方法添加到 Socket 类的原因。
  • 嗯,我明白了。我不确定再次调用 Begin 与使用 ManualResetEvent 会大大提高性能吗?我现在已将其更改为删除 ManualResetEvent 并且我在 End 之后调用 Begin,但在 ThreadPool 之前,我会看看它是如何执行的。
【解决方案3】:

在其他问题中提到过,但我建议在您的 tcpListener.Start() 方法中,使用允许您将积压设置为高于您所期望的最大连接数的重载一次:


    public void Start()
    {
        currentHandledRequests = 0;
        tcpListener = new TcpListener(IPAddress.Any, 10000);
        try
        {
            tcpListener.Start(1100);  // This is the backlog parameter

            while (true)
                DoBeginAcceptTcpClient(tcpListener);
        }
        catch (SocketException)
        {
            // The TcpListener is shutting down, exit gracefully
            CheckBuffer();
            return;
        }
    }

基本上,此选项设置允许有多少“挂起”的 TCP 连接等待调用 Accept。如果您接受连接的速度不够快,并且此积压已填满,则 TCP 连接将被自动拒绝,您甚至没有机会处理它们。

正如其他人所提到的,另一种可能性是加快处理传入连接的速度。但是,您仍然应该将 backlog 设置为更高的值,即使您可以加快接受时间。

【讨论】:

    【解决方案4】:

    只是一个建议:为什么不同步接受客户端(使用AcceptTcpClient 而不是BeginAcceptTcpClient),然后在新线程上处理客户端?这样一来,您就不必等待客户处理完毕才能接受下一个客户。

    【讨论】:

    • 这被否决了,因为正如 Thomas 所指出的,在 AcceptTcpClient 方面同步执行操作需要每个客户端 1 个线程。默认情况下,.NET 将自动为每个新线程分配 1MB。这不能很好地扩展。
    • @kmarks2 - 是的,所以只需将“在新线程上处理客户端”替换为“在线程池中处理客户端”,答案是 100% 没问题。我认为这里的重点是 TcpListener 可以完美地扩展没有 Async 方法。每当有人尝试使用 TcpListener 类时,人们也可以停止求助于 Socket 类,就像接受的 anwser 一样。这个其他答案对应于问题:stackoverflow.com/questions/5339782/…
    【解决方案5】:

    首先要问自己的是“1000 个连接是否合理”。我个人认为您不太可能陷入这种情况。您更有可能在短时间内发生 1000 个连接。

    我有一个 TCP 测试程序,我用它来测试我的服务器框架,它可以在 Y 批次中进行总共 X 连接之类的事情,每批次之间的间隔为 Z ms;我个人认为,这比“一次性大量数字”更真实。它是免费的,它可能会有所帮助,你可以从这里获得它:http://www.lenholgate.com/blog/2005/11/windows-tcpip-server-performance.html

    正如其他人所说,增加监听积压,更快地处理连接,尽可能使用异步接受......

    【讨论】:

    • 我正在使用 seige (google it) 软件来模拟压力测试,但它只是 http :)
    • 您真的希望在生产中同时实现 1000 个连接吗?
    猜你喜欢
    • 2016-10-22
    • 1970-01-01
    • 1970-01-01
    • 2010-11-20
    • 1970-01-01
    • 1970-01-01
    • 2021-04-27
    • 2023-04-02
    相关资源
    最近更新 更多