一过吻、知識(shí)準(zhǔn)備
1、在linux中蔗衡,一切皆為文件纤虽,所有不同種類的類型都被抽象成文件(比如:塊設(shè)備,socket套接字绞惦,pipe隊(duì)列)
2逼纸、操作這些不同的類型就像操作文件一樣,比如增刪改查等
3济蝉、主要用于:運(yùn)行在同一臺(tái)機(jī)器上的2個(gè)進(jìn)程相互之間的數(shù)據(jù)通信
4杰刽、它們和網(wǎng)絡(luò)文件描述符非常相似(比如:TCP socket)菠发,他們的通信發(fā)生在操作系統(tǒng)內(nèi)核
二、環(huán)境準(zhǔn)備
組件 | 版本 |
---|---|
OS | CentOS Linux release 7.5.1804 |
三贺嫂、Unix domain socket 文件描述符
先準(zhǔn)備2個(gè)腳本:
server.py主要用于建立客戶端的連接請(qǐng)求滓鸠,并且接收客戶端傳來的數(shù)據(jù),然后將收到的數(shù)據(jù)回傳給客戶端
client.py每隔1秒向服務(wù)端發(fā)送一次'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的狀態(tài):
[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
進(jìn)程2554創(chuàng)建了打開了unix domain socket描述符(3 -> socket:[19803]
)涝婉,并且通過該描述符哥力,打開了/tmp/server.sock文件,其主要作用是用于監(jiān)聽
我們運(yùn)行client.py并觀察狀態(tài)
[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也創(chuàng)建了unix domain socket描述符3 -> socket:[28728]
吩跋,通過socket:[18974]
,找到一條socket
查看server.py發(fā)生的變化:
[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新增了一個(gè)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
到目前為止锌钮,整個(gè)unix domain socket的通信過程已經(jīng)比較清晰的展現(xiàn)了:
● server.py啟動(dòng)之后,打開監(jiān)聽的描述符引矩,等待來自客戶端的連接請(qǐng)求
● client.py啟動(dòng)之后梁丘,與server連接成功,打開一個(gè)描述符用于與server.py通信
● server.py會(huì)再打開一個(gè)描述符用于與client.py進(jìn)行數(shù)據(jù)通信
但是目前還有2個(gè)問題:
(1)/tmp/server.sock到底作用是什么
(2)server與client是怎么進(jìn)行數(shù)據(jù)通信的
問題(1)
● /tmp/server.sock是操作系統(tǒng)的實(shí)體文件旺韭,擁有一個(gè)全局的文件系統(tǒng)描述符氛谜,這個(gè)描述符在操作系統(tǒng)中是唯一的
● server.py啟動(dòng)時(shí)打開了server.sock,就聲名了與server.py建立連接就只能通過server.sock文件
● 這就相當(dāng)于TCP socket中四元組中的兩元(server_ip:server_port
)
問題(2)
我們來使用strace命令看看server.py的內(nèi)核調(diào)用
[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(<font color=#de171c>4</font>, "hello world!", 100, 0, NULL, NULL) = 12
sendto(<font color=#de171c>4</font>, "hello world!", 12, 0, NULL, 0) = 12
server.py在接收客戶端數(shù)據(jù)的時(shí)候区端,使用了 4 -> socket:[28725]
這個(gè)文件描述符
再看client.py的內(nèi)核調(diào)用
[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(<font color=#de171c>3</font>, "hello world!", 100, 0, NULL, NULL) = 12
sendto(<font color=#de171c>3</font>, "hello world!", 12, 0, NULL, 0) = 12
client.py在與server.py通信的時(shí)候使用了 3 -> socket:[28728]
結(jié)論:
● server.py與client.py連接建立成功之后值漫,都會(huì)各自在自己的進(jìn)程下打開unix domain socket描述符,該描述符來指向?qū)?yīng)的socket內(nèi)存空間(下面簡(jiǎn)稱s_mem
)
● client.py通過3 -> socket:[28728]织盼,找到s_mem
杨何,然后寫入數(shù)據(jù)hello world!
● server.py通過4 -> socket:[28725]
,找到s_mem
沥邻,讀取數(shù)據(jù)hello world!
危虱,并且原封不動(dòng)的發(fā)送這串?dāng)?shù)據(jù)給client.py
● client.py通過讀取s_mem
,獲取從server.py傳來的數(shù)據(jù)
● 循環(huán)往復(fù)
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 |
+------------------------------------+
四唐全、小結(jié)
● /tmp/server.sock作為建立unix domain socket連接的唯一標(biāo)識(shí)符
● unix domain socket連接建立完成之后在內(nèi)存開辟一塊空間埃跷,而server與client在這塊內(nèi)存空間中進(jìn)行數(shù)據(jù)傳輸
● 在同一臺(tái)機(jī)器上的進(jìn)程通信,unix domain socket比tcp socket更快邮利,因?yàn)樗恍枰W(wǎng)絡(luò)協(xié)議棧弥雹,不需要打包拆包、計(jì)算校驗(yàn)和近弟、維護(hù)序號(hào)和應(yīng)答等等過程
五缅糟、參考資料
https://en.wikipedia.org/wiki/Unix_domain_socket
至此挺智,本文結(jié)束
在下才疏學(xué)淺祷愉,有撒湯漏水的窗宦,請(qǐng)各位不吝賜教...