UNIX域協(xié)議并不是一個(gè)實(shí)際的協(xié)議族,而是在單個(gè)主機(jī)上執(zhí)行客戶/服務(wù)器通信的一種方法,所用API與在不同主機(jī)上執(zhí)行客戶/服務(wù)器通信所用的API(套接口API)相同。UNIX域協(xié)議可視為進(jìn)程間通信(ipC)方法之一。
UNIX域提供兩類(lèi)套接口:字節(jié)流套接口(類(lèi)似TCP)和數(shù)據(jù)報(bào)套接口(類(lèi)似UDP)。
使用UNIX域套接口的理由有3個(gè):
在源自Berkeley的實(shí)現(xiàn)中,UNIX域套接口往往比通信兩端位于同一主機(jī)的TCP套接口快出一倍。
UNIX域套接口可用于在同一個(gè)主機(jī)上的不同進(jìn)程間傳遞描述字。
UNIX域套接口較新的實(shí)現(xiàn)把客戶的憑證(用戶ID和組ID)提供給服務(wù)器,從而能夠提供額外的安全檢查措施。
UNIX域中用于標(biāo)識(shí)客戶和服務(wù)器的協(xié)議地址是普通文件系統(tǒng)中的路徑名。這些路徑名不是普通的UNIX文件:除非把它們和UNIX域套接口關(guān)聯(lián)起來(lái),否則無(wú)法讀寫(xiě)這些文件。
在頭文件<sys/un.h>中定義了UNIX域套接口地址結(jié)構(gòu):
struct sockaddr_un { sa_family_t sun_family; /* AF_LOCAL */ char sun_path[104]; /* null-terminated pathname */};
存放在sun_path數(shù)組中的路徑名必須以空格字符結(jié)尾。
實(shí)現(xiàn)提供的SUN_LEN宏以一個(gè)指向sockaddr_un結(jié)構(gòu)的指針為參數(shù)并返回該結(jié)構(gòu)的長(zhǎng)度,其中包括路徑名中非空字節(jié)數(shù)。
未指定地址(通配地址),通過(guò)以空字符串作為路徑名指示,也就是一個(gè)sun_path[0]值為0的地址結(jié)構(gòu)。這是UNIX域中與IPv4的INADDR_ANY常值以及IPv6的IN6ADDR_ANY_INIT常值等價(jià)的一個(gè)地址。
POSIX把UNIX域協(xié)議重新命名為“本地IPC”,以消除它對(duì)于UNIX操作系統(tǒng)的依賴(lài)。歷史性的AF_UNIX常值變?yōu)锳F_LOCAL。盡管POSIX努力使它獨(dú)立于操作系統(tǒng),它的套接口地址結(jié)構(gòu)仍然保留_un后綴。
實(shí)例:UNIX域套接口的bind調(diào)用
創(chuàng)建一個(gè)UNIX域套接口,往其上bind一個(gè)路徑名,再調(diào)用getsockname輸出這個(gè)綁定的路徑名。
#include <sys/un.h>#include <sys/socket.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/types.h>intmain(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]); /* 如果文件系統(tǒng)中已存在該路徑名,bind將會(huì)失敗。為此我們先調(diào)用unlink刪除這個(gè)路徑名,以防止它已經(jīng)存在。 */ 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);}
運(yùn)行結(jié)果如下:
socketpair函數(shù)創(chuàng)建兩個(gè)隨后連接起來(lái)的套接口。本函數(shù)僅適用于UNIX域套接口。
#include <sys/socket.h>int socketpair(int family, int type, int protocol, int sockfd[2]);返回:0——成功,-1——出錯(cuò)
family參數(shù)必須為AF_LOCAL;
protocol參數(shù)必須為0;
type參數(shù)可以是SOCK_STREAM,也可以是SOCK_DGRAM。
新創(chuàng)建的兩個(gè)套接口描述字作為sockfd[0]和sockfd[1]返回。
本函數(shù)類(lèi)似于UNIX的pipe函數(shù):返回兩個(gè)彼此連接的描述字。事實(shí)上,源自berkeley的實(shí)現(xiàn)通過(guò)執(zhí)行與socketpair一樣的內(nèi)部操作給出pipe接口。
這樣創(chuàng)建的兩個(gè)套接口不曾命名;也就是說(shuō)其中沒(méi)有涉及隱式的bind調(diào)用。它與調(diào)用pipe創(chuàng)建的普通UNIX管道類(lèi)似,差別在于流管道(socketpair創(chuàng)建的)是全雙工的,即兩個(gè)描述字都是既可讀又可寫(xiě)。
POSIX不要求全雙工管道。
當(dāng)用于UNIX域套接口時(shí),套接口函數(shù)中存在一些差異和限制:
由bind創(chuàng)建的路徑名缺省訪問(wèn)權(quán)限應(yīng)為0777(屬主用戶、組用戶和其他用戶都可讀、可寫(xiě)并可執(zhí)行),并按照當(dāng)前umask值進(jìn)行修正。
與UNIX域套接口關(guān)聯(lián)的路徑名應(yīng)該是一個(gè)絕對(duì)路徑名,而不是一個(gè)相對(duì)路徑名。
在connect調(diào)用中指定的路徑名必須是一個(gè)當(dāng)前捆綁在某個(gè)打開(kāi)的UNIX域套接口上的路徑名,而且它們的套接口類(lèi)型(字節(jié)流或數(shù)據(jù)報(bào))也必須一致。
調(diào)用connect連接一個(gè)UNIX域套接口涉及的權(quán)限測(cè)試等同于調(diào)用open以只讀方式訪問(wèn)相應(yīng)的路徑名。
UNIX域字節(jié)流套接口類(lèi)似于TCP套接口:它們都為進(jìn)程提供一個(gè)無(wú)記錄邊界的字節(jié)流接口。
如果對(duì)于某個(gè)UNIX域字節(jié)流套接口的connect調(diào)用發(fā)現(xiàn)這個(gè)監(jiān)聽(tīng)套接口的隊(duì)列已滿,調(diào)用就立即返回一個(gè)ECONNREFUSED錯(cuò)誤。這一點(diǎn)不同于TCP:如果TCP監(jiān)聽(tīng)套接口的隊(duì)列已滿,TCP監(jiān)聽(tīng)端就忽略新到達(dá)的SYN,而TCP連接發(fā)起端將數(shù)次發(fā)送SYN進(jìn)行重試。
UNIX域數(shù)據(jù)報(bào)套接口類(lèi)似UDP套接口:它們都提供一個(gè)保留記錄邊界的不可靠的數(shù)據(jù)報(bào)服務(wù)。
在一個(gè)未綁定的UNIX域套接口上發(fā)送數(shù)據(jù)報(bào)不會(huì)自動(dòng)給這個(gè)套接口捆綁一個(gè)路徑名,這一點(diǎn)不同于UDP套接口:在一個(gè)未綁定的UDP套接口上發(fā)送UDP數(shù)據(jù)報(bào)導(dǎo)致給這個(gè)套接口捆綁一個(gè)臨時(shí)端口。這一點(diǎn)意味著除非數(shù)據(jù)報(bào)發(fā)送端已經(jīng)捆綁一個(gè)路徑名到它的套接口,否則數(shù)據(jù)報(bào)接收端無(wú)法發(fā)回應(yīng)答數(shù)據(jù)報(bào)。類(lèi)似地,對(duì)于某個(gè)UNIX域數(shù)據(jù)報(bào)套接口的connect調(diào)用不會(huì)給本套接口綁定一個(gè)路徑名,這一點(diǎn)不同于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"intmain(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); }}voidsig_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"intmain(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);}
其他相關(guān)使用到的函數(shù)參見(jiàn):http://www.CUOXin.com/nufangrensheng/p/3587962.html 以及http://www.CUOXin.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"intmain(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);}
使用到的相關(guān)函數(shù)可參考:http://www.CUOXin.com/nufangrensheng/p/3592158.html。
注意
與UDP客戶不同的是,當(dāng)使用UNIX域數(shù)據(jù)報(bào)協(xié)議時(shí),我們必須顯式bind一個(gè)路徑名到我們的套接口,這樣服務(wù)器才會(huì)有回送應(yīng)答的路徑名。
當(dāng)考慮從一個(gè)進(jìn)程到另一個(gè)進(jìn)程傳遞打開(kāi)的描述字時(shí),我們通常會(huì)想到:
(1)fork調(diào)用返回后,子進(jìn)程共享父進(jìn)程的所有打開(kāi)的描述字。
(2)exec調(diào)用執(zhí)行之后,所有描述字通常保持打開(kāi)狀態(tài)不變。
在(1)中,進(jìn)程先打開(kāi)一個(gè)描述字,再調(diào)用fork,然后父進(jìn)程關(guān)閉這個(gè)描述字,子進(jìn)程則處理這個(gè)描述字。這樣一個(gè)打開(kāi)的描述字就從父進(jìn)程傳遞到子進(jìn)程。然而我們也可能想讓子進(jìn)程打開(kāi)一個(gè)描述字并把它傳遞給父進(jìn)程。
當(dāng)前的UNIX系系統(tǒng)提供了用于從一個(gè)進(jìn)程到任一其他進(jìn)程傳遞任一打開(kāi)的描述字的方法。也就是說(shuō),這兩個(gè)進(jìn)程之間無(wú)需存在親緣關(guān)系。這種技術(shù)要求首先在這兩個(gè)進(jìn)程之間創(chuàng)建一個(gè)UNIX域套接口,然后使用sendmsg跨這個(gè)UNIX域套接口發(fā)送一個(gè)特殊消息。這個(gè)消息由內(nèi)核處理,從而把打開(kāi)的描述字從發(fā)送進(jìn)程傳遞到接收進(jìn)程。使用UNIX域套接口的描述字傳遞方法是最便于移植的編程技術(shù)。
在兩個(gè)進(jìn)程之間傳遞描述字涉及的步驟如下:
(1)創(chuàng)建一個(gè)字節(jié)流或數(shù)據(jù)報(bào)的UNIX域套接口。
如果目標(biāo)是fork一個(gè)子進(jìn)程,讓子進(jìn)程打開(kāi)待傳遞的描述字,再把它傳遞回父進(jìn)程,那么父進(jìn)程可以預(yù)先調(diào)用socketpair創(chuàng)建一個(gè)可用于在父子進(jìn)程之間交換描述字的流管道。
如果進(jìn)程之間沒(méi)有親緣關(guān)系,那么服務(wù)器進(jìn)程必須創(chuàng)建一個(gè)UNIX域字節(jié)流套接口,bind一個(gè)路徑到該套接口,以允許客戶進(jìn)程connect到該套接口??蛻羧缓罂梢韵蚍?wù)器發(fā)送一個(gè)打開(kāi)某個(gè)描述字的請(qǐng)求,服務(wù)器再把該描述字通過(guò)UNIX域套接口傳遞回客戶。客戶和服務(wù)器之間也可以使用UNIX域數(shù)據(jù)報(bào)套接口,不過(guò)這么做缺乏優(yōu)勢(shì),而且數(shù)據(jù)報(bào)存在被丟棄的可能性。
(2)發(fā)送進(jìn)程通過(guò)調(diào)用返回描述字的任一UNIX函數(shù)打開(kāi)一個(gè)描述字,這些函數(shù)的例子有:open、pipe、mkfifo、socket和accept。可以在進(jìn)程之間傳遞的描述字不限類(lèi)型,這就是我們稱(chēng)這種技術(shù)為“描述字傳遞”而不是“文件描述字傳遞”的原因。
(3)發(fā)送進(jìn)程創(chuàng)建一個(gè)msghdr結(jié)構(gòu)(http://www.CUOXin.com/nufangrensheng/p/3567376.html),其中含有待傳遞的描述字。POSIX規(guī)定描述字作為輔助數(shù)據(jù)(msghdr結(jié)構(gòu)的msg_control成員)發(fā)送。發(fā)送進(jìn)程調(diào)用sendmsg跨來(lái)自步驟(1)的UNIX域套接口發(fā)送該描述字。至此我們說(shuō)這個(gè)描述字“在飛行中(in flight)”。即使發(fā)送進(jìn)程在調(diào)用sendmsg之后但在接收進(jìn)程調(diào)用recvmsg之前就關(guān)閉了該描述字,對(duì)于接收進(jìn)程它仍然保持打開(kāi)狀態(tài)。發(fā)送一個(gè)描述字導(dǎo)致該描述字的引用計(jì)數(shù)加1.
(4)接收進(jìn)程調(diào)用recvmsg在來(lái)自步驟(1)的UNIX域套接口上接收這個(gè)描述字。這個(gè)描述字在接收進(jìn)程中的描述字號(hào)不同于它在發(fā)送進(jìn)程中的描述子號(hào)是正常的。傳遞一個(gè)描述字并不是傳遞一個(gè)描述字號(hào),而是涉及在接收進(jìn)程中創(chuàng)建一個(gè)新的描述字,而這個(gè)描述字指引的內(nèi)核中文件表項(xiàng)和發(fā)送進(jìn)程中飛行前的那個(gè)描述字指引的相同。
客戶和服務(wù)器之間必須存在某種應(yīng)用協(xié)議,以便描述字的接收進(jìn)程預(yù)先知道何時(shí)期待接收。另外,在期待接收描述字的recvmsg調(diào)用中應(yīng)該避免使用MSG_PEEK標(biāo)志,否則后果不可預(yù)料。
新聞熱點(diǎn)
疑難解答
圖片精選