前面講述了各類進程間通訊和網絡IPC的內容,除此之外,還有一種很是經常使用的IPC——UNIX域套接字。這種套接字實際上就是一種文件,可以讓本機的進程之間互相通訊。node
Unix域套接字用於同一臺電腦上運行的進程通訊。雖然TCP/IP協議的套接字很方便,可是在某些狀況下須要保證更高的通訊效率,因此Unix域套接字更加適合,由於它是經過內核轉發的信息,而網絡協議通訊須要經過網卡發送,可能效率更低些。
雖然不經過網絡傳輸數據,可是Unix域套接字也有流和數據報兩種接口。Unix域套接字就像是套接字和管道的組合,在數據傳輸上很像管道,可是是以套接字的形式使用。數組
int socketpair(int domain, int type, int protocol, int socket_vector[2]);複製代碼
socketpair函數建立一對未命名的已鏈接的套接字在指定的域(domain)中,而且以指定的類型(type),可選指定協議(protocol),描述符將存放在socket_vector數組中。須要注意的是,這對套接字其實是全雙工的,在不少Unix實現中,全雙工的管道之類的實際上就是經過Unix域套接字實現的。網絡
前面的socketpair雖然很方便,可是它建立的是未命名的套接字,也就是說不一樣進程沒法使用,在前面的網絡套接字章節中講了套接字如何綁定一個地址和端口,可是咱們也能夠將其綁定到路徑上,使其成爲一個文件,這樣就能讓不一樣進程使用。dom
#include "include/apue.h"
#include <sys/socket.h>
#include <sys/un.h>
int main(int argc, char *argv[]) {
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();
}複製代碼
上面的例程很是簡單,首先是建立一個Unix域地址,使用strcpy函數將地址複製到地址族變量,而後使用socket函數建立一個套接字,而後計算路徑成員在結構體中的偏移量加上路徑自己的長度,求出size變量,而後使用bind函數將地址族結構體和套接字綁定在一塊兒。
當咱們運行程序完畢,就能在當前目錄下看到一個foo.socket
文件,可能就有人要問了,爲何程序結束這個文件仍然存在,套接字不是文件描述符嗎,不是在程序結束的時候就回收了嗎?實際上,這是搞混了文件描述符和文件的區別,文件描述符只是0、一、2之類的數字,用於指向文件表項,實際上文件的打開是由內核維護的。套接字也同樣,當咱們建立套接字的時候,而且將其綁定到具體路徑,內核就會幫助咱們建立一個S_IFSOCK
類型的文件,可是實際上這個文件並無什麼用,它不會用於實際的寫入,否則這不就是和普通的文件同樣了嗎,因此這個文件純粹就是個flag,用於標記地址,就跟一般使用的xxx.pid
文件這種形式相似。socket
這小節沒什麼重要內容,除了三個封裝函數,其中有一些內容多是有困惑的,這裏筆者將本身的理解講一下。首先,咱們先須要知道各個平臺實際上實現是有差別的,例如sockaddr_un
的結構體不一樣,在Linux和Solaris中,是以下所示:函數
struct sockaddr_un {
sa_family_t sun_family;
char sun_path[108];
};複製代碼
而在FreeBSD和OSX系統中,是以下的:ui
struct sockaddr_un {
unsigned char sun_len;
sa_family_t sun_family;
char sun_path[104];
};複製代碼
先給出serv_listen函數this
#include "apue.h"
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#define QLEN 10
/* * Create a server endpoint of a connection. * Returns fd if all OK, <0 on error. */
int serv_listen(const char *name) {
int fd, len, err, rval;
struct sockaddr_un un;
if (strlen(name) >= sizeof(un.sun_path)) {
errno = ENAMETOOLONG;
return(-1);
}
/* create a UNIX domain stream socket */
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
return(-2);
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 = -3;
goto errout;
}
if (listen(fd, QLEN) < 0) { /* tell kernel we're a server */
rval = -4;
goto errout;
}
return(fd);
errout:
err = errno;
close(fd);
errno = err;
return(rval);
}複製代碼
首先是建立一個Unix域套接字,若是文件已經存在則刪除原先文件,而後構造sockaddr_un結構體,而後使用bind函數將地址和套接字綁定,系統自動生成套接字文件,而後使用listen函數偵聽套接字。這裏基本沒什麼須要講解的。
而後是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 user 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, err, rval;
socklen_t len;
time_t staletime;
struct sockaddr_un un;
struct stat statbuf;
char *name;
/* allocate enough space for longest name plus terminating null */
if ((name = malloc(sizeof(un.sun_path + 1))) == NULL)
return(-1);
len = sizeof(un);
if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0) {
free(name);
return(-2); /* 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 */
memcpy(name, un.sun_path, len);
name[len] = 0; /* null terminate */
if (stat(name, &statbuf) < 0) {
rval = -3;
goto errout;
}
#ifdef S_ISSOCK /* not defined for SVR4 */
if (S_ISSOCK(statbuf.st_mode) == 0) {
rval = -4; /* not a socket */
goto errout;
}
#endif
if ((statbuf.st_mode & (S_IRWXG | S_IRWXO)) ||
(statbuf.st_mode & S_IRWXU) != S_IRWXU) {
rval = -5; /* is not rwx------ */
goto errout;
}
staletime = time(NULL) - STALE;
if (statbuf.st_atime < staletime ||
statbuf.st_ctime < staletime ||
statbuf.st_mtime < staletime) {
rval = -6; /* i-node is too old */
goto errout;
}
if (uidptr != NULL)
*uidptr = statbuf.st_uid; /* return uid of caller */
unlink(name); /* we're done with pathname now */
free(name);
return(clifd);
errout:
err = errno;
close(clifd);
free(name);
errno = err;
return(rval);
}複製代碼
首先是使用accept函數阻塞等待客戶進程鏈接。當accept返回的時候,返回的是新的套接字描述符,也就是存在鏈接的套接字,而且從第二個參數獲得套接字的路徑名,接着複製路徑名,最後調用stat函數檢查路徑名。
其中len -= offsetof(struct sockaddr_un, sun_path);
可能有些人不是很明白,這裏實際上用了點編碼技巧,實際上就是結構體總長度減去sun_path成員的內存偏移量,最終就是sun_path的長度。還有,accept第二個餐宿實際上和bind是不同的,由於這個套接字是已經鏈接的套接字,因此會包含客戶進程ID的名字。spa
#include "apue.h"
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#define CLI_PATH "/var/tmp/"
#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, sun;
int do_unlink = 0;
if (strlen(name) >= sizeof(un.sun_path)) {
errno = ENAMETOOLONG;
return(-1);
}
/* create a UNIX domain stream socket */
if ((fd = socket(AF_UNIX, SOCK_STREAM, 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%05ld", CLI_PATH, (long)getpid());
printf("file is %s\n", un.sun_path);
len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
unlink(un.sun_path); /* in case it already exists */
if (bind(fd, (struct sockaddr *)&un, len) < 0) {
rval = -2;
goto errout;
}
if (chmod(un.sun_path, CLI_PERM) < 0) {
rval = -3;
do_unlink = 1;
goto errout;
}
/* fill socket address structure with server's address */
memset(&sun, 0, sizeof(sun));
sun.sun_family = AF_UNIX;
strcpy(sun.sun_path, name);
len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
if (connect(fd, (struct sockaddr *)&sun, len) < 0) {
rval = -4;
do_unlink = 1;
goto errout;
}
return(fd);
errout:
err = errno;
close(fd);
if (do_unlink)
unlink(un.sun_path);
errno = err;
return(rval);
}複製代碼
首先是建立一個套接字,而後用客戶端進程ID附加到路徑上,造成本身的套接字路徑,而且將這個套接字綁定在地址上,也就是系統會在生成客戶端的socket文件,可能有人要奇怪了,爲何不直接使用connect而是要bind地址,由於若是不綁定地址,咱們就沒法區分鏈接是屬於哪一個客戶端進程的,也就是相似於網絡上客戶端特地綁定一個端口鏈接服務端。
最後還有三節,實際上都是屬於實際操做的內容了,因此這裏就不講了。這篇文章就算是最終的系列結尾,由於最後章節是項目源碼解析了。因此就再也不講述