关于套接字监听和 backlog 的问题

11

我正在用C#编写一个应用程序,需要处理传入的连接,但我之前没有做过服务器端编程。这引导我提出以下问题:

  • 高backlog / 低backlog的优缺点是什么?为什么不应该将backlog设置为很大的数字?
  • 如果我调用Socket.Listen(10),在10次Accept()之后,我是否需要再次调用Listen()?还是每次Accept()之后都必须调用Listen()?
  • 如果我将backlog设置为0,并且假设两个人同时想连接我的服务器,会发生什么?(我在循环中调用Socket.Select并检查监听套接字的可读性,在处理第一个连接后,如果我再次调用Listen(),下一次迭代时第二个连接是否成功?)

提前感谢。

3个回答

25
正如Pieter所说的那样,监听队列是操作系统用来存储已被TCP堆栈接受但尚未被程序接受的连接的队列。在概念上,当客户端连接时,它会被放置在此队列中,直到您的Accept()代码将其移除并交给您的程序。
因此,监听队列是一个调整参数,可用于帮助您的服务器处理并发连接尝试的高峰。请注意,这与服务器可以维护的并发连接的最大数量无关。例如,如果您的服务器每秒接收10个新连接,则即使这些连接很长时间并且您的服务器正在支持10,000个并发连接(假设您的服务器没有使用完CPU来服务现有连接!),调整监听队列的大小也不太可能产生任何影响。然而,如果服务器偶尔经历短暂时期,当它每秒接受1000个新连接时,则通过调整监听队列以提供更大的队列可以防止某些连接被拒绝,从而为您的服务器提供更多时间为每个连接调用Accept()
至于优缺点,优点是您可以更好地处理并发连接尝试的高峰,而相应的缺点是操作系统需要为监听队列分配更多的空间,因为它更大。因此,这是性能与资源之间的权衡。
个人而言,我会将监听队列设置为可以通过配置文件进行外部调整。
如何调用listen和accept以及何时调用取决于您使用的套接字代码风格。对于同步代码,您需要使用一个值(例如10)一次调用Listen(),然后循环调用Accept()。调用Listen()设置了客户端可以连接到的终点,并在概念上创建指定大小的侦听队列。调用Accept()从侦听队列中移除一个挂起的连接,为应用程序使用设置一个套接字,并将其作为新建立的连接传递给您的代码。如果您的代码调用Accept()、处理新连接并循环调用Accept()所需的时间长于并发连接尝试之间的间隔,则会开始在侦听队列中累积条目。
对于异步套接字,情况可能略有不同。如果您正在使用异步接受,则像以前一样只需监听一次,然后发布多个(可配置的)异步接受。每当其中一个完成时,您就会处理新连接并发布一个新的异步接受。通过这种方式,您拥有了侦听队列和挂起接受“队列”,因此您可以更快地接受连接(此外,异步接受在线程池线程上处理,因此您不必担心单个紧密的接受循环)。通常情况下,这更具可扩展性,并提供了两个调整点以处理更多的并发连接尝试。

嗨,我正在尝试编写测试代码...但是代码并没有清楚地表明问题所在...我正在循环处理10,000个客户端请求...每个连接都会建立自己的TCP连接。我已将backlog设置为20,000。监听器异步接受连接并将它们放入各自的线程中。我已告诉监听器上接受的每个连接要执行5秒的工作。几乎瞬间,客户端就拒绝了我的请求...我知道这不是真实世界的测试...我只是想了解所有限制...你能帮助我理解吗? - Seabizkit
  1. 与其在现有答案中添加模糊的评论,不如写一个问题会更好。
  2. 使用“每个连接一个线程”模型扩展到10,000并不理想。
  3. 这种类型的测试可能会遇到各种系统资源限制,具体取决于它的结构(TIME_WAIT是其中之一)。
  4. 20,000的积压仅表示“允许20,000个连接尝试挂起”,它不影响可能的活动连接数量,20,000对于此来说是一个非常大的数字,可能比操作系统允许的还要大...
  5. 你可能还有很多其他错误,我需要看到代码。
- Len Holgate
@LenHolgate 客户端使用 send/write 调用发出的数据请求,是否也会排队在监听后备队列中?在客户端代码中调用 send/write 时,我们将服务器的监听套接字作为这些调用的输入。 - Ayush
不,这只是针对新的连接尝试。 - Len Holgate

3
backlog 的作用是提供一个队列,其中包含试图连接服务器但尚未处理的客户端。这涉及客户端实际连接到服务器和您接受或结束接受客户端之间的时间。
如果接受客户端需要很长时间,则可能会导致积压队列已满,并且新的客户端连接将被拒绝,直到您有时间处理队列中的客户端。
关于您的问题:
1. 我没有关于此的信息。如果默认数字没有任何问题(没有被拒绝的客户端连接),请将其保留在默认值。如果新客户端想要连接时出现许多错误,请增加该数字。但是,在增加积压量之前,您应该解决接受新客户端所需时间过长的问题; 2. 不,这由系统处理。接受客户端的常规机制会处理这个问题; 3. 请参阅我早期的解释。

请问,backlog队列中排队的连接是否与服务器可以承载的最大连接数无关?例如,具有backlog 10的服务器仍然可以承载100甚至1000个客户端。谢谢。 - jiajianrong

3

试一下这个程序,你就会知道什么是backlog好处。

using System;
using System.Net;
using System.Net.Sockets;

/*
   This program creates TCP server socket. Then a large number of clients tries to connect it.
   Server counts connected clients. The number of successfully connected clients depends on the BACKLOG_SIZE parameter.
 */


namespace BacklogTest
{
    class Program
    {
        private const int BACKLOG_SIZE = 0; //<<< Change this to 10, 20 ... 100 and see what happens!!!!
        private const int PORT = 12345;
        private const int maxClients = 100;

        private static Socket serverSocket;
        private static int clientCounter = 0;

        private static void AcceptCallback(IAsyncResult ar)
        {
            // Get the socket that handles the client request
            Socket listener = (Socket) ar.AsyncState;
            listener.EndAccept(ar);
            ++clientCounter;
            Console.WriteLine("Connected clients count: " + clientCounter.ToString() + " of " + maxClients.ToString());

            // do other some work
            for (int i = 0; i < 100000; ++i)
            {
            }

            listener.BeginAccept(AcceptCallback, listener);
        }

        private static void StartServer()
        {
            // Establish the locel endpoint for the socket
            IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, PORT);

            // Create a TCP/IP socket
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            // Bind the socket to the local endpoint and listen
            serverSocket.Bind(localEndPoint);
            serverSocket.Listen(BACKLOG_SIZE);
            serverSocket.BeginAccept(AcceptCallback, serverSocket);
        }

        static void Main(string[] args)
        {
            StartServer();

            // Clients connect to the server.
            for (int i = 0; i < 100; ++i)
            {
                IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
                IPEndPoint remoteEP = new IPEndPoint(ipAddress, PORT);

                // Create a TCP/IP socket and connect to the server
                Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                client.BeginConnect(remoteEP, null, null);
            }

            Console.ReadKey();
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接