UNIX域协议并非一个实际的协议族,而是在单个主机上执行客户/服务器通讯的一种方法,所用API与在不一样主机上执行客户/服务器通讯所用的API(套接口API)相同。UNIX域协议可视为进程间通讯(IPC)方法之一。html
UNIX域提供两类套接口:字节流套接口(相似TCP)和数据报套接口(相似UDP)。编程
使用UNIX域套接口的理由有3个:数组
在源自Berkeley的实现中,UNIX域套接口每每比通讯两端位于同一主机的TCP套接口快出一倍。安全
UNIX域套接口可用于在同一个主机上的不一样进程间传递描述字。服务器
UNIX域套接口较新的实现把客户的凭证(用户ID和组ID)提供给服务器,从而可以提供额外的安全检查措施。socket
UNIX域中用于标识客户和服务器的协议地址是普通文件系统中的路径名。这些路径名不是普通的UNIX文件:除非把它们和UNIX域套接口关联起来,不然没法读写这些文件。函数
在头文件<sys/un.h>中定义了UNIX域套接口地址结构:测试
struct sockaddr_un { sa_family_t sun_family; /* AF_LOCAL */ char sun_path[104]; /* null-terminated pathname */ };
存放在sun_path数组中的路径名必须以空格字符结尾。spa
实现提供的SUN_LEN宏以一个指向sockaddr_un结构的指针为参数并返回该结构的长度,其中包括路径名中非空字节数。操作系统
未指定地址(通配地址),经过以空字符串做为路径名指示,也就是一个sun_path[0]值为0的地址结构。这是UNIX域中与IPv4的INADDR_ANY常值以及IPv6的IN6ADDR_ANY_INIT常值等价的一个地址。
POSIX把UNIX域协议从新命名为“本地IPC”,以消除它对于UNIX操做系统的依赖。历史性的AF_UNIX常值变为AF_LOCAL。尽管POSIX努力使它独立于操做系统,它的套接口地址结构仍然保留_un后缀。
实例:UNIX域套接口的bind调用
建立一个UNIX域套接口,往其上bind一个路径名,再调用getsockname输出这个绑定的路径名。
#include <sys/un.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> int main(int argc, char **argv) { int sockfd; socklen_t len; struct sockaddr_un addr1, addr2; if(argc != 2) { printf("usage: unixbind <pathname> "); exit(0); } sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); unlink(argv[1]); /* 若是文件系统中已存在该路径名,bind将会失败。为此咱们先调用unlink删除这个路径名,以防止它已经存在。 */ bzero(&addr1, sizeof(addr1)); addr1.sun_family = AF_LOCAL; strncpy(addr1.sun_path, argv[1], sizeof(addr1.sun_path) - 1); bind(sockfd, (struct sockaddr *)&addr1, SUN_LEN(&addr1)); len = sizeof(addr2); getsockname(sockfd, (struct sockaddr *)&addr2, &len); printf("bound name = %s, returned len = %d\n", addr2.sun_path, len); exit(0); }
运行结果以下:
socketpair函数建立两个随后链接起来的套接口。本函数仅适用于UNIX域套接口。
#include <sys/socket.h> int socketpair(int family, int type, int protocol, int sockfd[2]); 返回:0——成功,-1——出错
family参数必须为AF_LOCAL;
protocol参数必须为0;
type参数能够是SOCK_STREAM,也能够是SOCK_DGRAM。
新建立的两个套接口描述字做为sockfd[0]和sockfd[1]返回。
本函数相似于UNIX的pipe函数:返回两个彼此链接的描述字。事实上,源自berkeley的实现经过执行与socketpair同样的内部操做给出pipe接口。
这样建立的两个套接口未曾命名;也就是说其中没有涉及隐式的bind调用。它与调用pipe建立的普通UNIX管道相似,差异在于流管道(socketpair建立的)是全双工的,即两个描述字都是既可读又可写。
POSIX不要求全双工管道。
当用于UNIX域套接口时,套接口函数中存在一些差别和限制:
由bind建立的路径名缺省访问权限应为0777(属主用户、组用户和其余用户均可读、可写并可执行),并按照当前umask值进行修正。
与UNIX域套接口关联的路径名应该是一个绝对路径名,而不是一个相对路径名。
在connect调用中指定的路径名必须是一个当前捆绑在某个打开的UNIX域套接口上的路径名,并且它们的套接口类型(字节流或数据报)也必须一致。
调用connect链接一个UNIX域套接口涉及的权限测试等同于调用open以只读方式访问相应的路径名。
UNIX域字节流套接口相似于TCP套接口:它们都为进程提供一个无记录边界的字节流接口。
若是对于某个UNIX域字节流套接口的connect调用发现这个监听套接口的队列已满,调用就当即返回一个ECONNREFUSED错误。这一点不一样于TCP:若是TCP监听套接口的队列已满,TCP监听端就忽略新到达的SYN,而TCP链接发起端将数次发送SYN进行重试。
UNIX域数据报套接口相似UDP套接口:它们都提供一个保留记录边界的不可靠的数据报服务。
在一个未绑定的UNIX域套接口上发送数据报不会自动给这个套接口捆绑一个路径名,这一点不一样于UDP套接口:在一个未绑定的UDP套接口上发送UDP数据报致使给这个套接口捆绑一个临时端口。这一点意味着除非数据报发送端已经捆绑一个路径名到它的套接口,不然数据报接收端没法发回应答数据报。相似地,对于某个UNIX域数据报套接口的connect调用不会给本套接口绑定一个路径名,这一点不一样于TCP和UDP。
/* unixstrserv01.c */ #include <sys/un.h> #include <errno.h> #include <sys/wait.h> #include <signal.h> #include <sys/socket.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <strings.h> #include <string.h> #define UNIXSTR_PATH "/tmp/unix.str" int main(int argc, char **argv) { int listenfd, connfd; pid_t childpid; socklen_t clilen; struct sockaddr_un cliaddr, servaddr; void sig_chld(int); daemonize("unixstrserver"); listenfd = socket(AF_LOCAL, SOCK_STREAM, 0); unlink(UNIXSTR_PATH); bzero(&servaddr, sizeof(servaddr)); servaddr.sun_family = AF_LOCAL; strcpy(servaddr.sun_path, UNIXSTR_PATH); bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); listen(listenfd, 5); signal(SIGCHLD, sig_chld); for(;;) { clilen = sizeof(cliaddr); if((connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen)) < 0) { if(errno == EINTR) continue; /* back to for() */ else { perror("accept"); exit(1); } } if((childpid = fork()) == 0) { close(listenfd); str_echo(connfd); exit(0); } close(connfd); } } void sig_chld(int signo) { pid_t pid; int stat; while((pid = waitpid(-1, &stat, WNOHANG)) > 0) { printf("child %d terminated\n", pid); } return; }
/* unixstrcli01.c */ #include <sys/un.h> #include <strings.h> #include <sys/un.h> #include <sys/socket.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #define UNIXSTR_PATH "/tmp/unix.str" int main(int argc, char **argv) { int sockfd; struct sockaddr_un servaddr; sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sun_family = AF_LOCAL; strcpy(servaddr.sun_path, UNIXSTR_PATH); connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); str_cli(stdin, sockfd); exit(0); }
其余相关使用到的函数参见:http://www.cnblogs.com/nufangrensheng/p/3587962.html 以及http://www.cnblogs.com/nufangrensheng/p/3544104.html。
/* unixdgserv01.c */ #include <sys/un.h> #include <sys/socket.h> #define UNIXDG_PATH "/tmp/unix.dg" int main(int argc, char **argv) { int sockfd; struct sockaddr_un servaddr, cliaddr; sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0); unlink(UNIXDG_PATH); bzero(&servaddr, sizeof(servaddr)); servaddr.sun_family = AF_LOCAL; strcpy(servaddr.sun_path, UNIXDG_PATH); bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); dg_echo(sockfd, (struct sockaddr *)&cliaddr, sizeof(cliaddr)); }
/* unixdgcli01.c */ #include <sys/un.h> #include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <sys/types.h> #define UNIXDG_PATH "/tmp/unix.dg" int main(int argc, char **argv) { int sockfd; struct sockaddr_un cliaddr, servaddr; sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0); bzero(&cliaddr, sizeof(cliaddr)); cliaddr.sun_family = AF_LOCAL; strcpy(cliaddr.sun_path, tmpnam(NULL)); bind(sockfd, (struct sockaddr *)&cliaddr, sizeof(cliaddr)); bzero(&servaddr, sizeof(servaddr)); servaddr.sun_family = AF_LOCAL; strcpy(servaddr.sun_path, UNIXDG_PATH); dg_cli(stdin, sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); exit(0); }
使用到的相关函数可参考:http://www.cnblogs.com/nufangrensheng/p/3592158.html。
注意
与UDP客户不一样的是,当使用UNIX域数据报协议时,咱们必须显式bind一个路径名到咱们的套接口,这样服务器才会有回送应答的路径名。
当考虑从一个进程到另外一个进程传递打开的描述字时,咱们一般会想到:
(1)fork调用返回后,子进程共享父进程的全部打开的描述字。
(2)exec调用执行以后,全部描述字一般保持打开状态不变。
在(1)中,进程先打开一个描述字,再调用fork,而后父进程关闭这个描述字,子进程则处理这个描述字。这样一个打开的描述字就从父进程传递到子进程。然而咱们也可能想让子进程打开一个描述字并把它传递给父进程。
当前的UNIX系系统提供了用于从一个进程到任一其余进程传递任一打开的描述字的方法。也就是说,这两个进程之间无需存在亲缘关系。这种技术要求首先在这两个进程之间建立一个UNIX域套接口,而后使用sendmsg跨这个UNIX域套接口发送一个特殊消息。这个消息由内核处理,从而把打开的描述字从发送进程传递到接收进程。使用UNIX域套接口的描述字传递方法是最便于移植的编程技术。
在两个进程之间传递描述字涉及的步骤以下:
(1)建立一个字节流或数据报的UNIX域套接口。
若是目标是fork一个子进程,让子进程打开待传递的描述字,再把它传递回父进程,那么父进程能够预先调用socketpair建立一个可用于在父子进程之间交换描述字的流管道。
若是进程之间没有亲缘关系,那么服务器进程必须建立一个UNIX域字节流套接口,bind一个路径到该套接口,以容许客户进程connect到该套接口。客户而后能够向服务器发送一个打开某个描述字的请求,服务器再把该描述字经过UNIX域套接口传递回客户。客户和服务器之间也可使用UNIX域数据报套接口,不过这么作缺少优点,并且数据报存在被丢弃的可能性。
(2)发送进程经过调用返回描述字的任一UNIX函数打开一个描述字,这些函数的例子有:open、pipe、mkfifo、socket和accept。能够在进程之间传递的描述字不限类型,这就是咱们称这种技术为“描述字传递”而不是“文件描述字传递”的缘由。
(3)发送进程建立一个msghdr结构(http://www.cnblogs.com/nufangrensheng/p/3567376.html),其中含有待传递的描述字。POSIX规定描述字做为辅助数据(msghdr结构的msg_control成员)发送。发送进程调用sendmsg跨来自步骤(1)的UNIX域套接口发送该描述字。至此咱们说这个描述字“在飞行中(in flight)”。即便发送进程在调用sendmsg以后但在接收进程调用recvmsg以前就关闭了该描述字,对于接收进程它仍然保持打开状态。发送一个描述字致使该描述字的引用计数加1.
(4)接收进程调用recvmsg在来自步骤(1)的UNIX域套接口上接收这个描述字。这个描述字在接收进程中的描述字号不一样于它在发送进程中的描述子号是正常的。传递一个描述字并非传递一个描述字号,而是涉及在接收进程中建立一个新的描述字,而这个描述字指引的内核中文件表项和发送进程中飞行前的那个描述字指引的相同。
客户和服务器之间必须存在某种应用协议,以便描述字的接收进程预先知道什么时候期待接收。另外,在期待接收描述字的recvmsg调用中应该避免使用MSG_PEEK标志,不然后果不可预料。