我发现 Linux 文档写错了-LMLPHP

大家好,我是小林。

周末的时候,有位读者疑惑为什么 Linux man 手册中关于 netstat 命令中的 tcp listen 状态下的 Recv-Q 和 Send-Q 这两个信息的描述跟我的图解网络写的不一样?

我看了源码后,确认了这个 man 手册写的不对。没想到 Linux 的 man 手册也会出错。

首先,先给大家介绍下 netstat 命令。netstat 命令是查看网络状态很常见的 Linux 命令。

比如,如果我们想查看系统中的进程监听了哪些 TCP 端口,则可以使用下面这个命令: 我发现 Linux 文档写错了-LMLPHP

接下来,小林带大家分析,为什么我说 man 手册写错了 netstat 命令中 Recv-Q 和 Send-Q 的描述?

当前全连接队列的大小;
  • 上面被我划掉的部分,就是我与 man 手册差异的地方。

    在 TCP 三次握手的时候,Linux 内核会维护两个队列,分别是:

    • 半连接队列,也称 SYN 队列;
    • 全连接队列,也称 accept 队列;

    服务端收到客户端发起的 SYN 请求后,内核会把该连接存储到半连接队列,并向客户端响应 SYN+ACK,接着客户端会返回 ACK,服务端收到第三次握手的 ACK 后,内核会把连接从半连接队列移除,然后创建新的完全的连接,并将其添加到全连接队列,等待进程调用 accept 函数时把连接取出来。

    我发现 Linux 文档写错了-LMLPHP 如果你想知道 TCP 半连接和全连接溢出会发生什么?可以看看这篇文章:TCP 半连接队列和全连接队列满了会发生什么?又该如何应对?

    源码分析

    netstat 工具在获取 TCP 连接的信息的时候,实际上是读取了 /proc/net/tcp 文件里的数据,而这个文件的数据是由内核由 net/ipv4/tcp_ipv4.c 文件中的 tcp4_seq_show() 函数打印的。

    所以,我们直接看 tcp4_seq_show() 函数是根据什么信息打印出 Recv-Q 和 Send-Q 的数据。

    有一个网站可以在线看 Linux 内核代码:https://elixir.bootlin.com/,每个内核版本的代码都有,平常我都是在这里看。

    我发现 Linux 文档写错了-LMLPHP

    这次,我们选择内核版本为 2.6.18 查看 tcp4_seq_show() 函数的实现,如下:

    static int tcp4_seq_show(struct seq_file *seq, void *v)
    {
     .....

     switch (st->state) {
     case TCP_SEQ_STATE_LISTENING:
     case TCP_SEQ_STATE_ESTABLISHED:
      get_tcp4_sock(v, tmpbuf, st->num);
      break;
     .......
     }
     ...
     return 0;
    }

    我们只分析 tcp 连接状态为 ESTABLISHED 和 LISTENING 时打印的信息,所以接下来看 get_tcp4_sock 函数。

    get_tcp4_sock 函数中,打印信息的代码如下: 我发现 Linux 文档写错了-LMLPHP 我在图中标红了两行代码,这两行代码分别是 Recv-Q 和 Send-Q 的数据。

    我单独把这两行代码抽了出来:

    // Send-Q 打印的数据
    tp->write_seq - tp->snd_una,

    //Recv-Q 打印的数据
    (sp->sk_state == TCP_LISTEN) ? sp->sk_ack_backlog : (tp->rcv_nxt - tp->copied_seq),

    可以看到, 不管 TCP 连接状态是什么, Send-Q 都是发送缓冲区中已发送但未被确认的数据大小。

    然后针对 Recv-Q ,在 TCP 连接状态为 LISTEN 时,打印的是 sk_ack_backlog 的值。

    那 sk_ack_backlog 的值代表什么意思呢?

    下面这个是判断全连接队列是否溢出的函数:我发现 Linux 文档写错了-LMLPHP 可以得知,sk_ack_backlog 其实是当前全连接队列的大小,也就是经历三次握手后等待被应用层 accpet() 的连接的数量。

    所以,从上面的源码分析过,得到的结论如下:

    • netstat 命令中的 Recv-Q:如果 TCP 连接状态处于 Established,Recv-Q 的数值表示接收缓冲区中还没拷贝到应用层的数据大小;如果 TCP 连接状态处于 Listen 状态,Recv-Q 的数值表示当前全连接队列的大小;
    • netstat 命令中的 Send-Q:表示发送缓冲区中已发送但未被确认的数据大小(不管 TCP 是 Listen 状态还是 Established 状态都表示这个意思);

    好了,至此就分析完了。

    最后

    看到这,大家肯定会说:小林你太强了吧,为什么对 Linux 内核源码那么熟,这都能分析出来

    其实,我并没有熟读过 Linux 内核源码啦,其实只要大家有好奇心,其实你也能分析出来。

    我也是通过网上的资料,一点一点分析出来的,并不是直接就在内核源码里查,不然那真是大海捞针。

    我是这样一步一步查资料分析的:

    • 先网上查下 netstat 源码,看是根据什么信息打印 Send-Q 和 Recv-Q,然后看到网上有人说是读 /proc/net/tcp 这个文件;
    • 接着,就网上查 /proc/net/tcp 这个文件是怎么打印的,然后看到网上有人说是由 net/ipv4/tcp_ipv4.c 文件中的 tcp4_seq_show() 函数打印的;
    • 最后,再自己去看 tcp4_seq_show 函数的实现,这个函数的代码也不多,就几十行,所以很容易就分析出来了。

    你看,其实我也是通过「搜索」一步一步分析出来的,其实并没有什么难度。

    只是我比较细节一点。

    微信搜索公众号:「小林coding」 ,回复「图解」即可免费获得「图解网络、图解系统、图解MySQL、图解Redis」PDF 电子书
    04-25 17:41