一, 知识准备
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)
五, 参考资料
https://gist.github.com/jkstill/5095725
至此, 本文结束
在下才疏学浅, 有撒汤漏水的, 请各位不吝赐教...
来源: http://www.bubuko.com/infodetail-2853871.html