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通訊的。
二、工作流程
UNIX Domain socket與網絡socket類似,可以與網絡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—綁定文件(端口)—連接—發送/接收數據—…—關閉
三、阻塞和非阻塞(SOCK_STREAM方式)
讀寫操作有兩種操作方式:阻塞和非阻塞。
1.阻塞模式下
阻塞模式下,發送數據方和接收數據方的表現情況如同命名管道,參見本人文章“Linux下的IPC-命名管道的使用(http://blog.csdn.net/guxch/article/details/6828452)”
2.非阻塞模式
在send或recv函數的標誌參數中設置MSG_DONTWAIT,則發送和接收都會返回。如果沒有成功,則返回值爲-1,errno爲EAGAIN 或 EWOULDBLOCK。
四、測試代碼
服務器端
- #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 <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");
- }
#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");
}
五、 討論
通過實際測試,發現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時,有些地方是複用的。