代码之家  ›  专栏  ›  技术社区  ›  Matt Brindley

TcpListener排队连接的速度比我清除连接的速度快

  •  8
  • Matt Brindley  · 技术社区  · 15 年前

    据我所知, TcpListener 调用后将对连接进行排队 Start() . 每次你打电话 AcceptTcpClient (或) BeginAcceptTcpClient ,它将从队列中取出一个项目。

    如果我们加载测试 TCplistener公司 应用程序通过一次发送1000个连接,队列的生成速度远远快于我们清除它的速度,导致(最终)客户端超时,因为它没有得到响应,因为它的连接仍在队列中。然而,服务器似乎没有承受太大的压力,我们的应用程序没有消耗太多的CPU时间,机器上的其他监控资源也没有出一点差错。感觉我们现在的效率不够。

    我们在打电话 BeginAcceptTcpListener 然后马上交给 ThreadPool 线程来实际执行工作,然后调用 beginacepttcpclient开始 再一次。所涉及的工作似乎没有给机器带来任何压力,基本上只是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 ,但我想知道我们该怎么做?

    我们的一个想法是打电话 接受客户 立即 Enqueue 这个 TCP客户端 成倍数之一 Queue<TcpClient> 物体。这样,我们就可以在不同的线程(每个线程一个队列)上轮询这些队列,而不必在等待其他线程时遇到可能阻塞线程的监视器 Dequeue 操作。然后,每个队列线程都可以使用 ThreadPool.QueueUserWorkItem 在一个 线程池 线程,然后进入下一个出列 TCP客户端 在队伍里。你是推荐这种方法,还是我们使用的问题 TCplistener公司 再多的快速排队也不能解决这个问题?

    5 回复  |  直到 14 年前
        1
  •  3
  •   dtb    15 年前

    我已经生成了一些直接使用套接字的代码,但是我缺乏对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();
    }
    
        2
  •  2
  •   Jonathan Beerhalter    15 年前

    除非我遗漏了什么,否则您将调用beingAcceptTCPClient,这是异步的,但是您将调用waitOne()来等待异步代码完成,这将有效地使进程同步。您的代码一次只能接受一个客户端。还是我疯了?至少,这看起来像是大量的上下文切换。

        3
  •  2
  •   Steve Wranovsky    15 年前

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

    
        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连接。如果您没有足够快地接受连接,并且此积压工作已满,则TCP连接将被自动拒绝,您甚至没有机会处理它们。

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

        4
  •  1
  •   Thomas Levesque    15 年前

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

        5
  •  1
  •   Len Holgate    14 年前

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

    我有一个TCP测试程序,我用它来测试我的服务器框架,它可以做的事情,像X连接,总共成批Y,每批之间的间隔是Z毫秒;我个人发现,这是一个比“一次大量”更真实的世界。它是免费的,可能会有帮助,你可以从这里得到它: http://www.lenholgate.com/blog/2005/11/windows-tcpip-server-performance.html

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