UNIX域套接字(UDS) 原理及比較

什麼是UDS

Unix domain socket 又叫 IPC(inter-process communication 進程間通信) socket,用於實現同一主機上的進程間通信。socket 原本是為網絡通訊設計的,但後來在 socket 的框架上發展出一種 IPC 機制,就是 UNIX domain socket。雖然網絡 socket 也可用於同一臺主機的進程間通訊(通過 loopback 地址 127.0.0.1),但是 UNIX domain socket 用於 IPC 更有效率:不需要經過網絡協議棧,不需要打包拆包、計算校驗和、維護序號和應答等,只是將應用層數據從一個進程拷貝到另一個進程。這是因為,IPC 機制本質上是可靠的通訊,而網絡協議是為不可靠的通訊設計的。

UNIX域套接字(UDS) 原理及比較

UNIX domain socket 是全雙工的,API 接口語義豐富,相比其它 IPC 機制有明顯的優越性,目前已成為使用最廣泛的 IPC 機制,比如 X Window 服務器和 GUI 程序之間就是通過 UNIX domain socket 通訊的。

IP socket

IP socket要利用主機的傳輸層(tcp),可以用於同一臺主機上不同進程間的通信,也可以用於網絡上不同主機間的通信。

Unix domain socket vs IP socket

先來看一個使用案例,配置php-fpm與Nginx交互的socket:


fastcgi_pass 127.0.0.1:9000
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock

這個案例中,運行在同一臺機器上的php和Nginx需要通信,有2種實現方式:第一種是ip socket,通過本機迴環地址127.0.0.1加端口實現;第二種是通過unix domain socket實現。哪一種效率更高呢?

基於localhost的ip socket需要實現跨網絡主機通訊的全部環節,包括建立socket連接,ACk開銷,tcp流控,封裝/解封,路由。在這個過程中還會有2個context switch,因為使用網絡層傳輸數據需要調用system call,而調用system call會產生中斷,導致context switch的;另外一個進程接受到來自網絡層的連接請求,也會產生系統中斷,導致context switch。以上過程導致2個context switch的開銷,外加其它各種開銷(overhead)。

unix域的數據報服務是否可靠

man unix 手冊即可看到,unix domain socket 的數據報既不會丟失也不會亂序 (據我所知,在Linux下的確是這樣)。不過最新版本的內核,仍然又提供了一個保證次序的類型 “ kernel 2.6.4 SOCK_SEQPACKET ”。

STREAM 和 DGRAM 的主要區別

既然數據報不丟失也可靠,那不是和 STREAM 很類似麼?我理解也確實是這樣,而且我覺得 DGRAM 相對還要好一些,因為發送的數據可以帶邊界。二者另外的區別在於收發時的數據量不一樣,基於 STREAM 的套接字,send 可以傳入超過 SO_SNDBUF 長的數據,recv 時同 TCP 類似會存在數據粘連。

採用阻塞方式使用API,在unix domain socket 下調用 sendto 時,如果緩衝隊列已滿,會阻塞。而UDP因為不是可靠的,無法感知對端的情況,即使對端沒有及時收取數據,基本上sendto都能立即返回成功(如果發端瘋狂sendto就另當別論,因為過快地調用sendto在慢速網絡的環境下,可能撐爆套接字的緩衝區,導致sendto阻塞)。

SO_SNDBUF 和 SO_REVBUF

對於 unix domain socket,設置 SO_SNDBUF 會影響 sendto 最大的報文長度,但是任何針對 SO_RCVBUF 的設置都是無效的 。實際上 unix domain socket 的數據報還是得將數據放入內核所申請的內存塊裡面,再由另一個進程通過 recvfrom 從內核讀取,因此具體可以發送的數據報長度受限於內核的 slab 策略 。在 linux 平臺下,早先版本(如 2.6.2)可發送最大數據報長度約為 128 k ,新版本的內核支持更大的長度。

使用 DGRAM 時,緩衝隊列的長度

有幾個因素會影響緩衝隊列的長度,一個是上面提到的 slab 策略,另一個則是系統的內核參數 /proc/sys/net/unix/max_dgram_qlen。緩衝隊列長度是這二者共同決定的。

如 max_dgram_qlen 默認為 10,在數據報較小時(如1k),先掛起接收數據的進程後,仍可以 sendto 10 次並順利返回;

但是如果數據報較大(如120k)時,就要看 slab “size-131072” 的 limit 了。

使用 unix domain socket 進行進程間通信 vs 其他方式

· 需要先確定

操作系統類型,以及其所對應的最大 DGRAM 長度,如果有需要傳送超過該長度的數據報,建議拆分成幾個發送,接收後組裝即可(不會亂序,個人覺得這樣做比用 STREAM 再切包方便得多)

· 同管道相比,unix 域的數據報不但可以維持數據的邊界,還不會碰到在寫入管道時的原子性問題。

· 同共享內存相比,不能獨立於進程緩存大量數據,但是卻避免了同步互斥的考量。

· 同普通 socket 相比,開銷相對較小(不用計算報頭),DGRAM 的報文長度可以大於 64k,不過不能像普通 socket 那樣將進程切換到不同機器 。

UNIX域套接字使用文件系統作為地址名稱空間。這意味著您可以使用UNIX文件權限來控制通信訪問和他們在一起。即則可以限制其他進程可以連接到守護進程——可能一個用戶可以,但是web服務器不能,或者類似的情況。有了IP套接字,連接守護進程的能力就暴露出來了目前的系統,所以可能需要採取額外的步驟安全。另一方面,網絡透明。與UNIX域套接字,實際上可以檢索進程的憑據創建了遠程套接字,並將其用於訪問控制,這在多用戶系統上非常方便。-本地主機上的IP套接字基本上是在迴環網絡IP。故意“沒有特殊知識”的事實是連接到相同的系統,所以不做任何努力來繞過基於性能原因的常規IP堆棧機制。例如,TCP上的傳輸總是涉及兩個上下文切換遠程插座,因為你必須通過netisr切換髮生在通過合成的包的“環回”之後環回接口。同樣地,你會得到所有的ack開銷,TCP流量控制,封裝/封裝等路由執行,以決定包是否發送到本地主機。大的發送必須被分解成mtu大小的數據報還增加了大型寫操作的開銷。它實際上是TCP,它只是去一個環回接口,通過一個特殊的地址,或發現請求的地址在本地提供,而不是通過以太網提供(等)。- UNIX域套接字有明確的知識,他們正在執行同樣的系統。它們避免了額外的上下文切換和發送線程將直接寫入流或數據進入接收套接字緩衝區。沒有計算校驗和,沒有插入標頭,不執行路由,等等,因為它們有訪問遠程socket緩衝區,也可以直接提供在填寫時反饋給發件人,或者更重要的是,清空,而不是增加顯式的開銷確認和窗口更改。一個功能UNIX域套接字不提供TCP提供的是帶外數據。在實踐,這是一個幾乎無人關注的問題。一般來說,在TCP上實現的理由是它給你位置獨立性和即時可移植性——您可以移動客戶機或者這個守護進程,更新一個地址,它就會“正常工作”。套接字層提供了通信服務的合理抽象,所以編寫一個連接/綁定的應用程序並不難部分了解TCP和UNIX域套接字,其餘的只是使用它給出的套接字。所以如果你想在局部尋找性能,我認為UNIX域套接字可能最能滿足您的需要。許多人因為性能通常不那麼重要,所以無論如何都要編碼到TCP嗎網絡可移植性的好處是巨大的。現在,UNIX域套接字代碼被一個子系統鎖覆蓋;我有一個版本使用了更細粒度的鎖定,但是還沒有評估這些更改對性能的影響。你跑進來了在一個有四個處理器的SMP環境中,這些變化是可能的可能會對性能產生積極的影響,所以如果您喜歡這些補丁。現在他們在我的時間表上開始測試,但不是在包含在FreeBSD 5.4中的路徑。更大的主要好處粒度應該是如果您有許多對線程/進程使用UNIX域套接字在處理器之間進行通信在UNIX域套接字子系統鎖上存在大量爭用。補丁不會增加正常的發送/接收操作的成本,但是在listen/accept/connect/bind路徑中添加額外的互斥操作。

API操作

函數介紹

開始創建socket

 int socket(int domain, int type, int protocol)

domain(域) : AF_UNIX

type : SOCK_STREAM/ SOCK_DGRAM :

protocol : 0

SOCK_STREAM(流) : 提供有序,可靠的雙向連接字節流。 可以支持帶外數據傳輸機制,

無論多大的數據都不會截斷

SOCK_DGRAM(數據報):支持數據報(固定最大長度的無連接,不可靠的消息),數據報超過最大長度,會被截斷.

獲取到socket文件描述符之後,還要將其綁定一個文件上

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd : 傳入sock的文件描述符

addr : 用sockaddr_un表示

addrlen : 結構體長度

struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
};

監聽客戶端的連接

int listen(int sockfd, int backlog);

sockfd : 文件描述符

backlog : 連接隊列的長度

接受客戶端的連接

int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);

UDS不存在客戶端地址的問題,因此這裡的addr和addrlen參數可以設置為NULL


分享到:


相關文章: