Socket 用於進程間通信 --- UNIX Domain Socket
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綁定到一個地址。

1 #include <stdlib.h> 2 #include <stdio.h> 3 #include <stddef.h> 4 #include <sys/socket.h> 5 #include <sys/un.h> 6 7 int main(void) 8 { 9 int fd, size; 10 struct sockaddr_un un; 11 12 memset(&un, 0, sizeof(un)); 13 un.sun_family = AF_UNIX; 14 strcpy(un.sun_path, "foo.socket"); 15 if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) 16 { 17 perror("socket error"); 18 exit(1); 19 } 20 size = offsetof(struct sockaddr_un, sun_path) +strlen(un.sun_path); 21 22 if (bind(fd, (struct sockaddr *)&un, size) < 0) 23 { 24 perror("bind error"); 25 exit(1); 26 } 27 28 printf("UNIX domain socket bound/n"); 29 exit(0); 30 }
注意程序中的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文件)提供服務。

1 #include <stddef.h> 2 #include <sys/socket.h> 3 #include <sys/un.h> 4 #include <errno.h> 5 6 #define QLEN 10 7 8 /* 9 * Create a server endpoint of a connection. 10 * Returns fd if all OK, <0 on error. 11 */ 12 13 int serv_listen(const char *name) 14 { 15 int fd, len, err, rval; 16 struct sockaddr_un un; 17 18 /* create a UNIX domain stream socket */ 19 if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) 20 return(-1); 21 unlink(name); /* in case it already exists */ 22 23 /* fill in socket address structure */ 24 memset(&un, 0, sizeof(un)); 25 un.sun_family = AF_UNIX; 26 27 strcpy(un.sun_path, name); 28 len = offsetof(struct sockaddr_un, sun_path) + strlen(name); 29 30 /* bind the name to the descriptor */ 31 if (bind(fd, (struct sockaddr *)&un, len) < 0) 32 { 33 rval = -2; 34 goto errout; 35 } 36 37 if (listen(fd, QLEN) < 0) 38 { /* tell kernel we're a server */ 39 rval = -3; 40 goto errout; 41 } 42 43 return(fd); 44 45 errout: 46 err = errno; 47 close(fd); 48 errno = err; 49 return(rval); 50 }
以下是服務器的accept模塊,通過accept得到客戶端地址也應該是一個socket文件,如果不是socket文件就返回錯誤碼,如果是 socket文件,在建立連接后這個文件就沒有用了,調用unlink把它刪掉,通過傳出參數uidptr返回客戶端程序的user id。

1 #include <stddef.h> 2 #include <sys/stat.h> 3 #include <sys/socket.h> 4 #include <sys/un.h> 5 #include <errno.h> 6 7 int serv_accept(int listenfd, uid_t *uidptr) 8 { int clifd, len, err, rval; 9 time_t staletime; 10 struct sockaddr_un un; 11 struct stat statbuf; 12 len = sizeof(un); 13 if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0) 14 return(-1); 15 /* often errno=EINTR, if signal caught */ 16 /* obtain the client's uid from its calling address */ 17 len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */ 18 un.sun_path[len] = 0; /* null terminate */ 19 if (stat(un.sun_path, &statbuf) < 0) 20 { 21 rval = -2; 22 goto errout; 23 } 24 if (S_ISSOCK(statbuf.st_mode) == 0) 25 { 26 rval = -3; 27 /* not a socket */ 28 goto errout; 29 } 30 if (uidptr != NULL) 31 *uidptr = statbuf.st_uid; /* return uid of caller */ 32 unlink(un.sun_path); /* we're done with pathname now */ 33 34 return(clifd); 35 36 errout: 37 err = errno; 38 close(clifd); 39 errno = err; 40 return(rval); 41 }
以下是客戶端的connect模塊,與網絡socket編程不同的是,UNIX Domain Socket客戶端一般要顯式調用bind函數,而不依賴系統自動分配的地址。客戶端bind一個自己指定的socket文件名的好處是,該文件名可以包含客戶端的pid以便服務器區分不同的客戶端。

1 /* 2 * ss.c 3 * 4 * Created on: 2013-7-29 5 * Author: Administrator 6 */ 7 #include <stdio.h> 8 #include <stddef.h> 9 #include <sys/stat.h> 10 #include <sys/socket.h> 11 #include <sys/un.h> 12 #include <errno.h> 13 14 #define CLI_PATH "/var/tmp/" 15 16 /* +5 for pid = 14 chars */ 17 /* 18 * Create a client endpoint and connect to a server. 19 * Returns fd if all OK, <0 on error. 20 */ 21 int cli_conn(const char *name) 22 { 23 int fd, len, err, rval; 24 struct sockaddr_un un; 25 /* create a UNIX domain stream socket */ 26 if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) 27 return(-1); /* fill socket address structure with our address */ 28 memset(&un, 0, sizeof(un)); 29 un.sun_family = AF_UNIX; 30 sprintf(un.sun_path, "%s%05d", CLI_PATH, getpid()); 31 len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path); 32 unlink(un.sun_path); 33 /* in case it already exists */ 34 if (bind(fd, (struct sockaddr *)&un, len) < 0) 35 { 36 rval = -2; 37 goto errout; 38 } 39 /* fill socket address structure with server's address */ 40 memset(&un, 0, sizeof(un)); 41 un.sun_family = AF_UNIX; 42 strcpy(un.sun_path, name); 43 len = offsetof(struct sockaddr_un, sun_path) + strlen(name); 44 if (connect(fd, (struct sockaddr *)&un, len) < 0) 45 { 46 rval = -4; 47 goto errout; 48 } return(fd); 49 errout: err = errno; 50 close(fd); 51 errno = err; 52 return(rval); 53 }
服務器端:

#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"); }
客戶端:

1 #include <stdio.h> 2 #include <stddef.h> 3 #include <sys/stat.h> 4 #include <sys/socket.h> 5 #include <sys/un.h> 6 #include <errno.h> 7 #include <string.h> 8 9 /* Create a client endpoint and connect to a server. Returns fd if all OK, <0 on error. */ 10 int unix_socket_conn(const char *servername) 11 { 12 int fd; 13 if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) /* create a UNIX domain stream socket */ 14 { 15 return(-1); 16 } 17 int len, rval; 18 struct sockaddr_un un; 19 memset(&un, 0, sizeof(un)); /* fill socket address structure with our address */ 20 un.sun_family = AF_UNIX; 21 sprintf(un.sun_path, "scktmp%05d", getpid()); 22 len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path); 23 unlink(un.sun_path); /* in case it already exists */ 24 if (bind(fd, (struct sockaddr *)&un, len) < 0) 25 { 26 rval= -2; 27 } 28 else 29 { 30 /* fill socket address structure with server's address */ 31 memset(&un, 0, sizeof(un)); 32 un.sun_family = AF_UNIX; 33 strcpy(un.sun_path, servername); 34 len = offsetof(struct sockaddr_un, sun_path) + strlen(servername); 35 if (connect(fd, (struct sockaddr *)&un, len) < 0) 36 { 37 rval= -4; 38 } 39 else 40 { 41 return (fd); 42 } 43 } 44 int err; 45 err = errno; 46 close(fd); 47 errno = err; 48 return rval; 49 } 50 51 void unix_socket_close(int fd) 52 { 53 close(fd); 54 } 55 56 57 int main(void) 58 { 59 srand((int)time(0)); 60 int connfd; 61 connfd = unix_socket_conn("foo.sock"); 62 if(connfd<0) 63 { 64 printf("Error[%d] when connecting...",errno); 65 return 0; 66 } 67 printf("Begin to recv/send...\n"); 68 int i,n,size; 69 char rvbuf[4096]; 70 for(i=0;i<10;i++) 71 { 72 /* 73 //=========接收===================== 74 size = recv(connfd, rvbuf, 800, 0); //MSG_DONTWAIT 75 if(size>=0) 76 { 77 printf("Recieved Data[%d]:%c...%c\n",size,rvbuf[0],rvbuf[size-1]); 78 } 79 if(size==-1) 80 { 81 printf("Error[%d] when recieving Data.\n",errno); 82 break; 83 } 84 if(size < 800) break; 85 */ 86 //=========發送====================== 87 memset(rvbuf,'a',2048); 88 rvbuf[2047]='b'; 89 size = send(connfd, rvbuf, 2048, 0); 90 if(size>=0) 91 { 92 printf("Data[%d] Sended:%c.\n",size,rvbuf[0]); 93 } 94 if(size==-1) 95 { 96 printf("Error[%d] when Sending Data:%s.\n",errno,strerror(errno)); 97 break; 98 } 99 sleep(1); 100 } 101 unix_socket_close(connfd); 102 printf("Client exited.\n"); 103 }
參考鏈接: