Unix域协议并不是一个实际的协议族,而是在单个主机上执行客户/服务器通信的一种方法。
Unix域提供两类套接字:字节流套接字和数据报套接字。
使用Unix域套接字的理由:
1.Unix域套接字往往比通信两端位于同一个主机的TCP套接字快出一倍。
2.Unix域套接字可用于在同一个主机上的不同进程之间传递描述符。
3.Unix域套接字较新的实现把客户的凭证(用户ID和组ID)提供给服务器,从而能够提供额外的安全检查措施。
Unix域用于标识客户和服务器的协议地址是普通文件系统中的路径名。
Unix域套接字地址结构
#include<sys/un.h> struct sockaddr_un { sa_family_t sun_family; //AF_LOCAL char sun_path[104]; //路径名 }
存放在sun_path数组中的路径名必须以空字符结尾。
实现提供的SUN_LEN宏以一个指向sockaddr_un结构的指针为参数并返回该结构的长度,其中包含路径名中非空字节数。
Unix域套接字的bind调用
创建一个Unix域套接字,往其上bind一个路径名,再调用getsockname输出这个绑定的路径名
#include "unp.h" int main(int argc, char *argv[]) { int sockfd; socklen_t len; struct sockaddr_un addr1, addr2; if (argc != 2) err_quit("usage: unixbind <pathname>"); 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, (SA *) &addr1, SUN_LEN(&addr1)); len = sizeof(addr2); getsockname(sockfd, (SA *) &addr2, &len); //调用getsockname取得刚绑定的路径名到addr2中。 printf("bound name = %s, returned len = %d \n",addr2.sun_path, len); exit(0); }
socketpair函数
socketpair函数创建两个随后连接起来的套接字。
int socketpair(int family, int type, int protocol, int sockfd[2]);
family参数必须为AF_LOCAL,protocol参数必须为0.type参数可以是SOCK_STREAM,也可以是SOCK_DGRAM。
新创建的两个套接字描述符作为sockfd[0]和sockfd[1]返回。
type参数为SOCK_STREAM创建得到的为流管道,它与调用pipe创建的普通UNIX管道类似。
使用Unix域TCP协议的回射客户/服务器程序
先定义两个使用的路径名:
#define UNIXSTR_PATH "/tmp/unix.str" /* Unix domain stream */ #define UNIXDG_PATH "/tmp/unix.dg" /* Unix domain datagram */
服务器端:
/* echo_localserv.c */ #include "unp.h" int main(int argc, char *argv[]) { int listenfd, connfd; pid_t childpid; socklen_t clilen; struct sockaddr_un cliaddr, servaddr; void sig_chld(int); 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, (SA *) &servaddr, sizeof(servaddr)); listen(listenfd,LISTENQ); signal(SIGCHLD, sig_chld); while(1) { clilen = sizeof(cliaddr); if( (connfd = accept(listenfd, (SA *) &cliaddr, *clilen)) < 0) { if (errno == EINTR) continue; //back to for() else err_sys("accept error"); } 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; }
客户端:
/* echo_localclnt.c */ #include "unp.h" int main(int argc, char *argv[]) { int sockfd; struct sockaddr_in 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, (SA *) &servaddr, sizeof(servaddr)); str_cli(stdin,sockfd); //do it all exit(0); }
使用Unix域UDP协议的客户/服务器程序
服务器端:
/* echo_unpserv.c */ #include "unp.h" int main(int argc, char *argv[]) { int sockfd; struct sockaddr_un servaddr, cliaddr; sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0); unlink(UNIXD_PATH); bzero(&servaddr, sizeof(servaddr)); servaddr.sun_family = AF_LOCAL; strcpy(servaddr.sun_path, UNIXDG_PATH); bind(sockfd, (SA *) &servaddr, sizeof(servaddr)); dg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr)); }
客户端:
/* echo_unpclnt.c */ #include "unp.h" 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)); /*与TCP客户不同的是,当使用UNIX域数据报协议时,必须显式bind一个路径名*/ bind(sockfd, (SA *) &cliaddr, sizeof(cliaddr)); bzero(&servaddr, sizeof(servaddr)); servaddr.sun_family = AF_LOCAL; strcpy(servaddr.sun_path, UNIXDG_PATH); dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr)); exit(0); }
描述符传递的例子
一个例子:mycat
通过命令行参数取得一个路径名,打开这个文件,再把文件的内容复制到标准输出。
调用 my_open函数,创建一个流管道,并调用fork和exec启动执行另一个程序,期待输出的文件由这个程序打开。该程序随后必须把打开的描述符通过流管道传递回父进程。
下图:调用socketpair创建一个流管道后的mycat进程。我们以[0]和[1]标识socketpair返回的两个描述符。
mycat进程接着调用fork,子进程再调用exec执行openfile程序。父进程关闭[1]描述符,子进程关闭[0]描述符。
mycat.c:
#include "unp.h" int myopen(const char *, int); int main(int argc, char *argv[]) { int fd, n; char buf[BUFFSIZE]; if (argc != 2) err_quit("usage: mycat <pathname>"); if ((fd = my_open(argv[1], O_RDONLY)) < 0 ) err_sys("cannot open %s",argv[1]); while ( (n = read(fd, buff, BUFFSIZE)) > 0) write(STDOUT_FILENO, buff, n); exit(0); } my_open(const char *pathname, int mode) { int fd, sockfd[2], status; pid_t childpid; char c, argsockfd[10], argmode[10]; socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd);1 if ( (childpid = fork()) == 0) { close(sockfd[0]); snprintf(argcsockfd, sizeof(argsockfd), "%d", sockfd[1]); snprintf(argmode, sizeof(argmode), "%d", mode); excel("./openfile","openfile",argsockfd,pathname,argmode,(char *) NULL); err_sys("excel error"); } /* parent process - wait for the child to terminate */ close(sockfd[1]); waitpid(childpid, *status, 0); if (WIFEXITED(status) == 0) err_quit("child did not terminate"); if (( status = WEXITSTATUS(status)) == 0) read_fd(sock[0], &c, 1, &fd); else { errno = status; fd = -1; } close(sockfd[0]); return(fd); }