为什么netstat命令中的Recv-Q值等于套接字backlog + 1?

3
当我执行netstat -tulnp命令时,输出如下:
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.11:43043        0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:8005          0.0.0.0:*               LISTEN      1/java
tcp        0      0 0.0.0.0:2021            0.0.0.0:*               LISTEN      1/java
tcp        0      0 0.0.0.0:22222           0.0.0.0:*               LISTEN      1/java
tcp        0      0 0.0.0.0:8719            0.0.0.0:*               LISTEN      1/java
tcp      101      0 0.0.0.0:80              0.0.0.0:*               LISTEN      1/java
tcp       51      0 0.0.0.0:1234            0.0.0.0:*               LISTEN      1/java
tcp        0      0 0.0.0.0:20891           0.0.0.0:*               LISTEN      1/java
udp        0      0 127.0.0.11:55285        0.0.0.0:*                           -

Recv-Q的值引起了我的注意。在调查后,我发现JVM应用程序中发生了OOM,并且可以在日志中找到负责监视端口80的http-nio-80-Acceptor-0线程已退出,负责分派端口1234请求的线程也已退出。相关的日志如下:

Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "http-nio-80-Acceptor-0"
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "Thread-5"

tomcat的默认配置是backlog为100,源代码位于tomcat/AbstractEndpoint.java at 8.5.59 · apache/tomcat · GitHub

/**
  * Allows the server developer to specify the acceptCount (backlog) that
  * should be used for server sockets. By default, this value
  * is 100.
  */
private int acceptCount = 100;
public void setAcceptCount(int acceptCount) { if (acceptCount > 0) this.acceptCount = acceptCount; }
public int getAcceptCount() { return acceptCount; }

端口1234的监听由HTTPServer触发并创建,创建的代码为HttpServer.create(new InetSocketAddress(PROMETHEUS_SERVER_PORT), 0);,backlog在ServerSocket.java中被更正为50,源代码位于jdk/ServerSocket.java at jdk8-b120 · openjdk/jdk · GitHub
public void bind(SocketAddress endpoint, int backlog) throws IOException {
    if (isClosed())
        throw new SocketException("Socket is closed");
    if (!oldImpl && isBound())
        throw new SocketException("Already bound");
    if (endpoint == null)
        endpoint = new InetSocketAddress(0);
    if (!(endpoint instanceof InetSocketAddress))
        throw new IllegalArgumentException("Unsupported address type");
    InetSocketAddress epoint = (InetSocketAddress) endpoint;
    if (epoint.isUnresolved())
        throw new SocketException("Unresolved address");
    if (backlog < 1)
      backlog = 50;
    try {
        SecurityManager security = System.getSecurityManager();
        if (security != null)
            security.checkListen(epoint.getPort());
        getImpl().bind(epoint.getAddress(), epoint.getPort());
        getImpl().listen(backlog);
        bound = true;
    } catch(SecurityException e) {
        bound = false;
        throw e;
    } catch(IOException e) {
        bound = false;
        throw e;
    }
}

根据netstat(8) - Linux manual page,我们知道在套接字处于Listening状态时,Recv-Q表示当前的syn backlog。令人困惑的是,为什么Recv-Q的值比我们设置的backlog值多1

所以你的内存用完了,你的接受线程停止了接受,所以积压队列增长到了最大值。这里需要解决的问题是OOM,而不是积压队列。 - user207421
@user207421 我的问题是,为什么 Recv-Q 的值比预设的 backlog 大一而不是等于 backlog。为什么 Recv-Q 的值是 101 和 51 而不是 100 和 50? - Poison
我能够在一个C程序中复现这种行为,并使用ss代替telnet。正在进一步研究中... - Liam Kelly
2个回答

0

TL;DR netstat/ss报告完整和不完整的连接,而backlog仅关注已完成的连接

我们知道,当套接字处于监听状态时,Recv-Q指示当前的syn排队等待的数量

是的,这在sock_diagman页面中得到了确认。看起来netlink从下面的内核结构中获取了这个值:

rql.udiag_rqueue = sk->sk_receive_queue.qlen;

进一步研究qlen,发现它与backlog不同。qlen包括完整和不完整的连接,而backlog仅关注已完成的连接。 listen手册对此进行了说明:

TCP套接字上backlog参数的行为在Linux 2.2中发生了变化。现在,它指定等待被接受的完全建立的套接字的队列长度,而不是不完整连接请求的数量。

至于为什么接收队列关心不完整的连接,我只能推测它试图处理当TCP握手仍在进行时,backlog释放的边缘情况。


0

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