linux一切皆文件之tcp socket描述符(三)
一、知识准备
1、在linux中,一切皆为文件,所有不同种类的类型都被抽象成文件(比如:块设备,socket套接字,pipe队列)
2、操作这些不同的类型就像操作文件一样,比如增删改查等
二、环境准备
组件 | 版本 |
---|---|
os | centos linux release 7.5.1804 |
三、tcp socket 文件描述符
● 当我们建立一条tcp连接时,在linux操作系统中会创建一个socket文件描述符
● 通过文件描述符就能找到socket的几本信息,比如tcp四元组(client-ip:client-port --> server-ip:server-port
)
先准备2个脚本:
server.py主要用于建立客户端的连接请求,并且接收客户端传来的数据,然后将收到的数据回传给客户端
client.py每隔1秒向服务端发送一次'hello world'
server.py
import socket server_addr = ('127.0.0.1' , 22222) sock = socket.socket(socket.af_inet, socket.sock_stream) sock.bind(server_addr) sock.listen(5) while true: conn, clientaddr = sock.accept() while true: data = conn.recv(100) conn.sendall(data) sock.close()
client.py
import socket import time server_addr = ('127.0.0.1' , 22222) sock = socket.socket(socket.af_inet, socket.sock_stream) sock.connect(server_addr) while true: message = 'hello world!' sock.send(message) sock.recv(100) time.sleep(1) sock.close()
分别启动server.py与client.py
[root@localhost ~]# python /tmp/server.py & [1] 14199 [root@localhost ~]# python /tmp/client.py & [2] 14202
查看server.py打开的文件描述符
[root@localhost ~]# ls -l /proc/14199/fd total 0 lrwx------ 1 root root 64 nov 7 07:42 0 -> /dev/pts/0 lrwx------ 1 root root 64 nov 7 07:42 1 -> /dev/pts/0 lrwx------ 1 root root 64 nov 7 07:42 2 -> /dev/pts/0 lrwx------ 1 root root 64 nov 7 07:42 3 -> socket:[99154] lrwx------ 1 root root 64 nov 7 07:42 4 -> socket:[99155] [root@localhost ~]# lsof -n | grep -e '99154|99155' python 14199 root 3u ipv4 99154 0t0 tcp 127.0.0.1:22222 (listen) python 14199 root 4u ipv4 99155 0t0 tcp 127.0.0.1:22222->127.0.0.1:56946 (established)
我们主要关注established
状态的socket描述符,也就是4 -> socket:[99155]
[root@localhost fd]# more /proc/net/tcp sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ... 4: 0100007f:56ce 0100007f:de72 01 00000000:00000000 00:00000000 00000000 0 0 99155 1 ffff90d8bb0145c0 20 4 31 10 -1
进程打开了tcp socket 描述符4 -> socket:[99155]
,socket描述符指向内存中的socket结构体,该结构体详细描述了这个socket的详细信息
最重要的是tcp四元组(local_ip:local_port --> remote_ip:remote_port
),拆分转换成10进制
0100007f:56ce
[root@localhost ~]# ((d=0x01)) [root@localhost ~]# ((c=0x00)) [root@localhost ~]# ((b=0x00)) [root@localhost ~]# ((a=0x7f)) [root@localhost ~]# ((e=0x56ce)) [root@localhost ~]# echo "$a.$b.$c.$d:$e" 127.0.0.1:22222
0100007f:de72
[root@localhost ~]# ((d=0x01)) [root@localhost ~]# ((c=0x00)) [root@localhost ~]# ((b=0x00)) [root@localhost ~]# ((a=0x7f)) [root@localhost ~]# ((e=0xde72)) [root@localhost ~]# echo "$a.$b.$c.$d:$e" 127.0.0.1:56946
在/proc/net/tcp包含了tcp连接的重要状态信息:
00000000:00000000 : 发送队列与接收队列 (正数第四个字段)
-1 : 慢启动门限 (倒数第一个字段)
10 : 拥塞窗口 (倒数第二个字段)
这里面还有很多描述:比如慢启动门限、传输队列以及接收队列、窗口探查等tcp相关的重要参数都可以查询到,具体的大家可以去看下《tcp/ip详解卷》
client.py也存在同样的行为:
[root@localhost ~]# ls -l /proc/14202/fd total 0 lrwx------ 1 root root 64 nov 19 04:43 0 -> /dev/pts/0 lrwx------ 1 root root 64 nov 19 04:43 1 -> /dev/pts/0 lrwx------ 1 root root 64 nov 19 04:43 2 -> /dev/pts/0 lrwx------ 1 root root 64 nov 19 04:43 3 -> socket:[28728] [root@localhost ~]# lsof -n | grep 28728 python 14202 root 3u ipv4 28728 0t0 tcp 127.0.0.1:56946->127.0.0.1:22222 (established)
[root@localhost fd]# more /proc/net/tcp sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ... 3: 0100007f:c31a 0100007f:de72 01 00000000:00000000 00:00000000 00000000 0 0 28728 3 ffff8a74ba1a0f80 20 4 30 10 -1
0100007f:56ce
[root@localhost ~]# ((d=0x01)) [root@localhost ~]# ((c=0x00)) [root@localhost ~]# ((b=0x00)) [root@localhost ~]# ((a=0x7f)) [root@localhost ~]# ((e=0x56ce)) [root@localhost ~]# echo "$a.$b.$c.$d:$e" 127.0.0.1:22222
0100007f:de72
[root@localhost ~]# ((d=0x01)) [root@localhost ~]# ((c=0x00)) [root@localhost ~]# ((b=0x00)) [root@localhost ~]# ((a=0x7f)) [root@localhost ~]# ((e=0xde72)) [root@localhost ~]# echo "$a.$b.$c.$d:$e" 127.0.0.1:56946
总结一下:
● server.py与client.py各自打开tcp socket 描述符,该描述符指向内存中的socket结构体
● socket结构体描述了关于tcp的所有信息,其中通过tcp 4元组找到对端的通信节点
● socket将用户数据以及自身结构数据封装完成之后会交给底层的tcp协议,然后是ip协议、链路层信息,最后通过物理链路到达对端
● 对端也会依次解包,直至将发送端数据写入到指定的内存当中,最终由应用程序读取(本文中的server.py或client.py)
client.py server.py +---------------+ +---------------+ |pid:14202 | |pid:14199 | | +-----+ | | +-----+ | | |fd:3 | | | |fd:4 | | | +-----+ | | +-----+ | +---------------+ +---------------+ | | user space | | +---------------------------------------------------------------------+ kernel space | | | | v v +------+-------+ +------+-------+ |socket:[28728]| |socket:[99155]| +------+-------+ +------+-------+ | | | | v v +----+----+ +----+----+ | socket | | socket | +----+----+ +----+----+ | | | | v v ++---------------------------------+- | tcp | +------------------------------------
四、小结
● tcp连接中最重要的是tcp四元组,而进程打开tcp socket描述符可以找到四元组信息,从而确定双方的ip和port
● 通过socket文件描述符可以找到内存中的socket结构体,获取到tcp连接的详细信息,包括必备四元组、文件的inode、时间、出队入队状态等等
● 1个进程可以创建多个tcp连接,也就是创建多个socket文件描述符,这由该进程能够打开的文件数量限制(ulimit -n
)
五、参考资料
http://www.cs.colostate.edu/~gersch/cs457/cs457_tutorial2.pdf
至此,本文结束
在下才疏学浅,有撒汤漏水的,请各位不吝赐教...