本文轉載自:http://blog.csdn.net/guxch/article/details/7041052linux
1、 概述編程
UNIX Domain Socket是在socket架構上發展起來的用於同一臺主機的進程間通信(IPC),它不須要通過網絡協議棧,不須要打包拆包、計算校驗和、維護序號和應答等,只是將應用層數據從一個進程拷貝到另外一個進程。UNIX Domain Socket有SOCK_DGRAM或SOCK_STREAM兩種工做模式,相似於UDP和TCP,可是面向消息的UNIX Domain Socket也是可靠的,消息既不會丟失也不會順序錯亂。服務器
UNIX Domain Socket可用於兩個沒有親緣關係的進程,是全雙工的,是目前使用最普遍的IPC機制,好比X Window服務器和GUI程序之間就是經過UNIX Domain Socket通信的。網絡
2、工做流程架構
UNIX Domain socket與網絡socket相似,能夠與網絡socket對比應用。dom
上述兩者編程的不一樣以下:socket
- address family爲AF_UNIX
- 由於應用於IPC,因此UNIXDomain socket不須要IP和端口,取而代之的是文件路徑來表示「網絡地址」。這點體如今下面兩個方面。
- 地址格式不一樣,UNIXDomain socket用結構體sockaddr_un表示,是一個socket類型的文件在文件系統中的路徑,這個socket文件由bind()調用建立,若是調用bind()時該文件已存在,則bind()錯誤返回。
- UNIX Domain Socket客戶端通常要顯式調用bind函數,而不象網絡socket同樣依賴系統自動分配的地址。客戶端bind的socket文件名能夠包含客戶端的pid,這樣服務器就能夠區分不一樣的客戶端。
UNIX Domain socket的工做流程簡述以下(與網絡socket相同)。函數
服務器端:建立socket—綁定文件(端口)—監聽—接受客戶端鏈接—接收/發送數據—…—關閉測試
客戶端:建立socket—綁定文件(端口)—鏈接—發送/接收數據—…—關閉網站
3、阻塞和非阻塞(SOCK_STREAM方式)
讀寫操做有兩種操做方式:阻塞和非阻塞。
1.阻塞模式下
阻塞模式下,發送數據方和接收數據方的表現狀況如同命名管道,參見本人文章「Linux下的IPC-命名管道的使用(http://blog.csdn.NET/guxch/article/details/6828452)」
2.非阻塞模式
在send或recv函數的標誌參數中設置MSG_DONTWAIT,則發送和接收都會返回。若是沒有成功,則返回值爲-1,errno爲EAGAIN 或 EWOULDBLOCK。
4、測試代碼
服務器端
- #include <stdio.h>
- #include <sys/stat.h>
- #include <sys/socket.h>
- #include <sys/un.h>
- #include <errno.h>
- #include <stddef.h>
- #include <string.h>
-
- #define MAX_CONNECTION_NUMBER 5
-
- 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);
- 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 (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);
- }
-
- len -= offsetof(struct sockaddr_un, sun_path);
- un.sun_path[len] = 0;
- if (stat(un.sun_path, &statbuf) < 0)
- {
- rval = -2;
- }
- else
- {
- if (S_ISSOCK(statbuf.st_mode) )
- {
- if (uidptr != NULL) *uidptr = statbuf.st_uid;
- unlink(un.sun_path);
- return clifd;
- }
- else
- {
- rval = -3;
- }
- }
- 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)
- {
-
- 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;
- }
- 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>
-
- int unix_socket_conn(const char *servername)
- {
- int fd;
- if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
- {
- return(-1);
- }
- int len, rval;
- struct sockaddr_un un;
- memset(&un, 0, sizeof(un));
- 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);
- if (bind(fd, (struct sockaddr *)&un, len) < 0)
- {
- rval= -2;
- }
- else
- {
-
- 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++)
- {
-
- 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");
- }
5、 討論
經過實際測試,發現UNIXDomain Socket與命名管道在表現上有很大的類似性,例如,UNIX Domain Socket也會在磁盤上建立一個socket類型文件;若是讀端進程關閉了,寫端進程「寫數據」時,有可能使進程異常退出,等等。查閱有關文檔,摘錄以下:
Send函數
當調用該函數時,send先比較待發送數據的長度len和套接字s的發送緩衝的 長度,若是len大於s的發送緩衝區的長度,該函數返回SOCKET_ERROR;若是len小於或者等於s的發送緩衝區的長度,那麼send先檢查協議是否正在發送s的發送緩衝中的數據,若是是就等待協議把數據發送完,若是協議尚未開始發送s的發送緩衝中的數據或者s的發送緩衝中沒有數據,那麼 send就比較s的發送緩衝區的剩餘空間和len,若是len大於剩餘空間大小send就一直等待協議把s的發送緩衝中的數據發送完,若是len小於剩餘空間大小send就僅僅把buf中的數據copy到剩餘空間裏(注意並非send把s的發送緩衝中的數據傳到鏈接的另外一端的,而是協議傳的,send僅僅是把buf中的數據copy到s的發送緩衝區的剩餘空間裏)。若是send函數copy數據成功,就返回實際copy的字節數,若是send在copy數據時出現錯誤,那麼send就返回SOCKET_ERROR;若是send在等待協議傳送數據時網絡斷開的話,那麼send函數也返回SOCKET_ERROR。
要注意send函數把buf中的數據成功copy到s的發送緩衝的剩餘空間裏後它就返回了,可是此時這些數據並不必定立刻被傳到鏈接的另外一端。若是協議在後續的傳送過程當中出現網絡錯誤的話,那麼下一個Socket函數就會返回SOCKET_ERROR。(每個除send外的Socket函數在執行的最開始總要先等待套接字的發送緩衝中的數據被協議傳送完畢才能繼續,若是在等待時出現網絡錯誤,那麼該Socket函數就返回 SOCKET_ERROR)
注意:在Unix系統下,若是send在等待協議傳送數據時網絡斷開的話,調用send的進程會接收到一個SIGPIPE信號,進程對該信號的默認處理是進程終止。
Recv函數與send相似,看樣子系統在實現各類IPC時,有些地方是複用的。