大家好,我是小林。周末有读者疑惑为什么linuxman手册中的netstat命令中关于tcplisten状态下的Recv-Q和Send-Q的描述和我在图文网络中写的不一样?看了源码最后确认是man手册写错了。没想到linux的man手册也会出错。首先,让我向您介绍netstat命令。netstat命令是一个非常常用的查看网络状态的Linux命令。比如我们要查看系统中有哪些进程监听了哪些TCP端口,可以使用如下命令netstat-napt:-Qdescription?读者提出的疑惑:先给大家翻译一下,man手册是什么(https://man7.org/linux/man-pages/man8/netstat.8.html):Recv-Q:如果TCP连接状态Established,Recv-Q的值表示接收缓冲区中还没有复制到应用层的数据大小;如果TCP连接状态为Listen状态,Recv-Q的值表示当前syn半连接队列Size(内核2.6.18版本起)Send-Q:如果TCP连接状态为Established,Send的值-Q表示发送缓冲区中已经发送但未确认的数据大小;如果TCP连接状态为Listen状态,Send-Q的值表示syn半连接队列的容量(内核2.6.18版本起)。通过查阅内核2.6.18版本的源码,得到如下结论:被复制到应用层;连接状态为Listen状态,Recv-Q的值表示当前syn半连接队列的大小;Send-Q:如果TCP连接状态为Established,Send-Q的值表示发送缓冲区已经发送但未确认数据大小;如果TCP连接状态为Listen状态,Send-Q的值表示syn半连接队列的容量。被我划掉的部分是我和man手册的区别。什么是TCP半连接队列和全连接队列?在TCP三次握手过程中,Linux内核会维护两个队列,即:半连接队列,也称为SYN队列;全连接队列,也称为接受队列;内核收到客户端发起的SYN请求后,会将连接存入半连接队列,并以SYN+ACK响应客户端,然后客户端返回ACK。服务器收到第三次握手的ACK后,内核会将该连接从半连接队列中移除,然后新建一个全连接,加入到全连接队列中,等待进程调用accept函数取出连接。如果你想知道TCP半连接和全连接溢出时会发生什么?可以看这篇文章:TCP半连接队列和全连接队列满了怎么办?如何处理?源码分析netstat工具在获取TCP连接发送信息时,实际读取的是/proc/net/tcp文件中的数据,这个文件中的数据是内核通过net/ipv4中的tcp4_seq_show()函数打印出来的/tcp_ipv4.c文件。那么,我们直接看看tcp4_seq_show()函数打印出Recv-Q和Send-Q的数据是什么信息。有一个可以在线查看Linux内核代码的网站:https://elixir.bootlin.com/,每个内核版本的代码都有,我一般都是在这里看的。这次我们选择2.6.18版本的内核,查看tcp4_seq_show()函数的实现,如下:staticinttcp4_seq_show(structseq_file*seq,void*v){.....switch(st->state){caseTCP_SEQ_STATE_LISTENING:caseTCP_SEQ_STATE_ESTABLISHED:get_tcp4_sock(v,tmpbuf,st->num);休息;....}...return0;}我们只分析tcp连接状态为ESTABLISHED和LISTENING时打印的信息,所以下面我们来看get_tcp4_sock函数。在get_tcp4_sock函数中,打印信息的代码如下:我在图中用红色标注了两行代码,这两行代码分别是Recv-Q和Send-Q的数据。我分别提取了这两行代码://DataprintedbySend-Qtp->write_seq-tp->snd_una,//DataprintedbyRecv-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的值是什么意思?下面是一个判断全连接队列是否溢出的函数:可以知道sk_ack_backlog其实就是当前全连接队列的大小,也就是三次握手后等待应用层accpet()连接的数量。因此,通过分析上述源码,得出如下结论:netstat命令中的Recv-Q:如果TCP连接状态为Established,则Recv-Q的值表示接收缓冲区中还未连接的数据大小被复制到应用层;ifTCP连接状态为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函数的实现,这个函数的代码不多,只有几十行,分析起来很容易。你看,其实我也是通过“搜索”一步步分析出来的,也不难。
