TCP连接中的“backlog”是什么?

60

下面是一个 Python 程序,它作为服务器监听端口9999的连接请求:

# server.py 
import socket                                         
import time

# create a socket object
serversocket = socket.socket(
            socket.AF_INET, socket.SOCK_STREAM) 

# get local machine name
host = socket.gethostname()                           

port = 9999                                           

# bind to the port
serversocket.bind((host, port))                                  

# queue up to 5 requests
serversocket.listen(5)                                           

while True:
    # establish a connection
    clientsocket,addr = serversocket.accept()      

    print("Got a connection from %s" % str(addr))
    currentTime = time.ctime(time.time()) + "\r\n"
    clientsocket.send(currentTime.encode('ascii'))
    clientsocket.close()
问题是 socket.listen() 方法中参数 (即 5) 的作用是什么。
根据互联网上的教程:
排队等候的参数指定最大连接数,应该至少为0;最大值取决于系统(通常为5),最小值被强制为0。
但是:
1.这些“排队等待”的连接是什么? 2.对于客户端请求有影响吗?(我的意思是运行 socket.listen(5) 的服务器是否不同于运行 socket.listen(1) 的服务器,在接受连接请求或接收数据方面有何区别?) 3.为什么最小值为零?不应该至少为1吗? 4.有没有首选值? 5.此 backlog 只适用于 TCP 连接还是也适用于 UDP 和其他协议?

1
这个答案比被接受的那个解释得更好。 - Rick
3个回答

75

注意:答案不考虑Python语言背景,但问题与语言无关,需要回答。

什么是“queued connections”?

简单来说,backlog参数指定队列将保存的挂起连接数。

当多个客户端连接到服务器时,服务器会将传入请求保存在队列中。客户端按顺序排列在队列中,服务器按照队列成员的进行处理他们的请求,一个接一个地处理。这种连接的性质称为队列式连接。

对客户端请求有任何影响吗?(我的意思是运行着 socket.listen(5) 的服务器和运行着 socket.listen(1) 的服务器在接受连接请求或接收数据方面有什么不同吗?)

是的,两种情况是不同的。第一种情况只允许将5个客户端排列到队列中;而在backlog=1的情况下,队列中只能容纳1个连接,因此导致后续连接请求被丢弃!

为什么最小值是零?难道不应该至少是1吗?

我对Python一无所知,但是,根据这个来源,在C语言中,当一个backlog参数被设置为0时,套接字可以接受连接,此时listen队列的长度可能会被设置为实现定义的最小值。

是否有首选值?

这个问题没有明确定义的答案。我认为这取决于您的应用程序的性质,以及硬件配置和软件配置。同样,根据来源,BackLog在C语言中被静默地限制在1到5之间(包括1和5)。

这个backlog只适用于TCP连接还是也适用于UDP和其他协议?

不是的。请注意,对于未连接的数据报套接字(UDP),没有必要进行listen()或accept()。这是使用未连接的数据报套接字的好处之一!
但是,请记住,还有基于TCP的数据报套接字实现(称为TCPDatagramSocket),它们具有backlog参数。

如果服务器是Windows,它将拒绝连接。在Unix、Linux等系统上,TCP协议会简单地丢弃SYN请求,这可能导致连接方重试,如果条件持续存在,则会导致连接超时,而不是被拒绝。 - user207421
1
@EJP - 我同意你所说的观点。但是,应该是客户端(连接)方的连接超时,而服务器端会丢弃请求(SYN)。请确认现在是否清楚! - Am_I_Helpful
这与我写的没有什么不同,除了你仍然忽略了Windows的情况。如果你有不同看法,请说明原因。 - user207421
1
@user207421 - 你能引用一下我帖子中你认为我忽略了Windows情况的那一行吗?我已经在之前的评论中同意了你的观点!另外,当我说连接被断开时,这是否也包括被拒绝(未被接受)的连接?我认为任何人都可以推断出来。 - Am_I_Helpful
@Am_I_Helpful 你能详细说明一下“待处理连接”是什么意思吗? - keramat
@keramat - 由于服务器无法同时处理所有连接,因此它管理一个队列来存储这些连接以便稍后处理。那些仍在队列中且尚未被服务器处理的连接请求称为“挂起”。 - Am_I_Helpful

33

当TCP连接正在建立时,会执行所谓的三次握手。双方交换一些数据包,一旦完成,则称该连接已完成,并可以由应用程序使用。

然而,这个三次握手需要一些时间。在此期间,连接会排队,这就是积压。因此,您可以通过.listen(no)调用设置最大数量的不完整并行连接(请注意,根据posix标准值仅为提示,可能被完全忽略)。如果有人尝试建立超过积压限制的连接,对面将拒绝它。

因此,积压极限是关于待处理的连接,而不是已建立的连接。

现在,在大多数情况下,更高的积压极限将更好。请注意,最大限制取决于操作系统,例如,在我的Ubuntu上,cat /proc/sys/net/core/somaxconn给我128


2
如果服务器是Windows,它将拒绝连接。在Unix、Linux等系统上,TCP协议会简单地丢弃SYN包,这可能导致连接方重试,如果条件持续存在,则会导致连接超时,而不是被拒绝。 - user207421
3
这不是未完成连接数量,它们在不同的队列中。这是应用程序尚未接受的已完成连接数量。答案完全不正确。请查看已接受答案以了解真相。 - user207421

5
该参数的作用似乎是限制服务器在高负载下处理当前请求和少量排队挂起请求所需的合理时间内保留在队列中的传入连接请求数量。以下是我遇到的一个很好的段落,它为这个参数提供了一些背景上下文...

最后,listen 的参数告诉套接字库,在拒绝外部连接之前,我们希望将多达5个连接请求(正常最大值)排队等待处理。如果其余代码编写正确,那应该足够了。

https://docs.python.org/3/howto/sockets.html#creating-a-socket

文档中早些时候的文字建议客户端应该反复与服务器进行通信,以避免在首次请求时建立长时间的请求队列...

connect完成后,套接字s可以用于发送页面文本的请求。同一个套接字将读取回复,然后被销毁。没错,被销毁了。客户端套接字通常仅用于一次交换(或一小组连续的交换)。

当您开始学习使用套接字进行网络编程时,建议阅读相关的HowTo指南。它确实聚焦了一些关于此主题的大局观。至于服务器套接字如何管理请求队列的实现细节,那是另外一个故事,可能很有趣。我想这种设计的动机更有说服力,如果没有这种设计,拒绝服务攻击的门槛将非常非常低。

关于为什么最小值为0而非1的原因,我们应该记住0仍然是一个有效值,意味着不排队任何请求。这基本上是说让没有请求队列,如果服务器套接字当前正在服务于一个连接,就直接拒绝连接。在这种情况下,始终要牢记当前正在服务的活动连接的重要性,这也是队列首先感兴趣的唯一原因。
这带我们来到关于“首选值”的下一个问题。这是一个设计决策,你想排队请求还是不想?如果是这样,你可以根据预期流量和已知硬件资源选择你认为合理的值。我想在选择一个值时没有任何公式可循。这让我想知道请求本身有多轻量级,以至于在服务器上排队任何东西都会受到惩罚。

更新

我希望证实用户207421的评论,并查找了Python源代码。不幸的是,在sockets.py源代码中找不到这种详细信息,而在哈希值530f506处的socketmodule.c#L3351-L3382中可以找到。

这些评论非常有启发性,我将在此直接复制源代码,并突出说明这里的澄清评论,这些评论非常有启发性...

我们尝试选择一个默认的backlog(等待连接队列)大小,以避免常见工作负载时的连接中断,但不会太高以限制资源使用。

如果指定了backlog,则必须至少为0(如果低于0,则设置为0);它指定系统在拒绝新连接之前允许的未接受连接数。如果未指定,则选择一个合理的默认值。

/* s.listen(n) method */

static PyObject *
sock_listen(PySocketSockObject *s, PyObject *args)
{
    /* We try to choose a default backlog high enough to avoid connection drops
     * for common workloads, yet not too high to limit resource usage. */
    int backlog = Py_MIN(SOMAXCONN, 128);
    int res;

    if (!PyArg_ParseTuple(args, "|i:listen", &backlog))
        return NULL;

    Py_BEGIN_ALLOW_THREADS
    /* To avoid problems on systems that don't allow a negative backlog
     * (which doesn't make sense anyway) we force a minimum value of 0. */
    if (backlog < 0)
        backlog = 0;
    res = listen(s->sock_fd, backlog);
    Py_END_ALLOW_THREADS
    if (res < 0)
        return s->errorhandler();
    Py_RETURN_NONE;
}

PyDoc_STRVAR(listen_doc,
"listen([backlog])\n\
\n\
Enable a server to accept connections.  If backlog is specified, it must be\n\
at least 0 (if it is lower, it is set to 0); it specifies the number of\n\
unaccepted connections that the system will allow before refusing new\n\
connections. If not specified, a default reasonable value is chosen.");

继续深入外部依赖项,我追踪了来自socketmodule的以下源代码...

 res = listen(s->sock_fd, backlog);

这个源代码位于socket.hsocket.c,以Linux作为具体平台背景进行讨论。
/* Maximum queue length specifiable by listen.  */
#define SOMAXCONN   128
extern int __sys_listen(int fd, int backlog);

在手册页中可以找到更多信息

http://man7.org/linux/man-pages/man2/listen.2.html

int listen(int sockfd, int backlog);

相应的文档字符串

listen()将由sockfd引用的套接字标记为被动套接字,即将用于使用accept(2)接受传入的连接请求的套接字。

sockfd参数是一个文件描述符,它引用了一种类型为SOCK_STREAMSOCK_SEQPACKET的套接字。

backlog参数定义了sockfd的挂起连接队列可以增长的最大长度。如果队列已满,则客户端可能会收到带有ECONNREFUSED指示的错误,或者如果底层协议支持重传,则该请求可能会被忽略,以便稍后重新尝试连接成功。

另外一个source标识内核负责挂起队列。

该函数的第二个参数backlog指定内核应为此套接字排队的最大连接数。他们简要地介绍了未接受/排队的连接如何在backlog中分区(链接源中包含有用的图表)。要理解backlog参数,我们必须意识到,对于给定的侦听套接字,内核维护两个队列:一个不完整的连接队列,其中包含每个SYN的条目,这些SYN来自服务器正在等待完成TCP三次握手的客户端。这些套接字处于SYN_RCVD状态(图2.4)。一个已完成的连接队列,其中包含每个与完成TCP三次握手的客户端的条目。这些套接字处于ESTABLISHED状态(图2.4)。这两个队列如下图所示:当在不完整队列上创建条目时,将从侦听套接字复制参数到新创建的连接。连接创建机制是完全自动的;服务器进程没有参与。

您的源代码有误。backlog队列是用于已完成连接的。连接请求会进入另一个队列,并在完成后移动到backlog队列中。 - user207421
实际上,这是一个全方位质量都很差的资源。它在多个问题上都是错误的,不仅仅是这一个。关于HTTP只使用一个套接字传输的说法是非常不正确的,同样的,声称在关闭之前应该使用shutdown()也是错的。 - user207421
哇,那确实会彻底改变事情。我想知道为什么从未发现有关该文档的问题,它已经在Python文档中存在一段时间了。这是由Gordon McMillan编写的。 - jxramos
我注意到你在使用“完整”与“不完整”连接方面的术语。从类似于这个C问题中,这些术语与三次握手相关。我想知道这是否意味着这些完整/不完整的连接是“未接受”的连接的分区。我认为这就是用户SegFault在他们的回答中所说的。 - jxramos
2
值得一提的是 tcp_abort_on_overflow,请参考 http://veithen.io/2014/01/01/how-tcp-backlog-works-in-linux.html。 - jfs
显示剩余2条评论

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