UNIX域协议是在单个主机上执行客户/服务器通信的一种方法
使用UNIX域套接字有以下3个理由:
1.UNIX域套接字往往比通信两端位于同一个主机的TCP套接字快出一倍
2.UNIX域套接字可用于在同一个主机上的不同进程之间传递描述符
3.UNIX域套接字较新的实现把客户的凭证提供给服务器,从而能够提供额外的安全检查措施
UNIX域中用于标识客户和服务器的协议地址是普通文件系统的路径名。这些路径名不是普通的UNIX文件:
除非他们和UNIX域套接字关联起来,否则无法读写这些文件。
可以查看之前apue的学习笔记 http://www.cnblogs.com/runnyu/p/4650843.html
UNIX域套接字的地址结构
struct sockaddr_un { sa_family_t sun_family; /* AF_LOCAL */ char sun_path[104]; /* pathname */ };
实现提供了SUN_LEN宏以一个指向sockaddr_un结构的指针作为参数并返回该结构的长度
UNIX域套接字的bind调用
下面程序创建一个UNIX域套接字,往其上bind一个路径名,再调用getsockname输出这个绑定的路径名
1 #include "unp.h" 2 3 int 4 main(int argc, char **argv) 5 { 6 int sockfd; 7 socklen_t len; 8 struct sockaddr_un addr1, addr2; 9 10 if (argc != 2) 11 err_quit("usage: unixbind <pathname>"); 12 13 sockfd = Socket(AF_LOCAL, SOCK_STREAM, 0); 14 15 unlink(argv[1]); /* OK if this fails */ 16 17 bzero(&addr1, sizeof(addr1)); 18 addr1.sun_family = AF_LOCAL; 19 strncpy(addr1.sun_path, argv[1], sizeof(addr1.sun_path)-1); 20 Bind(sockfd, (SA *) &addr1, SUN_LEN(&addr1)); 21 22 len = sizeof(addr2); 23 Getsockname(sockfd, (SA *) &addr2, &len); 24 printf("bound name = %s, returned len = %d ", addr2.sun_path, len); 25 26 exit(0); 27 }
先调用unlink删除这个路径名,以防bind失败
socketpair函数
socketpair函数创建两个随后连接起来的套接字。本函数仅适用于UNIX套接字
#include <sys/socket.h> int socketpair(int domain,int type,int protocol,int sockfd[2]);
family参数必须为AF_LOCAL,protocol参数必须为0,type参数既可以是SOCK_STREAM,也可以是SOCK_DGRAM。新创建的两个套接字描述符作为sockfd[0]和sockfd[1]返回
UNIX域字节流客户/服务器程序
下面将第五章的TCP回射客户/服务器程序重新编写成使用UNIX域套接字
服务器程序。unp.h定义的UNIXSTR_PATH常值为/tmp/unix.str
1 #include "unp.h" 2 3 int 4 main(int argc, char **argv) 5 { 6 int listenfd, connfd; 7 pid_t childpid; 8 socklen_t clilen; 9 struct sockaddr_un cliaddr, servaddr; 10 void sig_chld(int); 11 12 listenfd = Socket(AF_LOCAL, SOCK_STREAM, 0); 13 14 unlink(UNIXSTR_PATH); 15 bzero(&servaddr, sizeof(servaddr)); 16 servaddr.sun_family = AF_LOCAL; 17 strcpy(servaddr.sun_path, UNIXSTR_PATH); 18 19 Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); 20 21 Listen(listenfd, LISTENQ); 22 23 Signal(SIGCHLD, sig_chld); 24 25 for ( ; ; ) { 26 clilen = sizeof(cliaddr); 27 if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0) { 28 if (errno == EINTR) 29 continue; /* back to for() */ 30 else 31 err_sys("accept error"); 32 } 33 34 if ( (childpid = Fork()) == 0) { /* child process */ 35 Close(listenfd); /* close listening socket */ 36 str_echo(connfd); /* process request */ 37 exit(0); 38 } 39 Close(connfd); /* parent closes connected socket */ 40 } 41 }
客户程序
1 #include "unp.h" 2 3 int 4 main(int argc, char **argv) 5 { 6 int sockfd; 7 struct sockaddr_un servaddr; 8 9 sockfd = Socket(AF_LOCAL, SOCK_STREAM, 0); 10 11 bzero(&servaddr, sizeof(servaddr)); 12 servaddr.sun_family = AF_LOCAL; 13 strcpy(servaddr.sun_path, UNIXSTR_PATH); 14 15 Connect(sockfd, (SA *) &servaddr, sizeof(servaddr)); 16 17 str_cli(stdin, sockfd); /* do it all */ 18 19 exit(0); 20 }
UNIX域数据报客户/服务器程序
下面将第八章UDP回射客户/服务器程序重新编写成使用UNIX域数据报套接字。
服务器程序。unp.h定义的UNIXDG_PATH常值为/tmp/unix.dg
1 #include "unp.h" 2 3 int 4 main(int argc, char **argv) 5 { 6 int sockfd; 7 struct sockaddr_un servaddr, cliaddr; 8 9 sockfd = Socket(AF_LOCAL, SOCK_DGRAM, 0); 10 11 unlink(UNIXDG_PATH); 12 bzero(&servaddr, sizeof(servaddr)); 13 servaddr.sun_family = AF_LOCAL; 14 strcpy(servaddr.sun_path, UNIXDG_PATH); 15 16 Bind(sockfd, (SA *) &servaddr, sizeof(servaddr)); 17 18 dg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr)); 19 }
客户程序
1 #include "unp.h" 2 3 int 4 main(int argc, char **argv) 5 { 6 int sockfd; 7 struct sockaddr_un cliaddr, servaddr; 8 9 sockfd = Socket(AF_LOCAL, SOCK_DGRAM, 0); 10 11 bzero(&cliaddr, sizeof(cliaddr)); /* bind an address for us */ 12 cliaddr.sun_family = AF_LOCAL; 13 strcpy(cliaddr.sun_path, tmpnam(NULL)); 14 15 Bind(sockfd, (SA *) &cliaddr, sizeof(cliaddr)); 16 17 bzero(&servaddr, sizeof(servaddr)); /* fill in server's address */ 18 servaddr.sun_family = AF_LOCAL; 19 strcpy(servaddr.sun_path, UNIXDG_PATH); 20 21 dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr)); 22 23 exit(0); 24 }
与UDP客户不同的是:使用UNIX域数据报协议时,我们必须显式bind一个路径名到我们的套接字,这样服务器才会有能回射应答的路径名。
描述符传递
下面两种方法可以把一个描述符从一个进程传递到另一个进程
1.fork调用返回之后,子进程共享父进程的所有打开的描述符
2.exec调用执行之后,所有描述符通常保持打开状态不变
第一种方法中:进程先打开一个描述符,再调用fork,然后父进程关闭这个描述符,子进程则处理这个描述符。
这样一个打开的描述符就从父进程传递到子进程
当前UNIX系统提供了用于从一个进程向任一其他进程传递任一打开的描述符的方法,这两个进程无需存在亲缘关系
这种技术要求首先在这两个进程之间创建一个UNIX域套接字,然后使用sendmsg跨这个套接字发送一个特殊消息。
这个消息由内核来专门处理,会把打开的描述符从发送进程传递到接收进程。
在两个进程之间传递描述符涉及的步骤如下:
1.创建一个字节流或数据报流的UNIX域套接字
2.发送进程通过调用返回描述符的任一UNIX函数打开一个描述符,这些函数的例子有:open、pipe、mkfifo、socket和accept
3.发送进程创建一个msghdr结构,其中含有待传递的描述符
4.接收进程调用recvmsg在来自步骤1的UNIX域套接字上接收这个描述符
描述符传递的例子
mycat程序:通过命令行参数取得一个路径名,打开这个文件,再把文件的内容复制到标准输出。
该程序调用我们名为my_open的函数:my_open创建一个流管道,并调用fork和exec启动执行一个程序,期待输出的文件由这个程序打开。该程序随后必须把打开的描述符通过流管道传递给父进程。
下面展示上述步骤:
1.通过调用socketpair创建一个流管道后的mycat进程。
2.mycat进程接着调用fork,子进程再调用exec执行openfile程序。父进程关闭[1]描述符,子进程关闭[0]描述符
3.父进程给openfile程序传递三条信息(调用exec时进行传递):待打开文件的路径名、打开方式、流管道本进程端对应的描述符号
通过执行另一个程序打开文件的优势在于:另一个程序可以是一个setuid到root的程序,能够打开我们通常没有打开权限的文件。
下面是mycat程序(使用别的程序打开,然后把描述符传递过来进行读取该文件)
1 #include "unp.h" 2 3 int my_open(const char *, int); 4 5 int 6 main(int argc, char **argv) 7 { 8 int fd, n; 9 char buff[BUFFSIZE]; 10 11 if (argc != 2) 12 err_quit("usage: mycat <pathname>"); 13 14 if ( (fd = my_open(argv[1], O_RDONLY)) < 0) 15 err_sys("cannot open %s", argv[1]); 16 17 while ( (n = Read(fd, buff, BUFFSIZE)) > 0) 18 Write(STDOUT_FILENO, buff, n); 19 20 exit(0); 21 }
下面是my_open函数
1 #include "unp.h" 2 3 int 4 my_open(const char *pathname, int mode) 5 { 6 int fd, sockfd[2], status; 7 pid_t childpid; 8 char c, argsockfd[10], argmode[10]; 9 10 Socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd); 11 12 if ( (childpid = Fork()) == 0) { /* child process */ 13 Close(sockfd[0]); 14 snprintf(argsockfd, sizeof(argsockfd), "%d", sockfd[1]); 15 snprintf(argmode, sizeof(argmode), "%d", mode); 16 execl("./openfile", "openfile", argsockfd, pathname, argmode, 17 (char *) NULL); 18 err_sys("execl error"); 19 } 20 21 /* parent process - wait for the child to terminate */ 22 Close(sockfd[1]); /* close the end we don't use */ 23 24 Waitpid(childpid, &status, 0); 25 if (WIFEXITED(status) == 0) 26 err_quit("child did not terminate"); 27 if ( (status = WEXITSTATUS(status)) == 0) 28 Read_fd(sockfd[0], &c, 1, &fd); 29 else { 30 errno = status; /* set errno value from child's status */ 31 fd = -1; 32 } 33 34 Close(sockfd[0]); 35 return(fd); 36 }
my_open函数返回的描述符是调用read_fd返回的描述符(调用recvmsg接收一个描述符)
1 /* include read_fd */ 2 #include "unp.h" 3 4 ssize_t 5 read_fd(int fd, void *ptr, size_t nbytes, int *recvfd) 6 { 7 struct msghdr msg; 8 struct iovec iov[1]; 9 ssize_t n; 10 11 #ifdef HAVE_MSGHDR_MSG_CONTROL 12 union { 13 struct cmsghdr cm; 14 char control[CMSG_SPACE(sizeof(int))]; 15 } control_un; 16 struct cmsghdr *cmptr; 17 18 msg.msg_control = control_un.control; 19 msg.msg_controllen = sizeof(control_un.control); 20 #else 21 int newfd; 22 23 msg.msg_accrights = (caddr_t) &newfd; 24 msg.msg_accrightslen = sizeof(int); 25 #endif 26 27 msg.msg_name = NULL; 28 msg.msg_namelen = 0; 29 30 iov[0].iov_base = ptr; 31 iov[0].iov_len = nbytes; 32 msg.msg_iov = iov; 33 msg.msg_iovlen = 1; 34 35 if ( (n = recvmsg(fd, &msg, 0)) <= 0) 36 return(n); 37 38 #ifdef HAVE_MSGHDR_MSG_CONTROL 39 if ( (cmptr = CMSG_FIRSTHDR(&msg)) != NULL && 40 cmptr->cmsg_len == CMSG_LEN(sizeof(int))) { 41 if (cmptr->cmsg_level != SOL_SOCKET) 42 err_quit("control level != SOL_SOCKET"); 43 if (cmptr->cmsg_type != SCM_RIGHTS) 44 err_quit("control type != SCM_RIGHTS"); 45 *recvfd = *((int *) CMSG_DATA(cmptr)); 46 } else 47 *recvfd = -1; /* descriptor was not passed */ 48 #else 49 /* *INDENT-OFF* */ 50 if (msg.msg_accrightslen == sizeof(int)) 51 *recvfd = newfd; 52 else 53 *recvfd = -1; /* descriptor was not passed */ 54 /* *INDENT-ON* */ 55 #endif 56 57 return(n); 58 } 59 /* end read_fd */ 60 61 ssize_t 62 Read_fd(int fd, void *ptr, size_t nbytes, int *recvfd) 63 { 64 ssize_t n; 65 66 if ( (n = read_fd(fd, ptr, nbytes, recvfd)) < 0) 67 err_sys("read_fd error"); 68 69 return(n); 70 }
下面给出openfile程序:打开一个文件并传递回其描述符
1 #include "unp.h" 2 3 int 4 main(int argc, char **argv) 5 { 6 int fd; 7 8 if (argc != 4) 9 err_quit("openfile <sockfd#> <filename> <mode>"); 10 11 if ( (fd = open(argv[2], atoi(argv[3]))) < 0) 12 exit( (errno > 0) ? errno : 255 ); 13 14 if (write_fd(atoi(argv[1]), "", 1, fd) < 0) 15 exit( (errno > 0) ? errno : 255 ); 16 17 exit(0); 18 }
它调用write_fd调用sendmsg跨一个UNIX域套接字发送一个描述符
1 /* include write_fd */ 2 #include "unp.h" 3 4 ssize_t 5 write_fd(int fd, void *ptr, size_t nbytes, int sendfd) 6 { 7 struct msghdr msg; 8 struct iovec iov[1]; 9 10 #ifdef HAVE_MSGHDR_MSG_CONTROL 11 union { 12 struct cmsghdr cm; 13 char control[CMSG_SPACE(sizeof(int))]; 14 } control_un; 15 struct cmsghdr *cmptr; 16 17 msg.msg_control = control_un.control; 18 msg.msg_controllen = sizeof(control_un.control); 19 20 cmptr = CMSG_FIRSTHDR(&msg); 21 cmptr->cmsg_len = CMSG_LEN(sizeof(int)); 22 cmptr->cmsg_level = SOL_SOCKET; 23 cmptr->cmsg_type = SCM_RIGHTS; 24 *((int *) CMSG_DATA(cmptr)) = sendfd; 25 #else 26 msg.msg_accrights = (caddr_t) &sendfd; 27 msg.msg_accrightslen = sizeof(int); 28 #endif 29 30 msg.msg_name = NULL; 31 msg.msg_namelen = 0; 32 33 iov[0].iov_base = ptr; 34 iov[0].iov_len = nbytes; 35 msg.msg_iov = iov; 36 msg.msg_iovlen = 1; 37 38 return(sendmsg(fd, &msg, 0)); 39 } 40 /* end write_fd */