天天看点

Socket `accept queue is full ` 但是一个连接需要从SYN->ACCEPT

由于标题长度有限制,我把想要描述的问题再次描述下:

内核通常会为每一个

LISTEN

状态的

Socket

维护两个队列:

1

accept

队列:

listen()

函数第二个参数

BACKLOG

指定,表示已完成连接的队列,等待被

accept

函数取走。

2

SYN

队列:由

/proc/sys/net/ipv4/tcp_max_syn_backlog

指定,表示处于

SYN_RECV

状态的队列。

如果没有概念,参考:

http://blog.csdn.net/yangbodong22011/article/details/60399728

现在的问题是:

如果

accept

队列已经满了,并且服务器没有使用

accept

函数取走任何连接,那么当一个连接需要从

SYN队列

移动到

accept

队列时会发生什么?

正确的解释

答案来源:

http://veithen.github.io/2014/01/01/how-tcp-backlog-works-in-linux.html

下面的部分我翻译自作者原文:

This case is handled by the

tcp_check_req

function in

net/ipv4/tcp_minisocks.c

, The relevant code reads:

这种情况由

net/ipv4/tcp_minisocks.c

中的

tcp_check_req

函数处理,相关代码如下:

child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL);
     if (child == NULL)
         goto listen_overflow;
           
For IPv4, the first line of code will actually call tcp_v4_syn_recv_sock in net/ipv4/tcp_ipv4.c, which contains the following code:

对于IPv4,第一行代码实际上将调用

net/ipv4/tcp_ipv4.c

中的

tcp_v4_syn_recv_sock

,其中包含以下代码:

if (sk_acceptq_is_full(sk))
                goto exit_overflow;
           
We see here the check for the accept queue. The code after the

exit_overflow

label will perform some cleanup, update the

ListenOverflows

and

ListenDrops

statistics in

/proc/net/netstat

and then return

NULL

. This will trigger the execution of the

listen_overflow

code in

tcp_check_req

:

我们在这里看到了对于

accept

队列的检查。

exit_overflow

的代码将执行一些清理,更新

/proc/net/netstat

中的

ListenOverflows

ListenDrops

统计信息,然后返回

NULL

。 这将触发在

tcp_check_req

中执行

listen_overflow

代码:

listen_overflow:
        if (!sysctl_tcp_abort_on_overflow) {
                inet_rsk(req)->acked = ;
                return NULL;
        }
           
This means that unless /proc/sys/net/ipv4/tcp_abort_on_overflow is set to 1 , the implementation basically does… nothing!

这意味着只要

/proc/sys/net/ipv4/tcp_abort_on_overflow

被设置为

1

,在上面的

if (!sysctl_tcp_abort_on_overflow)

判断中为0,就不会执行下面代码,也就是Linux的策略是 : 什么都不干!!!!

备注:

这里的什么都不干对应的是:

回应RST包

,客户端出现

connection reset by peer

To summarize, if the TCP implementation in Linux receives the ACK packet of the 3-way handshake and the accept queue is full, it will basically ignore that packet. At first, this sounds strange, but remember that there is a timer associated with the SYN RECEIVED state: if the ACK packet is not received (or if it is ignored, as in the case considered here), then the TCP implementation will resend the SYN/ACK packet (with a certain number of retries specified by /proc/sys/net/ipv4/tcp_synack_retries and using an exponential backoff algorithm).

总而言之,如果

Linux

中的

TCP

接收到3次握手的

ACK数据包

,并且接受队列已满,它将基本上忽略该数据包。 这听起来很奇怪,但是记住有一个与SYN RECEIVED状态相关联的定时器:如果没有收到

ACK

分组(在这里我们考虑的情况是ACK被忽略,就和没有收到是一样的),则

TCP

将重新发送

SYN/ACK

数据包(由

/proc/sys/net/ipv4/tcp_synack_retries

指定次数)。

备注:我的电脑是5

Socket `accept queue is full ` 但是一个连接需要从SYN->ACCEPT
Since the TCP implementation on the client side gets multiple SYN/ACK packets, it will assume that the ACK packet was lost and resend it (see the lines with TCP Dup ACK in the above trace). If the application on the server side reduces the backlog (i.e. consumes an entry from the accept queue) before the maximum number of SYN/ACK retries has been reached, then the TCP implementation will eventually process one of the duplicate ACKs, transition the state of the connection from SYN RECEIVED to ESTABLISHED and add it to the accept queue. Otherwise, the client will eventually get a RST packet (as in the sample shown above).

由于客户端收到多个

SYN/ACK

分组,它将假定

ACK

分组丢失并重新发送它。 如果在达到最大

SYN/ACK

重试次数之前,服务器侧的应用程序减少了

Accept队列大小

(即

调用accept

来取接受队列的条目),则

TCP

将最终处理

重复ACK

中的一个,转变状态从

SYN RECEIVED

ESTABLISHED

的连接,并将其添加到

accept

队列。 否则,客户端将最终收到

RST

分组。

实验验证

验证环境:

RedHat 7

Linux version 3.10.0-514.el7.x86_64

验证思路:

  • 模拟一个”忙”的服务器,保证它的

    accept队列被占满

  • 再用客户端连接。

使用

wireshark

抓包情况如下所示(在新的标签页打开图片):

Socket `accept queue is full ` 但是一个连接需要从SYN->ACCEPT
  • 前三个包是三次握手,143是客户端,155是服务器。
  • 但是155服务器此时

    accept队列

    是满的,虽然143和它完成了三次握手,但是它仍然不能从

    SYN RECEIVED

    状态转换到

    ESTABLISHED

    状态。
  • 此时由于

    SYN RECEIVED

    状态的定时器机制,服务器155没有收到

    ACK

    分组的时候(实际上是直接扔掉了),会继续给客户端143发送

    SYN/ACK

    数据包,143以为服务器没有收到

    ACK

    ,然后回复ACK,这也就是”黑色”的包为什么有10个的原因了,服务器发送的

    SYN/ACK

    有5个,客户端回复的

    ACK

    有5个,就是

    /proc/sys/net/ipv4/tcp_synack_retries

    定义的。

我的疑问:

按道理,客户端最后是会收到服务器端发送的

RST

数据包的。但是我不知道多长时间之后会发,于是我等了很久(2小时)。此时服务器端通过

netstat

命令查看已经没有任何客户端的连接信息了,但是客户端查看和服务器的连接却是

ESTABLISHED

状态。很奇怪,这样的话客户端什么时候退出呢? 难道它一直卡在那里?

ps : 重新测了以下,服务器丢掉

SYN RECEIVED

状态的客户端时间大概是1分钟,也就是客户端连接过来,服务器

Accept队列满

的话,大概一分钟之后服务器就会丢失这个连接。但是此时客户端维持着一条

ESTABLISHED

状态的连接~~,它到底什么时候释放呢?

继续阅读