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 }
参考链接:

