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 }
參考鏈接:

