Unix域協議

簡介

若是咱們的目的僅是在同一臺主機上的不一樣進程之間進行通訊,那麼除了TCP/UDP套接字之外咱們還可使用Unix域協議。Unix域協議是IPC(進程間通訊)的方式之一,Unix域協議使用套接字API,支持同一臺主機的不一樣進程之間進行通訊。直觀上來講Unix域協議有點相似使用本地迴環接口(lo)的TCP/UDP。可是Unix域協議比起TCP/UDP套接字還有幾個其餘優點:1.比起TCP協議一般要更快;2.支持在同一臺主機上的不一樣進程之間傳遞描述符;3.支持傳遞客戶端憑證。java

使用Unix域協議的套接字(如下簡稱uds[unix domain socket])用到的API與TCP/UDP套接字API徹底一致,即服務端須要進行bind、listen、accpet等操做才能讀寫,客戶端須要先connect才能進行讀寫。與TCP/UDP套接字不一樣的一點是uds綁定的地址是一個文件系統的絕對路徑,好比"/tmp/myuds",而TCP/UDP套接字使用的地址則包含了地址和端口號。uds使用的路徑並非普通的文件,須要和uds關聯才能對其進行讀寫。Unix域套接字有兩種類型,字節流套接字(相似TCP)和數據報套接字(相似UDP)。linux

相關API

unix域協議的地址結構

前面提到uds並不使用地址加端口號做爲協議地址,而是用一個文件路徑來做爲地址,因此uds使用的地址結構也有一點不一樣:macos

struct sockaddr_un {
    sa_family_t     sun_family;     /* 協議族,一般爲AF_UNIX或者AF_LOCAL */
    char            sun_path[104];  /* 地址路徑 */
};
複製代碼

uds使用的地址結構叫作sockaddr_un,後面的un即unix,而TCP/UDP套接字使用的地址結構叫作sockaddr_in,in表示internet。bash

unix域協議雖然名字裏有unix,但它是POSIX的一部分,並不與unix系統強綁定,POSIX將unix域協議從新命名爲「本地IPC」,把AF_UNIX改成了AF_LOCAL,但更多的時候咱們仍是稱其爲unix域協議,咱們常見的linux和macos都支持unix域協議。網絡

unix域協議配合套接字API

unix域協議的使用方式與TCP/UDP套接字的方式相似,只須要將協議族替換爲AF_LOCAL(或者AF_UNIX),而後將地址替換爲sockaddr_un便可。下面是一個使用uds進行bind,而後經過getsockname獲取套接字名稱並打印的例子:數據結構

#include "unp.h"
int main(int argc, char **argv) {
    int                 sockfd;
    socklen_t           len;
    struct sockaddr_un addr1, addr2;

    if (argc != 2)
        err_quit("usage: unixbind <pathname>");

    //先調用socket建立套接字
    sockfd = Socket(AF_LOCAL, SOCK_STREAM, 0);
    //對已存在的路徑進行bind會致使失敗,因此預先調用unlink刪除文件
    unlink(argv[1]);
    //調用bzero初始化地址結構體
    bzero(&addr1, sizeof(addr1));
    //設置協議族爲AF_LOCAL,AF_UNIX也能夠
    addr1.sun_family = AF_LOCAL;
    //設置地址的文件路徑
    strncpy(addr1.sun_path, argv[1], sizeof(addr1.sun_path)-1);
    //調用bind,經過SUN_LEN計算bind所需的長度這個參數
    Bind(sockfd, (SA *) &addr1, SUN_LEN(&addr1));

    len = sizeof(addr2);
    //得到socket的名字
    Getsockname(sockfd, (SA *) &addr2, &len);
    printf("bound name = %s, returned len = %d\n", addr2.sun_path, len);
    exit(0);
}
複製代碼

執行上面的程序,咱們就能夠看到控制檯會有相似這樣的輸出:dom

bound name = xxx, returned len = yy
複製代碼

程序會輸出咱們綁定的路徑以及對應的socket長度,這時候也能夠看到對應路徑也自動建立了同名的文件。若是用ls -lF命令查看,能夠看到對應的文件類型爲socket。socket

socketpair

socketpair函數能夠建立兩個鏈接起來的unix域套接字:ide

#include <sys/socket.h>
int socketpair(int family, int type, int protocol, int sockfd[2]);
複製代碼

socketpair的參數中family必須爲AF_LOCAL,protocol必須爲0,type能夠爲SOCK_STREAM或者SOCK_DGRAM,新建立的兩個套接字描述符將做爲sockfd[0]和sockfd[1]返回。函數

套接字函數

使用uds時,套接字函數中存在一些差別和限制,具體列舉以下:

  • 有bind建立的路徑名默認的權限爲0777(全部者、組用戶和其餘用戶均可讀、可寫、可執行),並按照當前umask進行修正。

umask和chmod中的權限配合使用,是權限的「補碼」。好比在個人電腦上umask的值是022,因此uds建立出來的路徑權限爲777-022=755,表示全部者可讀可寫可執行,組用戶和其餘用戶可讀可寫。而一般新建立的目錄默認的權限爲0777,新建立的文件默認的權限爲0666.

  • uds綁定的路徑應使用絕對路徑。避免使用相對路徑的緣由是相對路徑的解析會依賴調用者的當前路徑。

POSIX聲稱使用相對路徑綁定到uds將致使不可預計的結果

  • 調用conenct時傳入的路徑必須是和一個已經打開的uds綁定的路徑。而且兩個套接字的type(數據報或者字節流)必須相同。出錯的條件有幾個:a)路徑已存在,但不是一個uds;b)路徑已存在且是一個uds,可是沒有與之關聯的打開的描述符;c)路徑已存在而且是一個打開的uds,可是類型不一樣。
  • 調用connect鏈接一個uds涉及的權限檢查等同於調用open以只讀模式訪問對應路徑。
  • unix域字節流套接字與TCP套接字相似,它們都提供無記錄邊界的字節流接口。
  • 若是對一個uds進行connect時發現監聽套接字的隊列已滿,調用會當即返回一個ECONNECTREFUSED錯誤;而TCP監聽套接字在隊列滿時則會忽略新到達的SYNC,進而鏈接發起端發起端進行重試。
  • unix域數據報套接字與UDP套接字相似,它們都提供保留記錄邊界的不可靠的數據報服務。
  • 在未綁定的uds上發送數據不會自動爲其綁定一個路徑,這一點不一樣於UDP套接字:在一個未綁定的UDP套接字上發送數據會爲其綁定一個臨時端口。這意味着除非數據報發送端已經綁定到一個路徑,不然數據報接收端沒法發回應答數據報。相似的,對於uds的connect調用不會爲其綁定一個路徑,這一點不一樣於TCP/UDP。

什麼場景下能夠選擇uds

本機通訊

當咱們須要在本機通訊時,可使用uds來代替本地迴環接口。uds相比TCP/UDP套接字性能會更好,由於它不須要通過網絡協議棧,省去了各類解析和應答等步驟,而是直接在內核拷貝傳遞數據。好比最近很熱的service mesh,業務進程和sidecar就能夠經過uds來通訊。

傳遞描述符

當咱們須要傳遞描述符時,一般可使用方法有:

  1. fork調用返回之後,子進程共享父進程的全部描述符
  2. exec調用執行後,全部的描述符一般保持打開狀態

第一種方式裏,咱們能夠把描述符從父進程傳遞到子進程,然而咱們也可能須要在子進程傳遞描述符到父進程。unix系統提供了用於從一個進程向其餘任意進程傳遞描述符的方式,而這兩個進程不須要有任何親緣關係。這種技術要求在兩個進程之間建立一個uds,而後使用sendmsg經過這個uds發送特殊結構的消息。這個特殊的消息會由內核處理,把打開的描述符從發送進程傳遞到接收進程。

經過uds傳遞描述符的步驟具體以下:

  1. 建立一個字節流或數據報的uds。這能夠經過調用socketpair而後父子進程之間的鏈接;也可使用套接字API。一般建議使用字節流套接字而不是數據報套接字,由於使用數據報套接字並無什麼好處,反而還存在數據報被丟棄的可能。
  2. 發送端打開描述符。uds能夠傳遞各類類型的描述符,而不是僅包括文件描述符。
  3. 發送端進程建立一個msghdr的結構,其中含有待傳遞的描述符,而後調用sendmsg將其發送出去。發送一個描述符會使其引用計數加一。
  4. 接收端進程調用recvmsg在建立的uds上接收描述符。這個過程會在接收進程建立一個新的描述符,而後將其指向和發送進程發送的描述符指向的同一個內核文件選項。因此接收端收到的描述符不一樣於發送端發送端描述符時很正常的。

msghdr的結構定義:

/* * [XSI] Message header for recvmsg and sendmsg calls. * Used value-result for recvmsg, value only for sendmsg. */
struct msghdr {
	void		*msg_name;	/* [XSI] optional address */
	socklen_t	msg_namelen;	/* [XSI] size of address */
	struct iovec *msg_iov;	/* [XSI] scatter/gather array */
	int		msg_iovlen;	/* [XSI] # elements in msg_iov */
	void		*msg_control;	/* [XSI] ancillary data, see below */
	socklen_t	msg_controllen;	/* [XSI] ancillary data buffer len */
	int		msg_flags;	/* [XSI] flags on received message */
};
複製代碼

具體的例子就暫時不列舉了。

驗證發送者的身份

能夠用uds傳遞的另外一種輔助數據就是用戶憑證。用戶憑證的數據結構在不一樣的操做系統中並不一致,這裏就再也不詳細介紹了。

uds的優點

uds是客戶端和服務端在同一臺主機上的IPC方法之一,與其餘IPC方法(pipe,共享內存等)相比,uds的優點在於其使用的API幾乎等同於網絡通信中使用的API,與客戶端和服務端在同一臺主機上的TCP相比,unix域字節流套接字的性能要更優。

此外,uds還支持傳遞其餘輔助數據,好比描述符和用戶憑證。

java中的uds

java中並不支持直接使用uds,多是由於java標榜跨平臺,而uds則只在部分操做系統中才能使用。要在java中使用uds,一般須要使用第三方提供的類庫,好比著名的網絡通信組件netty就提供了uds通信的支持。

相關文章
相關標籤/搜索