一、知識準備
1、在linux中,一切皆為檔案,所有不同種類的型別都被抽象成檔案(比如:塊裝置,socket套接字,pipe佇列)
2、操作這些不同的型別就像操作檔案一樣,比如增刪改查等
3、主要用於:執行在同一臺機器上的2個程式相互之間的資料通訊
4、它們和網路檔案描述符非常相似(比如:TCP socket),他們的通訊發生在作業系統核心
二、環境準備
元件 | 版本 |
---|---|
OS | CentOS Linux release 7.5.1804 |
三、Unix domain socket 檔案描述符
先準備2個指令碼:
server.py主要用於建立客戶端的連線請求,並且接收客戶端傳來的資料,然後將收到的資料回傳給客戶端
client.py每隔1秒向服務端傳送一次`hello world`
server.py:
import socket
server_addr = `/tmp/server.sock`
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.bind(server_addr)
sock.listen(0)
while True:
conn, clientAddr = sock.accept()
while True:
data = conn.recv(100)
conn.sendall(data)
client.py:
import socket
import time
server_addr = `/tmp/server.sock`
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect(server_addr)
while True:
message = `hello world!`
sock.sendall(message)
sock.recv(100)
time.sleep(1)
sock.close()
先看下server.py的狀態:
[root@localhost ~]# python /tmp/server.py &
[1] 2554
[root@localhost ~]# ls -l /proc/2554/fd
total 0
lrwx------ 1 root root 64 Nov 5 02:39 0 -> /dev/pts/0
lrwx------ 1 root root 64 Nov 5 02:39 1 -> /dev/pts/0
lrwx------ 1 root root 64 Nov 5 02:39 2 -> /dev/pts/0
lrwx------ 1 root root 64 Nov 5 02:39 3 -> socket:[28724]
[root@localhost ~]# grep 28724 /proc/net/unix
ffff90d8ba564000: 00000002 00000000 00010000 0001 01 28724 /tmp/server.sock
[root@localhost ~]# lsof -n | grep 28724
python 2554 root 3u unix 0xffff90d8ba564000 0t0 28724 /tmp/server.sock
[root@localhost ~]# netstat -anp | grep 28724
unix 2 [ ACC ] STREAM LISTENING 28724 2554/python /tmp/server.sock
程式2554建立了開啟了unix domain socket描述符(3 -> socket:[19803]
),並且通過該描述符,開啟了/tmp/server.sock檔案,其主要作用是用於監聽
我們執行client.py並觀察狀態
[root@localhost ~]# python /tmp/client.py &
[2] 2555
[root@localhost ~]# ls -l /proc/2555/fd
total 0
lrwx------ 1 root root 64 Nov 5 02:39 0 -> /dev/pts/0
lrwx------ 1 root root 64 Nov 5 02:39 1 -> /dev/pts/0
lrwx------ 1 root root 64 Nov 5 02:39 2 -> /dev/pts/0
lrwx------ 1 root root 64 Nov 5 02:39 3 -> socket:[28728]
[root@localhost ~]# grep 28728 /proc/net/unix
ffff90d8b95b0400: 00000003 00000000 00000000 0001 03 28728
[root@localhost ~]# lsof -n | grep 28728
python 2555 root 3u unix 0xffff90d8b95b0400 0t0 28728 socket
與server.py的行為差不多。client.py也建立了unix domain socket描述符3 -> socket:[28728]
,通過socket:[18974]
,找到一條socket
檢視server.py發生的變化:
[root@localhost ~]# ls -l /proc/2554/fd
total 0
lrwx------ 1 root root 64 Nov 5 02:39 0 -> /dev/pts/0
lrwx------ 1 root root 64 Nov 5 02:39 1 -> /dev/pts/0
lrwx------ 1 root root 64 Nov 5 02:39 2 -> /dev/pts/0
lrwx------ 1 root root 64 Nov 5 02:39 3 -> socket:[28724]
lrwx------ 1 root root 64 Nov 5 02:39 4 -> socket:[28725]
server.py新增了一個4 -> socket:[28725]
,這是剛才client.py連線成功之後server.py新開啟的描述符
[root@localhost ~]# lsof -n | grep -E `28728|28724|28725`
python 2554 root 3u unix 0xffff90d8ba564000 0t0 28724 /tmp/server.sock
python 2554 root 4u unix 0xffff90d8b95b0000 0t0 28725 /tmp/server.sock
python 2555 root 3u unix 0xffff90d8b95b0400 0t0 28728 socket
[root@localhost ~]# netstat -anp | grep unix | grep -E `28728|28724|28725`
unix 2 [ ACC ] STREAM LISTENING 28724 2554/python /tmp/server.sock
unix 3 [ ] STREAM CONNECTED 28725 2554/python /tmp/server.sock
unix 3 [ ] STREAM CONNECTED 28728 2555/python
到目前為止,整個unix domain socket的通訊過程已經比較清晰的展現了:
● server.py啟動之後,開啟監聽的描述符,等待來自客戶端的連線請求
● client.py啟動之後,與server連線成功,開啟一個描述符用於與server.py通訊
● server.py會再開啟一個描述符用於與client.py進行資料通訊
但是目前還有2個問題:
(1)/tmp/server.sock到底作用是什麼
(2)server與client是怎麼進行資料通訊的
問題(1)
● /tmp/server.sock是作業系統的實體檔案,擁有一個全域性的檔案系統描述符,這個描述符在作業系統中是唯一的
● server.py啟動時開啟了server.sock,就聲名了與server.py建立連線就只能通過server.sock檔案
● 這就相當於TCP socket中四元組中的兩元(server_ip:server_port
)
問題(2)
我們來使用strace命令看看server.py的核心呼叫
[root@localhost tmp]# strace -p 2554
strace: Process 2554 attached
recvfrom(4, "hello world!", 100, 0, NULL, NULL) = 12
sendto(4, "hello world!", 12, 0, NULL, 0) = 12
recvfrom(4, "hello world!", 100, 0, NULL, NULL) = 12
sendto(4, "hello world!", 12, 0, NULL, 0) = 12
recvfrom(4, “hello world!”, 100, 0, NULL, NULL) = 12
sendto(4, “hello world!”, 12, 0, NULL, 0) = 12
server.py在接收客戶端資料的時候,使用了 4 -> socket:[28725]
這個檔案描述符
再看client.py的核心呼叫
[root@localhost tmp]# strace -p 2555
strace: Process 2555 attached
select(0, NULL, NULL, NULL, {0, 996991}) = 0 (Timeout)
sendto(3, "hello world!", 12, 0, NULL, 0) = 12
recvfrom(3, "hello world!", 100, 0, NULL, NULL) = 12
select(0, NULL, NULL, NULL, {1, 0}) = 0 (Timeout)
sendto(3, "hello world!", 12, 0, NULL, 0) = 12
recvfrom(3, “hello world!”, 100, 0, NULL, NULL) = 12
sendto(3, “hello world!”, 12, 0, NULL, 0) = 12
client.py在與server.py通訊的時候使用了 3 -> socket:[28728]
結論:
● server.py與client.py連線建立成功之後,都會各自在自己的程式下開啟unix domain socket描述符,該描述符來指向對應的socket記憶體空間(下面簡稱s_mem
)
● client.py通過3 -> socket:[28728],找到s_mem
,然後寫入資料hello world!
● server.py通過4 -> socket:[28725]
,找到s_mem
,讀取資料hello world!
,並且原封不動的傳送這串資料給client.py
● client.py通過讀取s_mem
,獲取從server.py傳來的資料
● 迴圈往復
client.py server.py
+---------------+ +---------------+
|pid:2555 | |pid:2554 |
| +-----+ | | +-----+ |
| |fd:3 | | | |fd:4 | |
| +-----+ | | +-----+ |
+---------------+ +---------------+
| |
user space | |
+---------------------------------------------------------------------+
kernel space | |
| |
v v
+--------------+ +--------------+
|socket:[28728]| |socket:[28725]|
+------+-------+ +------+-------+
| |
| |
v v
+------------------------------------+
| socket |
+------------------------------------+
四、小結
● /tmp/server.sock作為建立unix domain socket連線的唯一識別符號
● unix domain socket連線建立完成之後在記憶體開闢一塊空間,而server與client在這塊記憶體空間中進行資料傳輸
● 在同一臺機器上的程式通訊,unix domain socket比tcp socket更快,因為它不需要網路協議棧,不需要打包拆包、計算校驗和、維護序號和應答等等過程
五、參考資料
https://en.wikipedia.org/wiki/Unix_domain_socket
至此,本文結束
在下才疏學淺,有撒湯漏水的,請各位不吝賜教…