若是咱們的目的僅是在同一臺主機上的不一樣進程之間進行通訊,那麼除了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
前面提到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域協議的使用方式與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函數能夠建立兩個鏈接起來的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時,套接字函數中存在一些差別和限制,具體列舉以下:
umask和chmod中的權限配合使用,是權限的「補碼」。好比在個人電腦上umask的值是022,因此uds建立出來的路徑權限爲777-022=755,表示全部者可讀可寫可執行,組用戶和其餘用戶可讀可寫。而一般新建立的目錄默認的權限爲0777,新建立的文件默認的權限爲0666.
POSIX聲稱使用相對路徑綁定到uds將致使不可預計的結果
當咱們須要在本機通訊時,可使用uds來代替本地迴環接口。uds相比TCP/UDP套接字性能會更好,由於它不須要通過網絡協議棧,省去了各類解析和應答等步驟,而是直接在內核拷貝傳遞數據。好比最近很熱的service mesh,業務進程和sidecar就能夠經過uds來通訊。
當咱們須要傳遞描述符時,一般可使用方法有:
第一種方式裏,咱們能夠把描述符從父進程傳遞到子進程,然而咱們也可能須要在子進程傳遞描述符到父進程。unix系統提供了用於從一個進程向其餘任意進程傳遞描述符的方式,而這兩個進程不須要有任何親緣關係。這種技術要求在兩個進程之間建立一個uds,而後使用sendmsg經過這個uds發送特殊結構的消息。這個特殊的消息會由內核處理,把打開的描述符從發送進程傳遞到接收進程。
經過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是客戶端和服務端在同一臺主機上的IPC方法之一,與其餘IPC方法(pipe,共享內存等)相比,uds的優點在於其使用的API幾乎等同於網絡通信中使用的API,與客戶端和服務端在同一臺主機上的TCP相比,unix域字節流套接字的性能要更優。
此外,uds還支持傳遞其餘輔助數據,好比描述符和用戶憑證。
java中並不支持直接使用uds,多是由於java標榜跨平臺,而uds則只在部分操做系統中才能使用。要在java中使用uds,一般須要使用第三方提供的類庫,好比著名的網絡通信組件netty就提供了uds通信的支持。