8

据我了解,TcpListener一旦您调用Start(). 每次调用AcceptTcpClient(or 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;
        }
    }

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

我们的一个想法是AcceptTcpClient立即调用多个对象之一。这样,我们可以在不同的线程上轮询这些队列(每个线程一个队列),而不会遇到可能在等待其他操作时阻塞线程的监视器。然后,每个队列线程可以用来在一个线程中完成工作,然后继续将其队列中的下一个出队。你会推荐这种方法,还是我们正在使用的问题,并且没有任何快速出队可以解决这个问题?EnqueueTcpClientQueue<TcpClient>DequeueThreadPool.QueueUserWorkItemThreadPoolTcpClientTcpListener

4

5 回答 5

3

我已经编写了一些直接使用套接字的代码,但是我缺乏对 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();
}
于 2010-04-30T15:14:56.097 回答
2

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

于 2010-04-30T15:23:31.667 回答
2

在其他问题中提到过,但我建议在您的 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 连接将被自动拒绝,您甚至没有机会处理它们。

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

于 2010-05-01T04:21:42.670 回答
1

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

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

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

于 2010-04-30T16:23:51.953 回答
1

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

于 2010-04-30T16:56:09.003 回答