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 #include2 #include 3 #include 4 #include 5 #include 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 #include2 #include 3 #include 4 #include 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 #include2 #include 3 #include 4 #include 5 #include 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 #include8 #include 9 #include 10 #include 11 #include 12 #include 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#include #include #include #include #include #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 #include2 #include 3 #include 4 #include 5 #include 6 #include 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 }
参考链接:
http://blog.csdn.net/guxch/article/details/7041052