socket描述符的那些事

沉默术士
• 阅读 3513

前几天看到有人发的一个面试题,问的是MySQL连接的进程描述符的问题。

在Linux里,一切皆文件,那进程描述符,实际就是文件描述符了。

我们还知道Linux 内核提供了一种通过 proc文件系统,/proc 文件系统是一个虚拟文件系统,通过它可以使用一种新的方法在 Linux内核空间和用户间之间进行通信。在 /proc文件系统中,我们可以将对虚拟文件的读写作为与内核中实体进行通信的一种手段,但是与普通文件不同的是,这些虚拟文件的内容都是动态创建的。用户和应用程序可以通过proc得到系统的信息,并可以改变内核的某些参数。

/proc目录通常对用户来说是只读的,如果你直接在bash下想要修改一个文件是权限不足的。但是对系统来说是可写的,因此也就可以通过编程来实现增删改查。

查看socket描述符

那么,这个文件描述符就一定是在/proc 目录了。想必那就是在相应进程的/proc/$pid/fd 目录下存放了此进程所有打开的fd。

[root@localhost fd]# pwd
/proc/1723/fd
[root@manager 1723]# ll fd|grep socket
lrwx------ 1 root root 64 Jul  7 13:49 103 -> socket:[5722374]
lrwx------ 1 root root 64 Jul  7 13:49 104 -> socket:[5057632]
lrwx------ 1 root root 64 Jul  7 13:49 105 -> socket:[5722375]
lrwx------ 1 root root 64 Jul  7 13:49 106 -> socket:[5057636]
lrwx------ 1 root root 64 Jul  7 13:49 107 -> socket:[5983188]
lrwx------ 1 root root 64 Jul  7 13:49 124 -> socket:[5983189]
lrwx------ 1 root root 64 Jul  7 13:49 130 -> socket:[27456]
lrwx------ 1 root root 64 Jul  7 13:49 131 -> socket:[27458]
lrwx------ 1 root root 64 Jul  7 13:49 132 -> socket:[27460]
lrwx------ 1 root root 64 Jul  7 13:49 51 -> socket:[23447]
lrwx------ 1 root root 64 Jul  7 13:49 52 -> socket:[23448]
lrwx------ 1 root root 64 Jul  7 13:49 78 -> socket:[5057630]
lrwx------ 1 root root 64 Jul  7 13:49 79 -> socket:[5721339]
lrwx------ 1 root root 64 Jul  7 13:49 80 -> socket:[3639663]
lrwx------ 1 root root 64 Jul  7 13:49 81 -> socket:[5057631]
lrwx------ 1 root root 64 Jul  7 13:49 82 -> socket:[5721340]
lrwx------ 1 root root 64 Jul  7 13:49 95 -> socket:[5722372]

这个结果,和netstat看到的相差无几

[root@manager ~]# netstat -antp | grep 1723 | wc -l
16

当然,这和用lsof统计到的结果应该也是差不多的。之所以说差不多,而不是一样,是因为虽然netstat和lsof虽然也是读取的/proc文件系统,但是有自己的过滤和判断条件,比如这两个工具除了读取/proc/pid/fd目录,还会读取/proc/net/tcp(udp)文件。因此,如果socket创建了,没有被使用,那么就只会在/proc/pid/fd下面有,而不会在/proc/net/tcp(udp),那么netstat就统计不到了。

那么这个socket:后面的一串数字是什么呢?看起来像是端口号,有些又明显不是,其实是该socket的inode号。
那么,知道了某个进程打开的socket的inode号后,我们可以做什么呢?这就涉及到/proc/net/tcp(udp对应/proc/net/udp)文件了,其中也列出了相应socket的inode号通过比对此字段,我们能在/proc/net/tcp下获得此套接口的其他信息,如对应的<本地地址:端口号,远端地址:端口号>四元组,窗口大小,状态等信息。具体字段含义详见net/ipv4/tcp_ipv4.c 中的 tcp4_seq_show 函数。

 [root@manager net]# cat /proc/net/tcp
  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode
   0: 00000000:006F 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 15528 1 ffff880426f60000 100 0 0 10 0
   1: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 19300 1 ffff880426f607c0 100 0 0 10 0
   2: 0100007F:0019 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 20170 1 ffff88042dfc8000 100 0 0 10 0
   3: 00000000:21DE 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 21513 1 ffff88042dfc87c0 100 0 0 10 0
   4: 55F9B40A:0016 59CDB40A:2779 01 00000000:00000000 02:000936FD 00000000     0        0 6004930 2 ffff88042dfca6c0 22 6 1 10 -1
   5: 55F9B40A:0016 59CDB40A:2733 01 00000030:00000000 01:00000018 00000000     0        0 5984362 4 ffff880426f645c0 25 4 31 10 -1
   6: 55F9B40A:0016 59CDB40A:2778 01 00000000:00000000 02:000936FD 00000000     0        0 6004866 2 ffff88042dfcae80 24 7 1 10 -1
   7: 55F9B40A:8F2E 55F9B40A:20F9 01 00000000:00000000 00:00000000 00000000     0        0 22051 1 ffff88042dfc9f00 20 4 30 10 -1
   8: 55F9B40A:0016 59CDB40A:2738 01 00000000:00000000 02:000843C9 00000000     0        0 5983909 2 ffff880426f664c0 22 4 21 7 6
[root@manager net]#

这个文件怎么解读呢,我们暂时只看第一部分

   8: 55F9B40A:0016 59CDB40A:2738 01 
   |      |      |      |      |   |--> connection state(套接字状态)
   |      |      |      |      |------> remote TCP port number(远端端口,主机字节序)
   |      |      |      |-------------> remote IPv4 address(远端IP,网络字节序)
   |      |      |--------------------> local TCP port number(本地端口,主机字节序)
   |      |---------------------------> local IPv4 address(本地IP,网络字节序)
   |----------------------------------> number of entry

比如我们看到59CDB40A:2738这个rem_address,很自然它就是TCP的四元组,十六进制转为二进制后就是
89.205.180.10:10040,注意此处IP地址应该是10.180.205.89。用lsof验证下

[root@manager net]# lsof -i|grep 10040
sshd    17757   root    3u  IPv4 5983909      0t0  TCP manager.bigdata:ssh->10.180.205.89:10040 (ESTABLISHED)

connection state(套接字状态),不同的数值代表不同的状态,参照如下:

TCP_ESTABLISHED:1   TCP_SYN_SENT:2
TCP_SYN_RECV:3      TCP_FIN_WAIT1:4
TCP_FIN_WAIT2:5     TCP_TIME_WAIT:6
TCP_CLOSE:7         TCP_CLOSE_WAIT:8
TCP_LAST_ACL:9      TCP_LISTEN:10
TCP_CLOSING:11

我们看的这条数据并不是MySQL的连接。问题来了,为什么MySQL里看到那么多fd,netstat也看到了很多,但是 /proc/net/tcp 下并没有那么多socket描述符呢。前面说过了,/proc/net/tcp(udp)可以认为是proc/pid/fd的子集,但是这也差的太离谱了。其实原因很简单,如果你在 /proc/net/tcp下找不到,试试去/proc/net/tcp6 下找找呢

关闭指定socket连接

如果我们再进一步,我们现在可以找到每个pid下的socket描述符,如果我想断掉这个描述符也就是断开这个连接,怎么做呢?用防火墙显然不是好主意,防火墙通常是针对某个IP和固定端口的。这个时候,socket fd号就派上用场了。注意,fd和inode是两码事。

查看当前fd

(base) [root@manager ~]# ll /proc/1723/fd|grep socket
lrwx------ 1 root root 64 Jul  7 13:49 103 -> socket:[10232948]
lrwx------ 1 root root 64 Jul  7 13:49 105 -> socket:[9490029]
lrwx------ 1 root root 64 Jul  7 13:49 107 -> socket:[10232952]
lrwx------ 1 root root 64 Jul  7 13:49 124 -> socket:[10232954]
lrwx------ 1 root root 64 Jul  7 13:49 130 -> socket:[27456]
lrwx------ 1 root root 64 Jul  7 13:49 131 -> socket:[27458]
lrwx------ 1 root root 64 Jul  7 13:49 132 -> socket:[27460]
lrwx------ 1 root root 64 Jul  7 13:49 51 -> socket:[23447]
lrwx------ 1 root root 64 Jul  7 13:49 52 -> socket:[23448]
lrwx------ 1 root root 64 Jul  7 13:49 79 -> socket:[10882498]

现在在另外一台服务器,直接用命令行mysql -h连接本机的mysql服务,然后再查看下fd列表

(base) [root@manager ~]# ll /proc/1723/fd|grep socket
lrwx------ 1 root root 64 Jul  7 13:49 103 -> socket:[10232948]
lrwx------ 1 root root 64 Jul  7 13:49 105 -> socket:[9490029]
lrwx------ 1 root root 64 Jul  7 13:49 107 -> socket:[10232952]
lrwx------ 1 root root 64 Jul  7 13:49 124 -> socket:[10232954]
lrwx------ 1 root root 64 Jul  7 13:49 130 -> socket:[27456]
lrwx------ 1 root root 64 Jul  7 13:49 131 -> socket:[27458]
lrwx------ 1 root root 64 Jul  7 13:49 132 -> socket:[27460]
lrwx------ 1 root root 64 Jul  7 13:49 51 -> socket:[23447]
lrwx------ 1 root root 64 Jul  7 13:49 52 -> socket:[23448]
lrwx------ 1 root root 64 Jul  7 13:49 79 -> socket:[10882498]
lrwx------ 1 root root 64 Jul  7 13:49 80 -> socket:[11222776]

对比下,最下面多出来的一行就是新增的那个连接,fd=80,socket inode=11222776。

我们使用gdb调用syscall,关闭这个fd

gdb -p 1723
call close(80)
quit

然后看一下,远程mysql连接已经断了
socket描述符的那些事

--- 公众号
socket描述符的那些事

点赞
收藏
评论区
推荐文章
芝士年糕 芝士年糕
2年前
Linux shell 内建命令
前言一、shell简介shell是Linux系统中一个提供命名行的交互式工具,本质是一个c语言可执行程序,为用户提供了启动程序,管理文件系统中的文件以及运行在Linux系统上的进程的途径。命令行提示符是shell负责交互的部分,允许用户输入文本命令,然后解释命令,并在内核中执行。Linux系统上有多种shell可用,Linux发行版默认的shell都是ba
说透IO多路复用模型
在说IO多路复用模型之前,我们先来大致了解下Linux文件系统。在Linux系统中,不论是你的鼠标,键盘,还是打印机,甚至于连接到本机的socketclient端,都是以文件描述符的形式存在于系统中,诸如此类,等等等等,所以可以这么说,一切皆文件。
Wesley13 Wesley13
3年前
IO复用-阻塞IO-非阻塞IO-同步IO-异步IO
本文是对《UNIX网络编程卷1》第6章的总结。一、            什么是IO复用?它是内核提供的一种同时监控多个文件描述符状态改变的一种能力;例如当进程需要操作多个IO相关描述符时(例如服务器程序要同时查看监听socket和大量业务socket是否有数据到来),需要内核能够监控这许多描述符,一旦这些描述符有就绪(或者状态改变了)就告
Stella981 Stella981
3年前
Linux IO 概念(1)
基础概念文件描述fd        文件描述符(filedescription),用于表述指向文件引用的抽象话题概念文件描述符在形式上是一个非负整数,实际上它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表,当程序打开一个现有文件或者创建一个新文件时,内核就向该进程返回一个文件描述符        unix系统把任何对象看做
Stella981 Stella981
3年前
Docker 镜像
我们都知道,操作系统分为内核和用户空间。对于Linux而言,内核启动后,会挂载 root 文件系统为其提供用户空间支持。而Docker镜像(Image),就相当于是一个 root 文件系统。比如官方镜像 ubuntu:18.04 就包含了完整的一套Ubuntu18.04最小系统的 root 文件系统。Docker镜像是一个
Stella981 Stella981
3年前
Linux内核Socket参数调优
可调优的内核变量存在两种主要接口:sysctl命令和/proc文件系统,proc中与进程无关的所有信息都被移植到sysfs中。IPV4协议栈的sysctl参数主要是sysctl.net.core、sysctl.net.ipv4,对应的/proc文件系统是/proc/sys/net/ipv4和/proc/sys/net/core。只有内核在编译时包含了特定的
Stella981 Stella981
3年前
Linux 多线程编程
1.Linux“线程”进程与线程之间是有区别的,不过Linux内核只提供了轻量进程的支持,未实现线程模型。Linux是一种“多进程单线程”的操作系统。Linux本身只有进程的概念,而其所谓的“线程”本质上在内核里仍然是进程。大家知道,进程是资源分配的单位,同一进程中的多个线程共享该进程的资源(如作为共享内存的全局变量)。Linux
Stella981 Stella981
3年前
Linux系统性能监控
Linux中提供了一个非常好的文件虚拟系统(/proc)来对系统性能进行监控。/proc文件虚拟系统是一种内核和内核模块用来向进程发送信息的机制。它存在于内存而不是硬盘中。/proc文件虚拟系统的主要功能,可以监控到包括进程信息、系统信息、CPU信息、负载信息、系统内存信息等等。1、监测系统负载1.使用uptime命令可以查看系统负载
Stella981 Stella981
3年前
Linux的文件描述符
(1).文件描述符的定义  文件描述符是内核为了高效管理已被打开的文件所创建的索引,用于指向被打开的文件,所有执行I/O操作的系统调用都通过文件描述符;文件描述符是一个简单的非负整数,用以表明每个被进程打开的文件。程序刚刚启动时,第一个打开的文件是0,第二个是1,以此类推。也可以理解为文件的身份ID。  用户通过操作系统处理信息的过程中,使用的交互设
Stella981 Stella981
3年前
Linux中的文件描述符与打开文件之间的关系
导读内核(kernel)利用文件描述符(filedescriptor)来访问文件。文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。1\.概述在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件、目录文件、链接文件和设备文件。文件描述符(file