TCP 服务器不接受客户端发出的正确数量的连接,具有少量侦听积压
TCP server not accepting right number of connections issued by client with small listen backlog
我编写了一个简单的客户端程序,在非常短的时间内使用 100 个线程发出总共 10000 个连接。一个简单的服务器程序 listen backlog
设置为 20 使用 epoll 来接受任何新连接并记录总连接数。我使用 ulimit -n
来确保它大于 20000,因此应该有足够的 fd 资源。
但是在服务器程序 accept
像 8800、9400(不固定)连接一样,它就停止接受任何新连接。客户端使用阻塞connect
建立连接,connect
这10000次调用全部返回成功。然后一切都冻结了,没有更多的数据包(没有重传),没有更多的接受连接。
但是一旦我关闭客户端程序,服务器程序在关闭一些连接后开始接受剩余的连接(并最终接受所有10000个连接并关闭所有这些连接)。
当我将 backlog
更改为 100 或更大时,所有 10000 个连接都被毫无问题地接受了。 (所以不是fd资源问题)
我知道当接受队列已满时,linux 可能只是忽略传入的 ACK
3 次握手,使客户端连接建立但服务器连接仍未建立,然后让重传机制工作.最终服务器重新传输 SYN/ACK
pakcet,客户端响应 ACK
以重新建立此连接(如果服务器的接受队列可用)。如果队列中没有可用的 space,服务器将再次忽略 ACK
。
但是当我使用wireshark监控这些重传时,我发现只有少量的SYN/ACK
重传发生(大约100~200,远少于丢失的连接数,在500~1500之间) ), 它们只被重传一两次, 都小于 /proc/sys/net/ipv4/tcp_synack_retries
中指定的值。我检查了其中一些重传的 SYN/ACK
数据包,所有这些数据包都从客户端收到了 ACK
s。但是从客户端重传的SYN
个数据包数量很大。
那么底层的细节是什么?
当积压已满且启用SYN cookie 时,内核将激活临时SYN 泛洪模式。此图显示了 cookie 的工作原理 (source) :
当SYN泛洪被激活时,所有从客户端发送的ACK都将被丢弃。服务器只发送 SYN/ACK 和 cookie 到客户端,cookie 缓存 table 将比 socket table 小得多以维持活动连接。
在这种情况下,在客户端,套接字被认为已建立,但在服务器端没有真正打开任何东西(半开连接)。
当客户端应用关闭套接字时,会向服务器发送一个设置了ACK标志的FIN包,里面有cookie,服务器看到连接有ACK,此时,如果积压不已满,服务器将尝试从 cookie 重建连接。如果 cookie 有效(在有效的往返行程内),套接字将被添加到积压队列中,并且可以作为普通套接字处理。
这意味着,当SYN cookie模式被激活并且积压队列未满时,如果客户端通过半开套接字向服务器发送一些数据,accept()函数将return new传入连接。
我编写了一个简单的客户端程序,在非常短的时间内使用 100 个线程发出总共 10000 个连接。一个简单的服务器程序 listen backlog
设置为 20 使用 epoll 来接受任何新连接并记录总连接数。我使用 ulimit -n
来确保它大于 20000,因此应该有足够的 fd 资源。
但是在服务器程序 accept
像 8800、9400(不固定)连接一样,它就停止接受任何新连接。客户端使用阻塞connect
建立连接,connect
这10000次调用全部返回成功。然后一切都冻结了,没有更多的数据包(没有重传),没有更多的接受连接。
但是一旦我关闭客户端程序,服务器程序在关闭一些连接后开始接受剩余的连接(并最终接受所有10000个连接并关闭所有这些连接)。
当我将 backlog
更改为 100 或更大时,所有 10000 个连接都被毫无问题地接受了。 (所以不是fd资源问题)
我知道当接受队列已满时,linux 可能只是忽略传入的 ACK
3 次握手,使客户端连接建立但服务器连接仍未建立,然后让重传机制工作.最终服务器重新传输 SYN/ACK
pakcet,客户端响应 ACK
以重新建立此连接(如果服务器的接受队列可用)。如果队列中没有可用的 space,服务器将再次忽略 ACK
。
但是当我使用wireshark监控这些重传时,我发现只有少量的SYN/ACK
重传发生(大约100~200,远少于丢失的连接数,在500~1500之间) ), 它们只被重传一两次, 都小于 /proc/sys/net/ipv4/tcp_synack_retries
中指定的值。我检查了其中一些重传的 SYN/ACK
数据包,所有这些数据包都从客户端收到了 ACK
s。但是从客户端重传的SYN
个数据包数量很大。
那么底层的细节是什么?
当积压已满且启用SYN cookie 时,内核将激活临时SYN 泛洪模式。此图显示了 cookie 的工作原理 (source) :
当SYN泛洪被激活时,所有从客户端发送的ACK都将被丢弃。服务器只发送 SYN/ACK 和 cookie 到客户端,cookie 缓存 table 将比 socket table 小得多以维持活动连接。 在这种情况下,在客户端,套接字被认为已建立,但在服务器端没有真正打开任何东西(半开连接)。
当客户端应用关闭套接字时,会向服务器发送一个设置了ACK标志的FIN包,里面有cookie,服务器看到连接有ACK,此时,如果积压不已满,服务器将尝试从 cookie 重建连接。如果 cookie 有效(在有效的往返行程内),套接字将被添加到积压队列中,并且可以作为普通套接字处理。
这意味着,当SYN cookie模式被激活并且积压队列未满时,如果客户端通过半开套接字向服务器发送一些数据,accept()函数将return new传入连接。