UNIX域套接字用於在同一臺機器上運行的進程之間的通訊。雖然因特網域套接字可用於同一目的,但UNIX域套接字的效率更高。UNIX域套接字僅僅複製數據;它們並不執行協議處理,不須要添加或刪除網絡報頭,無需計算檢驗和,不要產生順序號,無需發送確認報文。html
UNIX域套接字提供流和數據報兩種接口。UNIX域數據報服務是可靠的,既不會丟失消息也不會傳遞出錯。UNIX域套接字是套接字和管道之間的混合物。爲了建立一對非命名的、相互鏈接的UNIX域套接字,用戶可使用它們面向網絡的域套接字接口,也可以使用socketpair函數。node
#include <sys/socket.h> int socketpair(int domain, int type, int protocol, int sockfd[2]); 返回值:若成功則返回0,出錯則返回-1
雖然該接口具備足夠的通常性,socketpair可用於任意域,但操做系統一般僅對UNIX域提供支持。編程
實例:使用UNIX域套接字的s_pipe函數服務器
程序清單17-6 s_pipe函數的套接字版本(建立一對相鏈接的UNIX域流套接字)網絡
#include "apue.h" #include <sys/socket.h> /* * Return a full-duplex "stream" pipe (a UNIX domain socket) * with the two file descriptors returned in fd[0] and fd[1]. */ int s_pipe(int fd[2]) { return(socketpair(AF_UNIX, SOCK_STREAM, 0, fd)); }
某些基於BSD的系統使用UNIX域套接字實現管道。但當調用pipe時,第一描述符的寫端和第二描述符的讀端都被關閉。爲了獲得全雙工管道,咱們必須直接調用socketpair。dom
一、命名UNIX域套接字socket
雖然socketpair函數建立相互鏈接的一對套接字,可是每個套接字都沒有名字。這意味着無關進程不能使用它們。函數
在http://www.cnblogs.com/nufangrensheng/p/3565402.html,咱們學習瞭如何將一個地址綁定一因特網域套接字。恰如因特網域套接字同樣,咱們也能夠命名UNIX域套接字,並可將其用於告示服務。可是要注意的是,UNIX域套接字使用的地址格式不一樣於因特網域套接字。學習
套接字地址格式可能隨實現而變。UNIX域套接字的地址由sockaddr_un結構表示。在Linux 2.4.22和Solaris 9中,sockaddr_un結構按下列形式定義在頭文件<sys/un.h>中。ui
struct sockaddr_un { sa_family sun_family; /* AF_UNIX */ char sun_path[108]; /* pathname */ };
sockaddr_un結構的sun_path成員包含一路徑名。當咱們將以地址綁定至UNIX域套接字時,系統用該路徑名建立一類型爲S_IFSOCK的文件。
該文件僅用於向客戶進程告知套接字名字。該文件不能打開,也不能由應用程序用於通訊。
若是當咱們試圖綁定地址時,該文件已經存在,那麼bind請求失敗。當關閉套接字時,並不自動刪除該文件,因此咱們必須確保在應用程序終止前,對該文件執行解除連接操做。
實例
程序清單17-7 將一個地址綁定一UNIX域套接字
#include "apue.h" #include <sys/socket.h> #include <sys/un.h> int main(void) { int fd, size; struct sockaddr_un un; un.sun_family = AF_UNIX; strcpy(un.sun_path, "foo.socket"); if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) err_sys("socket failed"); size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path); if(bind(fd, (struct sockaddr *)&un, size) < 0) err_sys("bind failed"); printf("UNIX domain socket bound\n"); exit(0); }
當運行此程序時,bind請求成功執行,可是若是第二次運行該程序,則出錯返回,其緣由是該文件已經存在。在刪去該文件以前,程序清單17-7不會成功執行。
肯定綁定地址長度的方法是,先肯定sun_path成員在sockaddr_un結構中的偏移量,而後將此與路徑名長度(不包括終止null字符)相加。由於在sun_path以前的成員與實現相關,因此咱們使用<stddef.h>頭文件中的offsetof宏計算sun_path成員從結構開始處的偏移量。若是查看<stddef.h>,則可見到相似於下列形式的定義:
#define offsetof(TYPE, MEMBER) ((int)&((TYPE *)0)->MEMBER)
假定該結構從地址0開始,此表達式求得成員起始地址的整型值。
二、惟一鏈接
服務器進程可使用標準bind、listen和accept函數,爲客戶進程安排一個惟一的UNIX域鏈接(unique UNIX domain connection)。客戶進程使用connect與服務器進程聯繫;服務器進程接受了connect請求後,在服務器進程和客戶進程之間就存在了惟一鏈接。這種風格的操做與咱們在http://www.cnblogs.com/nufangrensheng/p/3567376.html中的程序清單16-4和程序清單16-5中所示的對因特網域套接字的操做相同。
程序清單17-8 UNIX域套接字的serv_listen函數
#include "apue.h" #include <sys/socket.h> #include <sys/un.h> #include <errno.h> #define QLEN 10 /* * Create a server endpoint of a connection. * Return fd if all ok, <0 on error. */ int serv_listen(const char *name) { int fd, len, err, rval; struct sockaddr_un un; /* create a UNIX domain stream socket */ if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) return(-1); unlink(name); /* in case it already exists */ /* fill in socket address structure */ memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; strcpy(un.sun_path, name); len = offsetof(struct sockaddr_un, sun_path) + strlen(name); /* bind the name to the descriptor */ if(bind(fd, (struct sockaddr *)&un, len) < 0) { rval = -2; goto errout; } if(listen(fd, QLEN) < 0) /* tell kernel we're a server */ { rval = -3; goto errout; } return(fd); errout: err = errno; close(fd); errno = err; return(rval); }
首先,咱們調用socket建立一個UNIX域套接字。而後將欲賦予套接字的衆所周知路徑名填入sockaddr_un結構。該結構是調用bind的參數。注意,咱們不須要設置某些平臺提供的sun_len字段,操做系統用傳送給bind函數的地址長度設置該字段。
最後調用listen函數以通知內核進程該進程將做爲服務器進程等待客戶進程的鏈接請求。當收到一個客戶進程的鏈接請求後,服務器進程調用serv_accept函數。
程序清單17-9 UNIX域套接字的serv_accept函數
#include "apue.h" #include <sys/socket.h> #include <sys/un.h> #include <time.h> #include <errno.h> #define STALE 30 /* client's name can't be older than this (sec) */ /* * Wait for a client connection to arrive, and accept it. * We also obtain the client's usr ID from the pathname * that it must bind before calling us. * Returns new fd if all ok, <0 on error */ int serv_accept(int listenfd, uid_t *uidptr) { int clifd, len, err, rval; time_t staletime; struct sockaddr_un un; struct stat statbuf; len = sizeof(un); if((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0) return(-1); /* often errno=EINTR, if signal caught */ /* obtain the client's uid from its calling address */ len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */ un.sun_path[len] = 0; /* null terminate */ if(stat(un.sun_path, &statbuf) < 0) { rval = -2; goto errout; } #ifdef S_ISSOCK /* not defined fro SVR4 */ if(S_ISSOCK(statbuf.st_mode) == 0) { rval = -3; /* not a socket */ goto errout; } #endif if((statbuf.st_mode & (S_IRWXG | S_IRWXO)) || (statbuf.st_mode & S_IRWXU) != S_IRWXU) { rval = -4; /* is not rwx------ */ goto errout; } staletime = time(NULL) - STALE; if(statbuf.st_atime < staletime || statbuf.st_ctime < staletime || statbuf.st_mtime < staletime) { rval = -5; /* i-node is too old */ goto errout; } if(uidptr != NULL) *uidptr = statbuf.st_uid; /* return uid of caller */ unlink(un.sun_path); /* we're done with pathname now */ return(clifd); errout: err = errno; close(clifd); errno = err; return(rval); }
服務器進程在調用serv_accept中阻塞以等待一客戶進程調用cli_conn。從accept返回時,返回值是鏈接到客戶進程的嶄新的描述符。另外,accept函數也經由其第二個參數(指向sockaddr_un結構的指針)返回客戶進程賦予其套接字的路徑名(包含客戶進程ID的名字)。接着,程序在此路徑名結尾處填補null字符,而後調用stat函數。這使咱們驗證該路徑名確實是一個套接字,其權限容許用戶-讀、用戶-寫及用戶-執行。咱們也驗證與套接字相關聯的3個時間不比當前時間早30秒。(time函數參考http://www.cnblogs.com/nufangrensheng/p/3507715.html)。
如若經過了全部這些檢驗,則可認爲客戶進程的身份(其有效用戶ID)是該套接字的全部者。
客戶進程調用cli_conn函數對聯向服務器進程的鏈接進行初始化。
程序清單17-10 用於UNIX域套接字的cli_conn函數
#include "apue.h" #include <sys/socket.h> #include <sys/un.h> #include <errno.h> #define CLI_PATH "/var/tmp/" /* +5 fro pid = 14 chars */ #define CLI_PERM S_IRWXU /* rwx for user only */ /* * Create a client endpoint and connect to a server. * Returns fd if all ok, <0 on error. */ int cli_conn(const char *name) { int fd, len, err, rval; struct sockaddr_un un; /* create a UNIX domain stream socket */ if((fd = socket(AF_UNIX, SOCK_STREM, 0)) < 0) return(-1); /* fill socket address structure with our address */ memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; sprintf(un.sun_path, "%s%05d", CLI_PATH, getpid()); len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path); unlink(un.sun_path); /* in case it already exits */ if(bind(fd, (struct sockaddr *)&un, len) < 0) { rval = -2; goto errout; } if(chmod(un.sun_path, CLI_PERM) < 0) { rval = -3; goto errout; } /* fill socket address structure with server's address */ memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; strcpy(un.sun_path, name); len = offsetof(struct sockaddr_un, sun_path) + strlen(name); if(connect(fd, (struct sockaddr *)&un, len) < 0) { rval = -4; goto errout; } return(fd); errout: err = errno; close(fd); errno = err; return(rval); }
咱們調用socket函數建立UNIX域套接字的客戶端進程,而後用客戶端進程專有的名字填入sockaddr_un結構。
咱們不讓系統爲咱們選擇一個默認的地址,緣由是這樣處理後,服務器進程不能區分各個客戶進程。因而,咱們綁定咱們本身的地址,在開發使用套接字的客戶端程序時一般並不採用這一步驟。
咱們綁定的路徑名的最後5個字符來自客戶進程ID。咱們調用unlink,以防該路徑名已經存在,而後,調用bind將名字賦予客戶進程套接字。這在文件系統中建立了一個套接字文件,所用的名字與被綁定的路徑名同樣。接着,調用chmod關閉除用戶-讀、用戶-寫以及用戶-執行之外的其餘權限。在serv_accept中,服務器進程檢驗這些權限以及套接字用戶ID以驗證客戶進程的身份。
而後,咱們必須填充另外一個sockaddr_un結構,此次用的是服務器進程衆所周知的路徑名。最後,調用connect函數初始化與服務器進程的鏈接。
本篇博文內容摘自《UNIX環境高級編程》(第二版),僅做我的學習記錄所用。關於本書可參考:http://www.apuebook.com/。