Unix domain socket 又叫 IPC(inter-process communication 進程間通訊) socket,用於實現同一主機上的進程間通訊。socket 本來是爲網絡通信設計的,但後來在 socket 的框架上發展出一種 IPC 機制,就是 UNIX domain socket。雖然網絡 socket 也可用於同一臺主機的進程間通信(經過 loopback 地址 127.0.0.1),可是 UNIX domain socket 用於 IPC 更有效率:不須要通過網絡協議棧,不須要打包拆包、計算校驗和、維護序號和應答等,只是將應用層數據從一個進程拷貝到另外一個進程。這是由於,IPC 機制本質上是可靠的通信,而網絡協議是爲不可靠的通信設計的。
UNIX domain socket 是全雙工的,API 接口語義豐富,相比其它 IPC 機制有明顯的優越性,目前已成爲使用最普遍的 IPC 機制,好比 X Window 服務器和 GUI 程序之間就是經過 UNIX domain socket 通信的。
Unix domain socket 是 POSIX 標準中的一個組件,因此不要被名字迷惑,linux 系統也是支持它的。html
下面經過一個簡單的 demo 來理解相關概念。程序分爲服務器端和客戶端兩部分,它們之間經過 unix domain socket 進行通訊。linux
下面是一個很是簡單的服務器端程序,它從客戶端讀字符,而後將每一個字符轉換爲大寫並回送給客戶端:編程
#include <stdlib.h> #include <stdio.h> #include <stddef.h> #include <sys/socket.h> #include <sys/un.h> #include <errno.h> #include <string.h> #include <unistd.h> #include <ctype.h> #define MAXLINE 80 char *socket_path = "server.socket"; int main(void) { struct sockaddr_un serun, cliun; socklen_t cliun_len; int listenfd, connfd, size; char buf[MAXLINE]; int i, n; if ((listenfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { perror("socket error"); exit(1); } memset(&serun, 0, sizeof(serun)); serun.sun_family = AF_UNIX; strcpy(serun.sun_path, socket_path); size = offsetof(struct sockaddr_un, sun_path) + strlen(serun.sun_path); unlink(socket_path); if (bind(listenfd, (struct sockaddr *)&serun, size) < 0) { perror("bind error"); exit(1); } printf("UNIX domain socket bound\n"); if (listen(listenfd, 20) < 0) { perror("listen error"); exit(1); } printf("Accepting connections ...\n"); while(1) { cliun_len = sizeof(cliun); if ((connfd = accept(listenfd, (struct sockaddr *)&cliun, &cliun_len)) < 0){ perror("accept error"); continue; } while(1) { n = read(connfd, buf, sizeof(buf)); if (n < 0) { perror("read error"); break; } else if(n == 0) { printf("EOF\n"); break; } printf("received: %s", buf); for(i = 0; i < n; i++) { buf[i] = toupper(buf[i]); } write(connfd, buf, n); } close(connfd); } close(listenfd); return 0; }
簡單介紹一下這段代碼:服務器
int socket(int family, int type, int protocol);
使用 UNIX domain socket 的過程和網絡 socket 十分類似,也要先調用 socket() 建立一個 socket 文件描述符.
family 指定爲 AF_UNIX,使用 AF_UNIX 會在系統上建立一個 socket 文件,不一樣進程經過讀寫這個文件來實現通訊。
type 能夠選擇 SOCK_DGRAM 或 SOCK_STREAM。SOCK_STREAM 意味着會提供按順序的、可靠、雙向、面向鏈接的比特流。SOCK_DGRAM 意味着會提供定長的、不可靠、無鏈接的通訊。
protocol 參數指定爲 0 便可。
UNIX domain socket 與網絡 socket 編程最明顯的不一樣在於地址格式不一樣,用結構體 sockaddr_un 表示,網絡編程的 socket 地址是 IP 地址加端口號,而 UNIX domain socket 的地址是一個 socket 類型的文件在文件系統中的路徑,這個 socket 文件由 bind() 調用建立,若是調用 bind() 時該文件已存在,則 bind() 錯誤返回。所以,通常在調用 bind() 前會檢查 socket 文件是否存在,若是存在就刪除掉。
網絡 socket 編程相似,在 bind 以後要 listen,表示經過 bind 的地址(也就是 socket 文件)提供服務。
接下來必須用 accept() 函數初始化鏈接。accept() 爲每一個鏈接創立新的套接字並從監聽隊列中移除這個鏈接。網絡
下面是客戶端程序,它接受用戶的輸入,並把字符串發送給服務器,而後接收服務器返回的字符串並打印:框架
#include <stdlib.h> #include <stdio.h> #include <stddef.h> #include <sys/socket.h> #include <sys/un.h> #include <errno.h> #include <string.h> #include <unistd.h> #define MAXLINE 80 char *client_path = "client.socket"; char *server_path = "server.socket"; int main() { struct sockaddr_un cliun, serun; int len; char buf[100]; int sockfd, n; if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0){ perror("client socket error"); exit(1); } // 通常顯式調用bind函數,以便服務器區分不一樣客戶端 memset(&cliun, 0, sizeof(cliun)); cliun.sun_family = AF_UNIX; strcpy(cliun.sun_path, client_path); len = offsetof(struct sockaddr_un, sun_path) + strlen(cliun.sun_path); unlink(cliun.sun_path); if (bind(sockfd, (struct sockaddr *)&cliun, len) < 0) { perror("bind error"); exit(1); } memset(&serun, 0, sizeof(serun)); serun.sun_family = AF_UNIX; strcpy(serun.sun_path, server_path); len = offsetof(struct sockaddr_un, sun_path) + strlen(serun.sun_path); if (connect(sockfd, (struct sockaddr *)&serun, len) < 0){ perror("connect error"); exit(1); } while(fgets(buf, MAXLINE, stdin) != NULL) { write(sockfd, buf, strlen(buf)); n = read(sockfd, buf, MAXLINE); if ( n < 0 ) { printf("the other side has been closed.\n"); }else { write(STDOUT_FILENO, buf, n); } } close(sockfd); return 0; }
與網絡 socket 編程不一樣的是,UNIX domain socket 客戶端通常要顯式調用 bind 函數,而不依賴系統自動分配的地址。客戶端 bind 一個本身指定的 socket 文件名的好處是,該文件名能夠包含客戶端的 pid 等信息以便服務器區分不一樣的客戶端。dom
分別把服務器端程序和客戶端程序保存爲 server.c 和 client.c 文件,並編譯:socket
$ gcc server.c -o server $ gcc client.c -o client
先啓動服務器端程序,而後啓動客戶端程序輸入字符串並回車:ide
還不錯,客戶端獲得了服務器端返回的大寫字符串。接下來看看當前目錄下的文件:函數
哈哈,多了兩個 socket 文件。
Unix domain socket 主要用於同一主機上的進程間通訊。與主機間的進程通訊不一樣,它不是經過 "IP地址 + TCP或UDP端口號" 的方式進程通訊,而是使用 socket 類型的文件來完成通訊,所以在穩定性、可靠性以及效率方面的表現都很不錯。