轉載:http://www.cnblogs.com/chekliang/p/3222950.html
socket API原本是為網絡通訊設計的,但后來在socket的框架上發展出一種IPC機制,就是UNIX Domain Socket。 雖然網絡socket也可用於同一台主機的進程間通訊(通過loopback地址127.0.0.1),但是UNIX Domain Socket用於IPC更有效率:不需要經過網絡協議棧,不需要打包拆包、計算校驗和、維護序號和應答等,只是將應用層數據從一個進程拷貝到另一個進程。 這是因為,IPC機制本質上是可靠的通訊,而網絡協議是為不可靠的通訊設計的。UNIX Domain Socket也提供面向流和面向數據包兩種API接口,類似於TCP和UDP,但是面向消息的UNIX Domain Socket也是可靠的,消息既不會丟失也不會順序錯亂。
UNIX Domain Socket是全雙工的,API接口語義豐富,相比其它IPC機制有明顯的優越性,目前已成為使用最廣泛的IPC機制,比如X Window服務器和GUI程序之間就是通過UNIX Domain Socket通訊的。
使用UNIX Domain Socket的過程和網絡socket十分相似,也要先調用socket()創建一個socket文件描述符,address family指定為AF_UNIX,type可以選擇SOCK_DGRAM或SOCK_STREAM,protocol參數仍然指定為0即可。
UNIX Domain Socket與網絡socket編程最明顯的不同在於地址格式不同,用結構體sockaddr_un表示,網絡編程的socket地址是IP地址加端口號,而UNIX Domain Socket的地址是一個socket類型的文件在文件系統中的路徑,這個socket文件由bind()調用創建,如果調用bind()時該文件已存在,則bind()錯誤返回。
以下程序將UNIX Domain socket綁定到一個地址。

#include <stdlib.h> #include <stdio.h> #include <stddef.h> #include <sys/socket.h> #include <sys/un.h> int main(void) { int fd, size; struct sockaddr_un un; memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; strcpy(un.sun_path, "foo.socket"); if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { perror("socket error"); exit(1); } size = offsetof(struct sockaddr_un, sun_path) +strlen(un.sun_path); if (bind(fd, (struct sockaddr *)&un, size) < 0) { perror("bind error"); exit(1); } printf("UNIX domain socket bound/n"); exit(0); }
注意程序中的offsetof宏,它在stddef.h頭文件中定義:
#define offsetof(TYPE, MEMBER) ((int)&((TYPE *)0)->MEMBER)
offsetof(struct sockaddr_un, sun_path)就是取sockaddr_un結構體的sun_path成員在結構體中的偏移,也就是從結構體的第幾個字節開始是sun_path成 員。想一想,這個宏是如何實現這一功能的?(先將TYPE類型的指針首地址設為0,然后取MEMBER成員的地址就是該成員在TYPE中的偏移數。)
該程序的運行結果如下。
$ ./a.out UNIX domain socket bound
$ ls -l
foo.socket srwxrwxr-x 1 user 0 Aug 22 12:43 foo.socket
$ ./a.out bind error: Address already in use
$ rm foo.socket
$ ./a.out UNIX domain socket bound
以下是服務器的listen模塊,與網絡socket編程類似,在bind之后要listen,表示通過bind的地址(也就是socket文件)提供服務。
#include <stddef.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; /* 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); }
以下是服務器的accept模塊,通過accept得到客戶端地址也應該是一個socket文件,如果不是socket文件就返回錯誤碼,如果 是 socket文件,在建立連接后這個文件就沒有用了,調用unlink把它刪掉,通過傳出參數uidptr返回客戶端程序的user id。
#include <stddef.h> #include <sys/stat.h> #include <sys/socket.h> #include <sys/un.h> #include <errno.h> 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; } if (S_ISSOCK(statbuf.st_mode) == 0) { rval = -3; /* not a socket */ 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); }
以下是客戶端的connect模塊,與網絡socket編程不同的是,UNIX Domain Socket客戶端一般要顯式調用bind函數,而不依賴系統自動分配的地址。客戶端bind一個自己指定的socket文件名的好處是,該文件名可以包 含客戶端的pid以便服務器區分不同的客戶端。
/* * ss.c * * Created on: 2013-7-29 * Author: Administrator */ #include <stdio.h> #include <stddef.h> #include <sys/stat.h> #include <sys/socket.h> #include <sys/un.h> #include <errno.h> #define CLI_PATH "/var/tmp/" /* +5 for pid = 14 chars */ /* * 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_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%05d", CLI_PATH, getpid()); 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; } /* 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); }
服務器端:
#include <stdio.h> #include <sys/stat.h> #include <sys/socket.h> #include <sys/un.h> #include <errno.h> #include <stddef.h> #include <string.h> // the max connection number of the server #define MAX_CONNECTION_NUMBER 5 /* * Create a server endpoint of a connection. * Returns fd if all OK, <0 on error. */ int unix_socket_listen(const char *servername) { int fd; struct sockaddr_un un; if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { return(-1); } int len, rval; unlink(servername); /* in case it already exists */ memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; strcpy(un.sun_path, servername); len = offsetof(struct sockaddr_un, sun_path) + strlen(servername); /* bind the name to the descriptor */ if (bind(fd, (struct sockaddr *)&un, len) < 0) { rval = -2; } else { if (listen(fd, MAX_CONNECTION_NUMBER) < 0) { rval = -3; } else { return fd; } } int err; err = errno; close(fd); errno = err; return rval; } int unix_socket_accept(int listenfd, uid_t *uidptr) { int clifd, len, 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); } /* 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; } else { if (S_ISSOCK(statbuf.st_mode) ) { if (uidptr != NULL) *uidptr = statbuf.st_uid; /* return uid of caller */ unlink(un.sun_path); /* we're done with pathname now */ return clifd; } else { rval = -3; /* not a socket */ } } int err; err = errno; close(clifd); errno = err; return(rval); } void unix_socket_close(int fd) { close(fd); } int main(void) { int listenfd,connfd; listenfd = unix_socket_listen("foo.sock"); if(listenfd<0) { printf("Error[%d] when listening...\n",errno); return 0; } printf("Finished listening...\n",errno); uid_t uid; connfd = unix_socket_accept(listenfd, &uid); unix_socket_close(listenfd); if(connfd<0) { printf("Error[%d] when accepting...\n",errno); return 0; } printf("Begin to recv/send...\n"); int i,n,size; char rvbuf[2048]; for(i=0;i<2;i++) { //===========接收============== size = recv(connfd, rvbuf, 804, 0); if(size>=0) { // rvbuf[size]='\0'; printf("Recieved Data[%d]:%c...%c\n",size,rvbuf[0],rvbuf[size-1]); } if(size==-1) { printf("Error[%d] when recieving Data:%s.\n",errno,strerror(errno)); break; } /* //===========發送============== memset(rvbuf, 'c', 2048); size = send(connfd, rvbuf, 2048, 0); if(size>=0) { printf("Data[%d] Sended.\n",size); } if(size==-1) { printf("Error[%d] when Sending Data.\n",errno); break; } */ sleep(30); } unix_socket_close(connfd); printf("Server exited.\n"); }
客戶端:
#include <stdio.h> #include <stddef.h> #include <sys/stat.h> #include <sys/socket.h> #include <sys/un.h> #include <errno.h> #include <string.h> /* Create a client endpoint and connect to a server. Returns fd if all OK, <0 on error. */ int unix_socket_conn(const char *servername) { int fd; if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) /* create a UNIX domain stream socket */ { return(-1); } int len, rval; struct sockaddr_un un; memset(&un, 0, sizeof(un)); /* fill socket address structure with our address */ un.sun_family = AF_UNIX; sprintf(un.sun_path, "scktmp%05d", getpid()); 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; } else { /* fill socket address structure with server's address */ memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; strcpy(un.sun_path, servername); len = offsetof(struct sockaddr_un, sun_path) + strlen(servername); if (connect(fd, (struct sockaddr *)&un, len) < 0) { rval= -4; } else { return (fd); } } int err; err = errno; close(fd); errno = err; return rval; } void unix_socket_close(int fd) { close(fd); } int main(void) { srand((int)time(0)); int connfd; connfd = unix_socket_conn("foo.sock"); if(connfd<0) { printf("Error[%d] when connecting...",errno); return 0; } printf("Begin to recv/send...\n"); int i,n,size; char rvbuf[4096]; for(i=0;i<10;i++) { /* //=========接收===================== size = recv(connfd, rvbuf, 800, 0); //MSG_DONTWAIT if(size>=0) { printf("Recieved Data[%d]:%c...%c\n",size,rvbuf[0],rvbuf[size-1]); } if(size==-1) { printf("Error[%d] when recieving Data.\n",errno); break; } if(size < 800) break; */ //=========發送====================== memset(rvbuf,'a',2048); rvbuf[2047]='b'; size = send(connfd, rvbuf, 2048, 0); if(size>=0) { printf("Data[%d] Sended:%c.\n",size,rvbuf[0]); } if(size==-1) { printf("Error[%d] when Sending Data:%s.\n",errno,strerror(errno)); break; } sleep(1); } unix_socket_close(connfd); printf("Client exited.\n"); }
參考鏈接:
http://blog.csdn.net/guxch/article/details/7041052
基於DRAM實現的UNIX域套接字
服務器端
#include <stdio.h> #include <sys/stat.h> #include <sys/socket.h> #include <sys/un.h> #include <errno.h> #include <stddef.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #define NAME "/tmp/foo.sock" #define N 256 typedef struct sockaddr SA; int main(void) { int sockfd; int size; struct sockaddr_un un; char rvbuf[2048]; int len; if ((sockfd = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0) { return(-1); } unlink(NAME); /* in case it already exists */ memset(&un, 0, sizeof(un)); un.sun_family = PF_UNIX; strcpy(un.sun_path, NAME); len = offsetof(struct sockaddr_un, sun_path) + strlen(NAME); /* bind the name to the descriptor */ if (bind(sockfd, (struct sockaddr *)&un, len) < 0) { perror("fail to bind!\n"); exit(-1); } printf("Begin to recv/send...\n"); while(1) { size = recvfrom(sockfd, rvbuf, N, 0,NULL, NULL); if(size>=0) { // rvbuf[size]='\0'; printf("Recieved Data[%d]:%c...%c\n",size,rvbuf[0],rvbuf[size-1]); } if(size==-1) { printf("Error[%d] when recieving Data:%s.\n",errno,strerror(errno)); break; } } close(sockfd); printf("Server exited.\n"); return 0; }
客戶端
#include <stdio.h> #include <stddef.h> #include <sys/stat.h> #include <sys/socket.h> #include <sys/un.h> #include <errno.h> #include <string.h> #include <unistd.h> #include <stdlib.h> typedef struct sockaddr SA; #define NAME "/tmp/foo.sock" #define N 256 int main(void) { int sockfd; struct sockaddr_un un; int size; char rvbuf[4096]; if ((sockfd = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0) /* create a UNIX domain stream socket */ { return(-1); } int len; memset(&un, 0, sizeof(un)); un.sun_family = PF_UNIX; strcpy(un.sun_path, NAME); len = offsetof(struct sockaddr_un, sun_path) + strlen(NAME); printf("Begin to recv/send...\n"); while(1) { memset(rvbuf,'a',2048); rvbuf[2047]='b'; size = sendto(sockfd, rvbuf, N, 0, (SA *)&un, len); if(size>=0) { printf("Data[%d] Sended:%c.\n",size,rvbuf[0]); } if(size==-1) { printf("Error[%d] when Sending Data:%s.\n",errno,strerror(errno)); break; } sleep(1); } close(sockfd); printf("Client exited.\n"); return 0; }
UNIX域套接字——UNIX domain socket(DGRAM)
By fireworks2@foxmail.com
找了大半天的資料,收獲也不多,其實還是自己思考更靠譜一些。
1. unix域的數據報服務是否可靠
man unix 手冊即可看到,unix domain socket 的數據報既不會丟失也不會亂序 (據我所知,在linux下的確是這樣)。不過最新版本的內核,仍然又提供了一個保證次序的類型 “ kernel 2.6.4 SOCK_SEQPACKET ”。
2. STREAM 和 DGRAM 的主要區別
既然數據報不丟失也可靠,那不是和 STREAM 很類似么?我理解也確實是這樣,而且我覺得 DGRAM 相對還要好一些,因為發送的數據可以帶邊界。二者另外的區別在於收發時的數據量不一樣,基於 STREAM 的套接字,send 可以傳入超過 SO_SNDBUF 長的數據,recv 時同 TCP 類似會存在數據粘連。
采用阻塞方式使用API,在unix domain socket 下調用 sendto 時,如果緩沖隊列已滿,會阻塞。而UDP因為不是可靠的,無法感知對端的情況,即使對端沒有及時收取數據,基本上sendto都能立即返回成功(如果發端 瘋狂sendto就另當別論,因為過快地調用sendto在慢速網絡的環境下,可能撐爆套接字的緩沖區,導致sendto阻塞)。
3. SO_SNDBUF 和 SO_REVBUF
對於 unix domain socket,設置 SO_SNDBUF 會影響 sendto 最大的報文長度,但是任何針對 SO_RCVBUF 的設置都是無效的 。實際上 unix domain socket 的數據報還是得將數據放入內核所申請的內存塊里面,再由另一個進程通過 recvfrom 從內核讀取,因此具體可以發送的數據報長度受限於內核的 slab 策略 。在 linux 平台下,早先版本(如 2.6.2)可發送最大數據報長度約為 128 k ,新版本的內核支持更大的長度。
4. 使用 DGRAM 時,緩沖隊列的長度
有幾個因素會影響緩沖隊列的長度,一個是上面提到的 slab 策略,另一個則是系統的內核參數 /proc/sys/net/unix/max_dgram_qlen。緩沖隊列長度是這二者共同決定的。
如 max_dgram_qlen 默認為 10,在數據報較小時(如1k),先掛起接收數據的進程后,仍可以 sendto 10 次並順利返回;
但是如果數據報較大(如120k)時,就要看 slab “size-131072” 的 limit 了。
5. 使用 unix domain socket 進行進程間通信 vs 其他方式
· 需要先確定操作系統類型,以及其所對應的最大 DGRAM 長度,如果有需要傳送超過該長度的數據報,建議拆分成幾個發送,接收后組裝即可(不會亂序,個人覺得這樣做比用 STREAM 再切包方便得多)
· 同管道相比,unix 域的數據報不但可以維持數據的邊界,還不會碰到在寫入管道時的原子性問題。
· 同共享內存相比,不能獨立於進程緩存大量數據,但是卻避免了同步互斥的考量。
· 同普通 socket 相比,開銷相對較小(不用計算報頭),DGRAM 的報文長度可以大於 64k,不過不能像普通 socket 那樣將進程切換到不同機器 。
6. 其他
其實在本機 IPC 時,同普通 socket 的 UDP 相比,unix domain socket 的數據報只不過是在收發時分別少計算了一下校驗和而已,本機的 UDP 會走 lo 接口,不會進行 IP 分片,也不會真正跑到網卡的鏈路層上去(不會占用網卡硬件) 。也就是說,在本機上使用普通的 socket UDP,只是多耗了一些 CPU(之所以說一些,是因為校驗和的計算很簡單),此外本機的 UDP 也可以保證數據不丟失、不亂序 。
從我個人的經驗來看,即便是高並發的網絡服務器,單純因為收發包造成的 CPU 占用其實並不算多(其中收包占用的 CPU 從 %si 可見一斑,因為收包需通過軟中斷實現的),倒是網卡帶寬、磁盤IO、后台邏輯、內存使用等問題往往成為主要矛盾。
所以,在沒有長時緩存通信數據的需求時,可以考慮通過 UDP 來實現本地進程間 IPC,這樣也便於切換機器。 (也不必去操心 unix domain socket 數據報的最大長度了,呵呵)對於較長的報文,可以切分成小的,再重新組裝,不過這樣做僅適用於本機通信,如果還要考慮以后遷移機器,那還是老老實實地 TCP 吧。 (本地 TCP 耗用資源的情況不得而知,沒有做過充分的測試,不過本地 TCP 自身的一些收發包操作一般也不構成瓶頸)
---在 google 時查找到的其他資源也一並列在這里---
setsockopt 設置 SO_SNDBUF SO_RCVBUF 時的冗余分配:
unix域套接字的使用:
http://www.cnblogs.com/skynet/archive/2010/12/04/1881236.html
http://hi.baidu.com/studyarea/blog/item/b007f4450a2a9e38879473ca.html