本文转载自: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时,有些地方是复用的。