[转帖]TCP的blacklog之全连接队列与半连接队列的深入研究

tcp,blacklog,连接,队列,深入,研究 · 浏览次数 : 0

小编点评

# Application Setting Backlog Value The passage indicates that the application setting for backlog value depends on various factors and should be adjusted according to the specific business scenario. **Here's a breakdown:** * **Nginx and Redis:** Default backlog values are set to 511. * **Linux:** Default backlog is 128. * **Java:** Default backlog is 50. **Appropriate Backlog Values:** * For high-performance connections, increasing the backlog value may be beneficial. * However, increasing the backlog value can increase connection failures due to increased backlog size. **Recommended Backlog Value:** The passage doesn't provide a specific recommended backlog value. However, based on the default values, **511** is a commonly used and suitable value for backlog size. **Factors to Consider:** * **Client-side performance:** If the application needs to handle many connections quickly, a larger backlog may be necessary. * **Server-side performance:** If the server is resource-constrained, increasing the backlog may not be helpful. * **Business requirements:** The application should be configured to handle the desired number of connections efficiently. **Additional Notes:** * The passage mentions that increasing the backlog value can also impact client receiving RST responses due to the possibility of RST being sent back with an error.

正文


说明:

  1. 以下所有实验均在Centos7上操作,内核版本为Linux version 3.10.0-957.el7.x86_64
  2. 你需要对三次握手过程要很熟悉

Linux内核探测工具systemtap的安装与使用

  1. yum install systemtap,查看版本结果如下

    [root@localhost test_tcp]# stap -V
    Systemtap translator/driver (version 4.0/0.172/0.176, rpm 4.0-13.el7)
    Copyright (C) 2005-2018 Red Hat, Inc. and others
    This is free software; see the source for copying conditions.
    tested kernel versions: 2.6.18 ... 4.19-rc7
    enabled features: AVAHI BOOST_STRING_REF DYNINST BPF JAVA PYTHON2 LIBRPM LIBSQLITE3 LIBVIRT LIBXML2 NLS NSS READLINE
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  2. 检查Linux version 3.10.0-957.el7.x86_64的debug包是否正确,命令行执行rpm -ivh kernel*,运行结果中有以下三个包即可,如果缺失,可以去这个地方下载,如果下载慢请配置代理或者迅雷

    [root@localhost test_tcp]# rpm -ihv kernel*
    准备中...                          ################################# [100%]
    	软件包 kernel-debuginfo-common-x86_64-3.10.0-957.el7.x86_64 已经安装
    	软件包 kernel-debuginfo-3.10.0-957.el7.x86_64 已经安装
    	软件包 kernel-devel-3.10.0-957.el7.x86_64 已经安装
    [root@localhost test_tcp]# 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  3. 在命令行执行以下systemtap代码,看是否能正常执行

    stap -ve 'probe begin { print("hello world\n") exit()}'
    
    • 1

    运行结果如下

    [root@localhost test_tcp]# stap -ve 'probe begin { print("hello world\n") exit()}'
    Pass 1: parsed user script and 477 library scripts using 278812virt/76104res/3500shr/72848data kb, in 570usr/60sys/640real ms.
    Pass 2: analyzed script: 1 probe, 1 function, 0 embeds, 0 globals using 280660virt/78212res/3736shr/74696data kb, in 10usr/0sys/10real ms.
    Pass 3: using cached /root/.systemtap/cache/c0/stap_c08cb247b178e08599d8b2006757109c_981.c
    Pass 4: using cached /root/.systemtap/cache/c0/stap_c08cb247b178e08599d8b2006757109c_981.ko
    Pass 5: starting run.
    hello world
    Pass 5: run completed in 0usr/30sys/340real ms.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

backlog、半连接队列、全连接队列是什么

半连接队列、全连接队列基本概念
  1. 为了理解 backlog,我们需要了解 listen 和 accept 函数背后的发生了什么。backlog 参数跟 listen 函数有关,listen 函数的定义如下:

    int listen(int sockfd, int backlog);
    
    • 1
  2. 当服务端调用 listen 函数时,TCP 的状态被从 CLOSE 状态变为 LISTEN,于此同时内核创建了两个队列:

    a.半连接队列(Incomplete connection queue),又称 SYN 队列,当客户端发起 SYN 到服务端,服务端收到以后会回 ACK 和自己的 SYN。这时服务端这边的 TCP 从 listen 状态变为 SYN_RCVD (SYN Received),此时会将这个连接信息放入「半连接队列」,半连接队列也被称为 SYN Queue,存储的是 “inbound SYN packets”。

    b.全连接队列(Completed connection queue),又称 Accept 队列, 服务端回复 SYN+ACK 包以后等待客户端回复 ACK,同时开启一个定时器,如果超时还未收到 ACK 会进行 SYN+ACK 的重传,重传的次数由 tcp_synack_retries 值确定。在 CentOS 上这个值等于 5。一旦收到客户端的 ACK,服务端就开始尝试把它加入另外一个全连接队列(Accept Queue)。
    服务端视角看这两个队列如下图在这里插入图片描述

linux 内核是如何计算半连接队列、全连接队列的

半连接队列的大小的计算

这里使用 SystemTap 工具插入系统探针,在收到 SYN 包以后打印当前的 SYN 队列的大小和半连接队列的总大小。

TCP listen 状态的 socket 收到 SYN 包的处理流程如下

tcp_v4_rcv
  ->tcp_v4_do_rcv
    -> tcp_v4_conn_request
  • 1
  • 2
  • 3

这里注入 tcp_v4_conn_request 方法,代码syn_backlog.c如下所示,启动stap -g syn_backlog.c

probe kernel.function("tcp_v4_conn_request") {
    tcphdr = __get_skb_tcphdr($skb);
    dport = __tcp_skb_dport(tcphdr);
    if (dport == 9090)
    {
        printf("reach here\n");
        // 当前 syn 排队队列的大小
        syn_qlen = @cast($sk, "struct inet_connection_sock")->icsk_accept_queue->listen_opt->qlen;
        // syn 队列总长度 log 值
        max_syn_qlen_log = @cast($sk, "struct inet_connection_sock")->icsk_accept_queue->listen_opt->max_qlen_log;
        // syn 队列总长度,2^n
        max_syn_qlen = (1 << max_syn_qlen_log);
        printf("syn queue: syn_qlen=%d, max_syn_qlen_log=%d, max_syn_qlen=%d\n",
         syn_qlen, max_syn_qlen_log, max_syn_qlen);
        // max_acc_qlen = $sk->sk_max_ack_backlog;
        // printf("accept queue length limit: %d\n", max_acc_qlen)
        print_backtrace();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

模拟服务端的代码如下

#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
int main(void) {
  int listenfd;
  socklen_t clilen;
  struct sockaddr_in cliaddr, servaddr;
  listenfd = socket(AF_INET, SOCK_STREAM, 0);
  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl (INADDR_ANY);
  servaddr.sin_port = htons (9090);
  bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
  listen(listenfd, 10);
  clilen = sizeof(cliaddr);
  accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
  sleep(-1);
  return 1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

启动服务端
用nc去连接
运行结果如下,可以看到我们listen的大小为10,但是最后打印出来的半连接队列大小为16,所以我们不能简单的认为listen指定的backlog大小就是队列的大小
在这里插入图片描述
接下来我们来看代码中是如何计算的,半连接队列的大小与三个值有关:

  • 用户层 listen 传入的backlog
  • 系统变量 net.ipv4.tcp_max_syn_backlog,默认值为 128
  • 系统变量 net.core.somaxconn,默认值为 128

具体的计算见下面的源码,调用 listen 函数首先会进入如下的代码。

SYSCALL_DEFINE2(listen, int, fd, int, backlog)
{
    // sysctl_somaxconn 是系统变量 net.core.somaxconn 的值
	int somaxconn = sysctl_somaxconn;
	if ((unsigned int)backlog > somaxconn)
		backlog = somaxconn;
	sock->ops->listen(sock, backlog);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

通过 SYSCALL_DEFINE2 代码可以得知,如果用户传入的 backlog 值大于系统变量 net.core.somaxconn 的值,用户设置的 backlog 不会生效,使用系统变量值,默认为 128。

接下来这个 backlog 值会被依次传递给 inet_listen()->inet_csk_listen_start()->reqsk_queue_alloc() 方法。在 reqsk_queue_alloc 方法中进行了最终的计算。精简后的代码如下

int reqsk_queue_alloc(struct request_sock_queue *queue,
		      unsigned int nr_table_entries)
{
    nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
    nr_table_entries = max_t(u32, nr_table_entries, 8);
    nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);
<span class="token keyword">for</span> <span class="token punctuation">(</span>lopt-<span class="token operator">&gt;</span>max_qlen_log <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span>
     <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">&lt;&lt;</span> lopt-<span class="token operator">&gt;</span>max_qlen_log<span class="token punctuation">)</span> <span class="token operator">&lt;</span> nr_table_entries<span class="token punctuation">;</span>
     lopt-<span class="token operator">&gt;</span>max_qlen_log++<span class="token punctuation">)</span><span class="token punctuation">;</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

代码中 nr_table_entries 为前面计算的 backlog 值,sysctl_max_syn_backlog 为 net.ipv4.tcp_max_syn_backlog 的值。 计算逻辑如下:

  • 在 nr_table_entries 与 sysctl_max_syn_backlog 两者中的较小值,赋值给 nr_table_entries
  • 在 nr_table_entries 和 8 取较大值,赋值给 nr_table_entries
  • nr_table_entries + 1 向上取求最接近的最大 2 的指数次幂
  • 通过 for 循环找不大于 nr_table_entries 最接近的 2 的对数值

下面来举几个实际的例子,以 listen(50) 为例,经过 SYSCALL_DEFINE2 中计算 backlog 的值为 min(50, somaxconn),等于 50,接下来进入 reqsk_queue_alloc 函数的计算。

// min(50, 128) = 50
nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
// max(50, 8) = 50
nr_table_entries = max_t(u32, nr_table_entries, 8);
// roundup_pow_of_two(51) = 64
nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);

max_qlen_log 最小值为 2^3 = 8
for (lopt->max_qlen_log = 3;
(1 << lopt->max_qlen_log) < nr_table_entries;
lopt->max_qlen_log++);
经过 for 循环 max_qlen_log = 2^6 = 64

    下面给了几个 somaxconn、max_syn_backlog、backlog 三者之间不同组合的最终半连接队列大小值。

    somaxconnmax_syn_backloglisten backlog半连接队列大小
    128128516
    1281281016
    1281285064
    128128128256
    1281281000256
    1281285000256
    1024128128256
    10241024128256
    40964096128256
    4096409640968192

    可以看到:

    • 在系统参数不修改的情形,盲目调大 listen 的 backlog 对最终半连接队列的大小不会有影响。
    • 在 listen 的 backlog 不变的情况下,盲目调大 somaxconn 和 max_syn_backlog 对最终半连接队列的大小不会有影响
    模拟半连接队列占满

    以 somaxconn=128、tcp_max_syn_backlog=128、listen backlog=50 为例,模拟的原理是在三次握手的第二步,客户端在收到服务端回复的 SYN+ACK 以后使用 iptables 丢弃这个包。这里实验的服务端是 192.168.170.129,客户端是 192.168.170.133。

    1. 在客户端使用 iptables 增加一条规则,如下所示。

      iptables --append INPUT  --match tcp --protocol tcp --src 192.168.170.129 --sport 9090 --tcp-flags SYN SYN --jump DROP
      

        这条规则的含义是丢弃来自 ip 为 192.168.170.129,源端口号为 9090 的 SYN 包,如下图所示。在这里插入图片描述

      • 启动之前的服务端

      • 客户端代码如下,并启动客户端

        package main
        

        import (
        "net"
        "fmt"
        "time"
        )
        func main() {
        for i := 0; i < 2000; i++ {
        go connect()
        }
        time.Sleep(time.Minute * 10)
        }
        func connect() {
        _, err := net.Dial("tcp4", "192.168.170.129:9090")
        if err != nil {
        fmt.Println(err)
        }
        }

        • 可以看到结果如下,半连接队列已经被占满了,其中在服务端使用 netstat 查看当前 9090 端口的连接状态,如下所示。

        netstat -lnpa | grep :9090  | awk '{print $6}' | sort | uniq -c | sort -rn
             64 SYN_RECV
              1 LISTEN
        

          可以观察到 SYN_RECV 状态的连接个数的从 0 开始涨到 64,就不再上涨了,这里的 64 就是半连接队列的大小。

          接下来我们来看全连接队列
          在这里插入图片描述

          全连接队列(Accept Queue)

          「全连接队列」包含了服务端所有完成了三次握手,但是还未被应用调用 accept 取走的连接队列。此时的 socket 处于 ESTABLISHED 状态。每次应用调用 accept() 函数会移除队列头的连接。如果队列为空,accept() 通常会阻塞。全连接队列也被称为 Accept 队列。

          你可以把这个过程想象生产者、消费者模型。内核是一个负责三次握手的生产者,握手完的连接会放入一个队列。我们的应用程序是一个消费者,取走队列中的连接进行下一步的处理。这种生产者消费者的模式,在生产过快、消费过慢的情况下就会出现队列积压。

          listen 函数的第二个参数 backlog 用来设置全连接队列大小,但不一定就会选用这一个 backlog 值,还受限于 somaxconn,等下会有更详细的内容说明全连接队列大小的计算规则。

          int listen(int sockfd, int backlog)
          

            如果全连接队列满,内核会舍弃掉 client 发过来的 ack(应用层会认为此时连接还未完全建立)

            我们来模拟一下全连接队列满的情况。因为只有 accept 才会移除全连接的队列,所以如果我们只 listen,不调用 accept,那么很快全连接就可以被占满。

            1. 清楚刚刚测试半连接队列添加的防火墙规则在客户端

              iptables -F
              
              • 1
            2. 这里用 c 语言来实现,新建一个 test_accept_queue_full.c 文件,编译启动服务端

              #include <stdio.h>
              #include <sys/socket.h>
              #include <stdlib.h>
              #include <string.h>
              #include <unistd.h>
              #include <errno.h>
              #include <arpa/inet.h>
              

              int main() {
              struct sockaddr_in serv_addr;
              int listen_fd = 0;
              if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
              exit(1);
              }
              bzero(&serv_addr, sizeof(serv_addr));

              serv_addr<span class="token punctuation">.</span>sin_family <span class="token operator">=</span> AF_INET<span class="token punctuation">;</span>
              serv_addr<span class="token punctuation">.</span>sin_addr<span class="token punctuation">.</span>s_addr <span class="token operator">=</span> <span class="token function">htonl</span><span class="token punctuation">(</span>INADDR_ANY<span class="token punctuation">)</span><span class="token punctuation">;</span>
              serv_addr<span class="token punctuation">.</span>sin_port <span class="token operator">=</span> <span class="token function">htons</span><span class="token punctuation">(</span><span class="token number">9090</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
              
              <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">bind</span><span class="token punctuation">(</span>listen_fd<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token keyword">struct</span> <span class="token class-name">sockaddr</span> <span class="token operator">*</span><span class="token punctuation">)</span> <span class="token operator">&amp;</span>serv_addr<span class="token punctuation">,</span> <span class="token keyword">sizeof</span><span class="token punctuation">(</span>serv_addr<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
                  <span class="token function">exit</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
              <span class="token punctuation">}</span>
              
              <span class="token comment">// 设置 backlog 为 50</span>
              <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">listen</span><span class="token punctuation">(</span>listen_fd<span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
                  <span class="token function">exit</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
              <span class="token punctuation">}</span>
              <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">100000000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
              <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
              

              }

              • 使用之前的的 go 程序发起 connect,在服务端用 netstat 查看 tcp 连接状态

                netstat -lnpa | grep :9090  | awk '{print $6}' | sort | uniq -c | sort -rn
                
                • 1

                在这里插入图片描述
                虽然并发发了很多请求,实际只有 51 个请求处于 ESTABLISHED 状态,还有大量请求处于 SYN_RECV 状态。

              另外注意到 backlog 等于 50,但是实际上处于 ESTABLISHED 状态的连接却有 51 个,后面会讲到。

              4.使用 systemstap 可以实时观察当前的全连接队列情况,探针代码如下所示。在服务端运行以下探针
              stap -g accept_queue_test.c

              probe kernel.function("tcp_v4_conn_request") {
                  tcphdr = __get_skb_tcphdr($skb);
                  dport = __tcp_skb_dport(tcphdr);
                  if (dport == 9090)
                  {
                      printf("reach here\n");
                      // 当前 syn 排队队列的大小
                      syn_qlen = @cast($sk, "struct inet_connection_sock")->icsk_accept_queue->listen_opt->qlen;
                      // syn 队列总长度 log 值
                      max_syn_qlen_log = @cast($sk, "struct inet_connection_sock")->icsk_accept_queue->listen_opt->max_qlen_log;
                      // syn 队列总长度,2^n
                      max_syn_qlen = (1 << max_syn_qlen_log);
                      printf("syn queue: syn_qlen=%d, max_syn_qlen_log=%d, max_syn_qlen=%d\n",
                       syn_qlen, max_syn_qlen_log, max_syn_qlen);
                      ack_backlog = $sk->sk_ack_backlog;
                      max_ack_backlog = $sk->sk_max_ack_backlog;
                      printf("accept queue length, max: %d, current: %d\n", max_ack_backlog, ack_backlog)
                  }
              }
              

                实验结果如下
                在这里插入图片描述
                这里也可以看出全连接队列的大小变化的情况,印证了我们前面的说法。

                对某个tcp连接进行抓包分析如下图
                ![在这里插入图片描述](https://img-blog.csdnimg.cn/63b00b30356e4905a18f1136e01e0e07.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbGl5dWFuY2hhb19ibG9n,size_20,color_FFFFFF,t_70,g_se,x_16

                以下记客户端 192.168.170.133 为 A,服务端 192.168.170.129 为 B

                1. 客户端 A 发起 SYN 到服务端 B 的 9090 端口,开始三次握手的第一步
                2. 服务器 B 马上回复了 ACK + SYN,此时 服务器 B socket处于 SYN_RCVD 状态
                3. 客户端 A 收到服务器 B 的 ACK + SYN,发送三次握手最后一步的 ACK 给服务器 B,自己此时处于 ESTABLISHED 状态,与此同时,由于服务器 B 的全连接队列满,它会丢掉这个 ACK,连接还未建立
                4. 服务端 B 因为认为没有收到 ACK,以为是自己在 2 中的 SYN + ACK 在传输过程中丢掉了,所以开始重传,期待客户端能重新回复 ACK。
                5. 客户端 A 收到 B 的 SYN + ACK 以后,确实马上回复了 ACK
                  6 ~ 13但是这个 ACK 同样也会被服务器 B 丢弃,服务端 B 还是认为没有收到 ACK,继续重传重传的过程同样也是指数级退避的(1s、2s、4s、8s、16s),总共历时 31s 重传 5 次 SYN + ACK 以后,服务器 B 认为没有希望,一段时间后此条 tcp 连接就被系统回收了。
                  SYN+ACK重传的次数是由操作系统的一个文件决定的/proc/sys/net/ipv4/tcp_synack_retries,可以用 cat 查看这个文件

                SYN+ACK重传次数

                1. SYN+ACK重传的次数是由操作系统的一个文件决定的/proc/sys/net/ipv4/tcp_synack_retries,可以用 cat 查看这个文件
                2. 整个过程如下图所示:
                  在这里插入图片描述
                全连接队列的大小

                全连接队列的大小是 listen 传入的 backlog 和 somaxconn 中的较小值。

                全连接队列大小判断是否满的函数是 /include/net/sock.h 中 的 sk_acceptq_is_full 方法。

                static inline bool sk_acceptq_is_full(const struct sock *sk)
                {
                	return sk->sk_ack_backlog > sk->sk_max_ack_backlog;
                }
                
                • 1
                • 2
                • 3
                • 4

                这里本身没有什么毛病,只是 sk_ack_backlog 是从 0 开始计算的,所以真正全连接队列大小是 backlog + 1。当你指定 backlog 值为 1 时,能容纳的连接个数会是 2。《Unix 网络编程卷一》87 页 4.5 节有详细的对比各个操作系统 backlog 与实际全连接队列最大数量之间的关系。

                ss 命令

                ss 命令可以查看全连接队列的大小和当前等待 accept 的连接个数,执行 ss -lnt 即可,比如上面的 accept 队列满的例子中,执行 ss 命令的输出结果如下。

                ss -lnt | grep :9090
                State      Recv-Q Send-Q Local Address:Port               Peer Address:Port
                LISTEN     51     50           *:9090                     *:*
                
                • 1
                • 2
                • 3

                对于 LISTEN 状态的套接字,Recv-Q 表示 accept 队列排队的连接个数,Send-Q 表示全连接队列(也就是 accept 队列)的总大小。

                我们来看看 ss 命令的底层实现。ss 命令的源码在 iproute2 项目里,它巧妙的利用了 netlink 与 TCP 协议栈中 tcp_diag 模块通信获取 socket 的详细信息。tcp_diag 是一个统计分析模块,可以获取内核中很多有用的信息,ss 输出中的 Recv-Q 和 Send-Q 就是从 tcp_diag 模块中获取的,这两个值是等于 inet_diag_msg 结构体的 idiag_rqueue 和 idiag_wqueue。tcp_diag 部分的源码如下所示。

                static void tcp_diag_get_info(struct sock *sk, struct inet_diag_msg *r,
                			      void *_info)
                {
                	struct tcp_info *info = _info;
                
                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">inet_sk_state_load</span><span class="token punctuation">(</span>sk<span class="token punctuation">)</span> <span class="token operator">==</span> TCP_LISTEN<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
                   <span class="token comment">// 对应 Recv-Q</span>
                	r<span class="token operator">-&gt;</span>idiag_rqueue <span class="token operator">=</span> <span class="token function">READ_ONCE</span><span class="token punctuation">(</span>sk<span class="token operator">-&gt;</span>sk_ack_backlog<span class="token punctuation">)</span><span class="token punctuation">;</span> 
                	<span class="token comment">// 对应 Send-Q</span>
                	r<span class="token operator">-&gt;</span>idiag_wqueue <span class="token operator">=</span> <span class="token function">READ_ONCE</span><span class="token punctuation">(</span>sk<span class="token operator">-&gt;</span>sk_max_ack_backlog<span class="token punctuation">)</span><span class="token punctuation">;</span>	<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>sk<span class="token operator">-&gt;</span>sk_type <span class="token operator">==</span> SOCK_STREAM<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
                	<span class="token keyword">const</span> <span class="token keyword">struct</span> <span class="token class-name">tcp_sock</span> <span class="token operator">*</span>tp <span class="token operator">=</span> <span class="token function">tcp_sk</span><span class="token punctuation">(</span>sk<span class="token punctuation">)</span><span class="token punctuation">;</span>
                	r<span class="token operator">-&gt;</span>idiag_rqueue <span class="token operator">=</span> <span class="token class-name">max_t</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">,</span> <span class="token function">READ_ONCE</span><span class="token punctuation">(</span>tp<span class="token operator">-&gt;</span>rcv_nxt<span class="token punctuation">)</span> <span class="token operator">-</span>
                				     <span class="token function">READ_ONCE</span><span class="token punctuation">(</span>tp<span class="token operator">-&gt;</span>copied_seq<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                	r<span class="token operator">-&gt;</span>idiag_wqueue <span class="token operator">=</span> <span class="token function">READ_ONCE</span><span class="token punctuation">(</span>tp<span class="token operator">-&gt;</span>write_seq<span class="token punctuation">)</span> <span class="token operator">-</span> tp<span class="token operator">-&gt;</span>snd_una<span class="token punctuation">;</span>
                <span class="token punctuation">}</span>
                

                }

                  从上面的源码可以得知:

                  • 处于 LISTEN 状态的 socket,Recv-Q 对应 sk_ack_backlog,表示当前 socket 的完成三次握手等待用户进程 accept 的连接个数,Send-Q 对应 sk_max_ack_backlog,表示当前 socket 全连接队列能最大容纳的连接数
                  • 对于非 LISTEN 状态的 socket,Recv-Q 表示 receive queue 的字节大小,Send-Q 表示 send queue 的字节大小

                  其它

                  多大的 backlog 是合适的

                  前面讲了这么多,应用程序设置多大的 backlog 是合理的呢?

                  答案是 It depends,根据不同过的业务场景,需要做对应的调整。

                  • 你如果的接口处理连接的速度要求非常高,或者在做压力测试,很有必要调高这个值
                  • 如果业务接口本身性能不好,accept 取走已建连的速度较慢,那么把 backlog 调的再大也没有用,只会增加连接失败的可能性

                  可以举个典型的 backlog 值供大家参考,Nginx 和 Redis 默认的 backlog 值等于 511,Linux 默认的 backlog 为 128,Java 默认的 backlog 等于 50

                  tcp_abort_on_overflow 参数

                  默认情况下,全连接队列满以后,服务端会忽略客户端的 ACK,随后会重传SYN+ACK,也可以修改这种行为,这个值由/proc/sys/net/ipv4/tcp_abort_on_overflow决定。

                  • tcp_abort_on_overflow 为 0 表示三次握手最后一步全连接队列满以后 server 会丢掉 client 发过来的 ACK,服务端随后会进行重传 SYN+ACK。
                  • tcp_abort_on_overflow 为 1 表示全连接队列满以后服务端直接发送 RST 给客户端。
                    但是回给客户端 RST 包会带来另外一个问题,客户端不知道服务端响应的 RST 包到底是因为「该端口没有进程监听」,还是「该端口有进程监听,只是它的队列满了」。

                  总结

                  这篇文章我们从 backlog 参数为入口来研究了半连接队列、全连接队列的关系。简单回顾一下。

                  • 半连接队列:服务端收到客户端的 SYN 包,回复 SYN+ACK 但是还没有收到客户端 ACK 情况下,会将连接信息放入半连接队列。半连接队列又被称为 SYN 队列。
                  • 全连接队列:服务端完成了三次握手,但是还未被 accept 取走的连接队列。全连接队列又被称为 Accept 队列。
                  • 半连接队列的大小与用户 listen 传入的 backlog、net.core.somaxconn、net.core.somaxconn 都有关系,准确的计算规则见上面的源码分析
                  • 全连接队列的大小是用户 listen 传入的 backlog 与 net.core.somaxconn 的较小值

                  参考文章

                  与[转帖]TCP的blacklog之全连接队列与半连接队列的深入研究相似的内容:

                  [转帖]TCP的blacklog之全连接队列与半连接队列的深入研究

                  文章目录 Linux内核探测工具systemtap的安装与使用backlog、半连接队列、全连接队列是什么半连接队列、全连接队列基本概念 linux 内核是如何计算半连接队列、全连接队列的半连接队列的大小的计算模拟半连接队列占满全连接队列(Accept Queue) SYN+ACK重传次数全连接队列

                  [转帖]TCP的全连接与半连接队列

                  TCP的全连接与半连接队列 总结 TCP 全连接队列的最大值: 取决于 somaxconn 和 backlog 之间的最小值, 也就是 min(somaxconn, backlog) TCP 半连接队列的最大值: min(min(somaxconn,backlog),tcp_max_syn_back

                  [转帖]神秘的backlog参数与TCP连接队列

                  https://www.cnblogs.com/codelogs/p/16060820.html 原创:打码日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处。 简介# 这要从一次压测项目说起,那是我们公司的系统与另几家同行公司的系统做性能比拼,性能数据会直接影响项目中标,因此压力非

                  [转帖]神秘的backlog参数与TCP连接队列

                  https://www.cnblogs.com/codelogs/p/16060820.html 简介# 这要从一次压测项目说起,那是我们公司的系统与另几家同行公司的系统做性能比拼,性能数据会直接影响项目中标,因此压力非常大。 当时甲方给大家提供了17台服务器供系统部署,并使用LoadRunner对

                  [转帖]net.ipv4.tcp_max_syn_backlog & net.core.somaxconn

                  https://www.cnblogs.com/apink/p/15632882.html TCP SYN_REVD, ESTABELLISHED 状态对应的队列 TCP 建立连接时要经过 3 次握手,在客户端向服务器发起连接时,对于服务器而言,一个完整的连接建立过程,服务器会经历 2 种 TCP 

                  [转帖]TCP的半关闭、半连接、半打开

                  参考:《UNIX 网络编程 · 卷1 : 套接字联网API》 TCP 半关闭 如果将客户端与服务器之间的网络作为全双工管道来考虑,请求是从客户端向服务器发送,应答是从服务器向客户端发送,其如下图所示: 上图假设 RTT 为 8,且服务器没有处理时间且请求大小与应答大小相同。既然从管道发出到管道的另一

                  [转帖]tcp 粘包 和 TCP_NODELAY 学习

                  https://www.cnblogs.com/zhangkele/p/9494280.html TCP通信粘包问题分析和解决 在socket网络程序中,TCP和UDP分别是面向连接和非面向连接的。因此TCP的socket编程,收发两端(客户端和服务器端)都要有成对的socket,因此,发送端为了将

                  [转帖]TCP三次握手详解,滑动窗口,拥塞窗口,网络包路由过程,全连接队列,半连接队列

                  众所周知,网络分层有传统的OSI七层模型和后来的基于TCP/IP的四层模型: 那么在一次网络的传输过程中具体的流程是怎么样的,我们先从一个数据包的传输说起(以TCP为例): TCP协议根据上层应用提供的信息生成TCP报文 TCP报文在交由下面的IP层(网络层)进行处理,委托IP模块将TCP报文封装成

                  [转帖]计算机网络【TCP的序号 确认号详解 TCP三次握手 和 四次挥手】

                  文章目录 初始TCP三次握手--建立连接再聊TCP的序号和确认号TCP建立连接--三次握手为什么需要三次握手,二次握手为什么不行?假如第三次握手失败,是如何处理的?TCP释放连接--四次挥手为什么断开连接需要4次挥手TCP释放连接--状态解读 初始TCP三次握手–建立连接 在发送方和接收方方收发TC

                  [转帖]TCP之Nagle、Cork、Delay ACK(延迟确认)

                  https://www.jianshu.com/p/167ba81206fb 参考资料 TCP协议中的Nagle算法 TCP中的Nagle算法 Linux下TCP延迟确认(Delayed Ack)机制导致的时延问题分析 TCP-IP详解:Delay ACK 1. Nagle 算法 1.1. 原理 N