UNIX域協議並非一個實際的協議族,而是在單個主機上執行客戶/服務器通訊的一種方法,所用API與在不一樣主機上執行客戶/服務器通訊所用的API(套接口API)相同。UNIX域協議可視爲進程間通訊(IPC)方法之一。html
UNIX域提供兩類套接口:字節流套接口(相似TCP)和數據報套接口(相似UDP)。編程
使用UNIX域套接口的理由有3個:數組
在源自Berkeley的實現中,UNIX域套接口每每比通訊兩端位於同一主機的TCP套接口快出一倍。安全
UNIX域套接口可用於在同一個主機上的不一樣進程間傳遞描述字。服務器
UNIX域套接口較新的實現把客戶的憑證(用戶ID和組ID)提供給服務器,從而可以提供額外的安全檢查措施。socket
UNIX域中用於標識客戶和服務器的協議地址是普通文件系統中的路徑名。這些路徑名不是普通的UNIX文件:除非把它們和UNIX域套接口關聯起來,不然沒法讀寫這些文件。函數
在頭文件<sys/un.h>中定義了UNIX域套接口地址結構:測試
struct sockaddr_un { sa_family_t sun_family; /* AF_LOCAL */ char sun_path[104]; /* null-terminated pathname */ };
存放在sun_path數組中的路徑名必須以空格字符結尾。spa
實現提供的SUN_LEN宏以一個指向sockaddr_un結構的指針爲參數並返回該結構的長度,其中包括路徑名中非空字節數。操作系統
未指定地址(通配地址),經過以空字符串做爲路徑名指示,也就是一個sun_path[0]值爲0的地址結構。這是UNIX域中與IPv4的INADDR_ANY常值以及IPv6的IN6ADDR_ANY_INIT常值等價的一個地址。
POSIX把UNIX域協議從新命名爲「本地IPC」,以消除它對於UNIX操做系統的依賴。歷史性的AF_UNIX常值變爲AF_LOCAL。儘管POSIX努力使它獨立於操做系統,它的套接口地址結構仍然保留_un後綴。
實例:UNIX域套接口的bind調用
建立一個UNIX域套接口,往其上bind一個路徑名,再調用getsockname輸出這個綁定的路徑名。
#include <sys/un.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> int main(int argc, char **argv) { int sockfd; socklen_t len; struct sockaddr_un addr1, addr2; if(argc != 2) { printf("usage: unixbind <pathname> "); exit(0); } sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); unlink(argv[1]); /* 若是文件系統中已存在該路徑名,bind將會失敗。爲此咱們先調用unlink刪除這個路徑名,以防止它已經存在。 */ bzero(&addr1, sizeof(addr1)); addr1.sun_family = AF_LOCAL; strncpy(addr1.sun_path, argv[1], sizeof(addr1.sun_path) - 1); bind(sockfd, (struct sockaddr *)&addr1, SUN_LEN(&addr1)); len = sizeof(addr2); getsockname(sockfd, (struct sockaddr *)&addr2, &len); printf("bound name = %s, returned len = %d\n", addr2.sun_path, len); exit(0); }
運行結果以下:
socketpair函數建立兩個隨後鏈接起來的套接口。本函數僅適用於UNIX域套接口。
#include <sys/socket.h> int socketpair(int family, int type, int protocol, int sockfd[2]); 返回:0——成功,-1——出錯
family參數必須爲AF_LOCAL;
protocol參數必須爲0;
type參數能夠是SOCK_STREAM,也能夠是SOCK_DGRAM。
新建立的兩個套接口描述字做爲sockfd[0]和sockfd[1]返回。
本函數相似於UNIX的pipe函數:返回兩個彼此鏈接的描述字。事實上,源自berkeley的實現經過執行與socketpair同樣的內部操做給出pipe接口。
這樣建立的兩個套接口未曾命名;也就是說其中沒有涉及隱式的bind調用。它與調用pipe建立的普通UNIX管道相似,差異在於流管道(socketpair建立的)是全雙工的,即兩個描述字都是既可讀又可寫。
POSIX不要求全雙工管道。
當用於UNIX域套接口時,套接口函數中存在一些差別和限制:
由bind建立的路徑名缺省訪問權限應爲0777(屬主用戶、組用戶和其餘用戶均可讀、可寫並可執行),並按照當前umask值進行修正。
與UNIX域套接口關聯的路徑名應該是一個絕對路徑名,而不是一個相對路徑名。
在connect調用中指定的路徑名必須是一個當前捆綁在某個打開的UNIX域套接口上的路徑名,並且它們的套接口類型(字節流或數據報)也必須一致。
調用connect鏈接一個UNIX域套接口涉及的權限測試等同於調用open以只讀方式訪問相應的路徑名。
UNIX域字節流套接口相似於TCP套接口:它們都爲進程提供一個無記錄邊界的字節流接口。
若是對於某個UNIX域字節流套接口的connect調用發現這個監聽套接口的隊列已滿,調用就當即返回一個ECONNREFUSED錯誤。這一點不一樣於TCP:若是TCP監聽套接口的隊列已滿,TCP監聽端就忽略新到達的SYN,而TCP鏈接發起端將數次發送SYN進行重試。
UNIX域數據報套接口相似UDP套接口:它們都提供一個保留記錄邊界的不可靠的數據報服務。
在一個未綁定的UNIX域套接口上發送數據報不會自動給這個套接口捆綁一個路徑名,這一點不一樣於UDP套接口:在一個未綁定的UDP套接口上發送UDP數據報致使給這個套接口捆綁一個臨時端口。這一點意味着除非數據報發送端已經捆綁一個路徑名到它的套接口,不然數據報接收端沒法發回應答數據報。相似地,對於某個UNIX域數據報套接口的connect調用不會給本套接口綁定一個路徑名,這一點不一樣於TCP和UDP。
/* unixstrserv01.c */ #include <sys/un.h> #include <errno.h> #include <sys/wait.h> #include <signal.h> #include <sys/socket.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <strings.h> #include <string.h> #define UNIXSTR_PATH "/tmp/unix.str" int main(int argc, char **argv) { int listenfd, connfd; pid_t childpid; socklen_t clilen; struct sockaddr_un cliaddr, servaddr; void sig_chld(int); daemonize("unixstrserver"); listenfd = socket(AF_LOCAL, SOCK_STREAM, 0); unlink(UNIXSTR_PATH); bzero(&servaddr, sizeof(servaddr)); servaddr.sun_family = AF_LOCAL; strcpy(servaddr.sun_path, UNIXSTR_PATH); bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); listen(listenfd, 5); signal(SIGCHLD, sig_chld); for(;;) { clilen = sizeof(cliaddr); if((connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen)) < 0) { if(errno == EINTR) continue; /* back to for() */ else { perror("accept"); exit(1); } } if((childpid = fork()) == 0) { close(listenfd); str_echo(connfd); exit(0); } close(connfd); } } void sig_chld(int signo) { pid_t pid; int stat; while((pid = waitpid(-1, &stat, WNOHANG)) > 0) { printf("child %d terminated\n", pid); } return; }
/* unixstrcli01.c */ #include <sys/un.h> #include <strings.h> #include <sys/un.h> #include <sys/socket.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #define UNIXSTR_PATH "/tmp/unix.str" int main(int argc, char **argv) { int sockfd; struct sockaddr_un servaddr; sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sun_family = AF_LOCAL; strcpy(servaddr.sun_path, UNIXSTR_PATH); connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); str_cli(stdin, sockfd); exit(0); }
其餘相關使用到的函數參見:http://www.cnblogs.com/nufangrensheng/p/3587962.html 以及http://www.cnblogs.com/nufangrensheng/p/3544104.html。
/* unixdgserv01.c */ #include <sys/un.h> #include <sys/socket.h> #define UNIXDG_PATH "/tmp/unix.dg" int main(int argc, char **argv) { int sockfd; struct sockaddr_un servaddr, cliaddr; sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0); unlink(UNIXDG_PATH); bzero(&servaddr, sizeof(servaddr)); servaddr.sun_family = AF_LOCAL; strcpy(servaddr.sun_path, UNIXDG_PATH); bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); dg_echo(sockfd, (struct sockaddr *)&cliaddr, sizeof(cliaddr)); }
/* unixdgcli01.c */ #include <sys/un.h> #include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <sys/types.h> #define UNIXDG_PATH "/tmp/unix.dg" int main(int argc, char **argv) { int sockfd; struct sockaddr_un cliaddr, servaddr; sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0); bzero(&cliaddr, sizeof(cliaddr)); cliaddr.sun_family = AF_LOCAL; strcpy(cliaddr.sun_path, tmpnam(NULL)); bind(sockfd, (struct sockaddr *)&cliaddr, sizeof(cliaddr)); bzero(&servaddr, sizeof(servaddr)); servaddr.sun_family = AF_LOCAL; strcpy(servaddr.sun_path, UNIXDG_PATH); dg_cli(stdin, sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); exit(0); }
使用到的相關函數可參考:http://www.cnblogs.com/nufangrensheng/p/3592158.html。
注意與UDP客戶不一樣的是,當使用UNIX域數據報協議時,咱們必須顯式bind一個路徑名到咱們的套接口,這樣服務器纔會有回送應答的路徑名。
當考慮從一個進程到另外一個進程傳遞打開的描述字時,咱們一般會想到:
(1)fork調用返回後,子進程共享父進程的全部打開的描述字。
(2)exec調用執行以後,全部描述字一般保持打開狀態不變。
在(1)中,進程先打開一個描述字,再調用fork,而後父進程關閉這個描述字,子進程則處理這個描述字。這樣一個打開的描述字就從父進程傳遞到子進程。然而咱們也可能想讓子進程打開一個描述字並把它傳遞給父進程。
當前的UNIX繫系統提供了用於從一個進程到任一其餘進程傳遞任一打開的描述字的方法。也就是說,這兩個進程之間無需存在親緣關係。這種技術要求首先在這兩個進程之間建立一個UNIX域套接口,而後使用sendmsg跨這個UNIX域套接口發送一個特殊消息。這個消息由內核處理,從而把打開的描述字從發送進程傳遞到接收進程。使用UNIX域套接口的描述字傳遞方法是最便於移植的編程技術。
在兩個進程之間傳遞描述字涉及的步驟以下:
(1)建立一個字節流或數據報的UNIX域套接口。
若是目標是fork一個子進程,讓子進程打開待傳遞的描述字,再把它傳遞迴父進程,那麼父進程能夠預先調用socketpair建立一個可用於在父子進程之間交換描述字的流管道。
若是進程之間沒有親緣關係,那麼服務器進程必須建立一個UNIX域字節流套接口,bind一個路徑到該套接口,以容許客戶進程connect到該套接口。客戶而後能夠向服務器發送一個打開某個描述字的請求,服務器再把該描述字經過UNIX域套接口傳遞迴客戶。客戶和服務器之間也可使用UNIX域數據報套接口,不過這麼作缺少優點,並且數據報存在被丟棄的可能性。
(2)發送進程經過調用返回描述字的任一UNIX函數打開一個描述字,這些函數的例子有:open、pipe、mkfifo、socket和accept。能夠在進程之間傳遞的描述字不限類型,這就是咱們稱這種技術爲「描述字傳遞」而不是「文件描述字傳遞」的緣由。
(3)發送進程建立一個msghdr結構(http://www.cnblogs.com/nufangrensheng/p/3567376.html),其中含有待傳遞的描述字。POSIX規定描述字做爲輔助數據(msghdr結構的msg_control成員)發送。發送進程調用sendmsg跨來自步驟(1)的UNIX域套接口發送該描述字。至此咱們說這個描述字「在飛行中(in flight)」。即便發送進程在調用sendmsg以後但在接收進程調用recvmsg以前就關閉了該描述字,對於接收進程它仍然保持打開狀態。發送一個描述字致使該描述字的引用計數加1.
(4)接收進程調用recvmsg在來自步驟(1)的UNIX域套接口上接收這個描述字。這個描述字在接收進程中的描述字號不一樣於它在發送進程中的描述子號是正常的。傳遞一個描述字並非傳遞一個描述字號,而是涉及在接收進程中建立一個新的描述字,而這個描述字指引的內核中文件表項和發送進程中飛行前的那個描述字指引的相同。
客戶和服務器之間必須存在某種應用協議,以便描述字的接收進程預先知道什麼時候期待接收。另外,在期待接收描述字的recvmsg調用中應該避免使用MSG_PEEK標誌,不然後果不可預料。