![](https://img-my.csdn.net/uploads/201212/30/1356867984_7836.jpg)
17.1 引言
17.2 基于STREAMS的管道
17.3 UNIX域套接字
17.3.1 命名UNIX域套接字
17.3.2 唯一连接
17.4 传送文件描述符
17.4.1 经由基于STREAMS的管道传送文件描述符
17.4.2 经由UNIX域套接字传送文件描述符
sendmsg和recvmsg使用
在UNIX域套接字上发送凭证
17.5 OPEN服务器版本1
|
17.1 引言
前面两章讨论了UNIX系统提供的各种IPC,包括管道和套接字。本章介绍两种高级IPC:基于STREAMS的管道(STREAMS-based pipe)以及UNIX域套接字(UNIX domain socket)。
17.2 基于STREAMS的管道
17.3 UNIX域套接字
UNIX域套接字用于在同一台机器上运行的进程之间的通信。虽然因特网域套接字可用于同一目的,但UNIX域套接字的效率更高。UNIX域套接字仅仅复制数据;他们并不执行协议处理,不需要添加或删除网络报头,无需计算检验和,不要产生顺序号,无需发送确认报文。
小结:采用UNIX域套接字进行进程间通信的原因在于 UNIX域套接字仅仅复制数据。
UNIX域套接字提供流和数据报两种接口。UNIX域数据报服务是可靠地,既不会丢失消息也不会传递出错。
UNIX域套接字是套接字和管道之间的混合物。为了创建一对非命名的、相互连接的UNIX域套接字,用户可以使用它们面向网络的域套接字接口,也可以使用socketpair函数。
#include <sys/socket.h> int socketpair(int domain,int type,int protocol,int sockfd[2]); return value: 若成功返回0,若出错返回-1. |
为了得到全双工管道,我们必须直接调用socketpair。
小结:管道的缺陷有两个:1)半双工 2)必须由共同祖先的进程才能通信。为了解决这个缺陷使用UNIX域套接字,利用socketpair函数创建全双工管道。
17.3.1 命名UNIX域套接字
17.3.2 唯一连接
17.4 传送文件描述符
概述部分:关于文件共享的介绍
17.4.1 经由基于STREAMS的管道传送文件描述符
17.4.2 经由UNIX域套接字传送文件描述符
struct iovec { /* Scatter/gather array items */ void *iov_base; /* Starting address */ size_t iov_len; /* Number of bytes to transfer */ }; struct msghdr { void *msg_name; /* optional address */ socklen_t msg_namelen; /* size of address */ struct iovec *msg_iov; /* scatter/gather array */ size_t msg_iovlen; /* # elements in msg_iov */ void *msg_control; /* ancillary data, see below */ socklen_t msg_controllen; /* ancillary data buffer len */ int msg_flags; /* flags on received message */ }; struct cmsghdr {(控制信息首部结构体 control-message-header) socklen_t cmsg_len; /* data byte count, including hdr */ int cmsg_level; /* originating protocol */ int cmsg_type; /* protocol-specific type */ /* followed by unsigned char cmsg_data[]; */ }; |
Here msg_name and msg_namelen specify the source address if the socket is unconnected.msg_name may be given as a null pointer if no names are desired or required. 如果socket是无连接的,那么msg_name和msg_namelen用于指定源地址。 The fields msg_iov and msg_iovlen describe scatter-gather locations, as discussed in readv(2) msg_iov和msg_iovlen可以指定多个缓冲区构成的数组(散布读和聚集写)。 msg_flags字段包含了说明所接受到消息的标识,这些标识如表16-9中。 msg_control和msg_controllen控制信息的传送和接收。 msg_control字段指向cmsghdr(控制信息首部)结构, msg_controllen字段包含控制信息的字节数。 |
为了发送文件描述符, 将cmsg_len设置为cmsghdr结构的长度加一个整型(描述符)的长度; cmsg_level字段设置为SOL_SOCKET, cmsg_type字段设置为SCM_RIGHTS,用以指明我们在传送访问权。(SCM:套接字级控制消息,socket_level_control_message)。访问权仅能通过UNIX域套接字传送。描述符紧随cmsg_type字段之后存放,用CMSG_DATA宏获得该整型量的指针。 |
![](https://img-my.csdn.net/uploads/201212/30/1356868030_7827.jpg)
int send_fd(int fd,int fd_to_send)
{ struct iovec iov[1]; struct msghdr msg; char buf[2]; iov[0].iov_base=buf; iov[0].iov_len=2; msg.msg_iov=iov; msg.msg_iovlen=1; msg.msg_name=NULL; msg.msg_namelen=0; if(fd_to_send<0) { msg.msg_control=NULL; msg.msg_controllen=0; buf[1]=-fd_to_send; if(buf[1]==0) buf[1]=1; } else { if(cmptr==NULL && (cmptr=malloc(CONTROLLEN))==NULL) return(-1); cmptr->cmsg_level=SOL_SOCKET; cmptr->cmsg_type=SCM_RIGHTS; cmptr->cmsg_len=CONTROLLEN; msg.msg_control=cmptr; msg.msg_controllen=CONTROLLEN; *(int *)CMSG_DATA(cmptr)=fd_to_send; buf[1]=0; } buf[0]=0; if(sendmsg(fd,&msg,0)!=2) return(-1); return(0); } int send_err(int fd,int errcode,const char * msg) { int n; if((n=strlen(msg))>0) if(writen(fd,msg,n)!=n) return(-1); if(errcode>=0) errcode=-1; if(send_fd(fd,errcode)<0) return(-1); return(0); } |
|
![](https://img-my.csdn.net/uploads/201212/30/1356868081_2997.jpg)
在UNIX域套接字上发送凭证
![](https://img-my.csdn.net/uploads/201212/30/1356868094_2862.jpg)
open服务器版本1
/*client.c * gcc -Wall client.c -o client */ #include<stdio.h> #include<stdlib.h> #include<fcntl.h> #include<unistd.h> #include<sys/socket.h> #include<sys/un.h> #include<errno.h> #include<sys/uio.h> //#include"error.c" #define MAXLINE 200 #define CL_OPEN "open" #define BUFFSIZE 8192 #define CONTROLLEN CMSG_LEN(sizeof(int)) static struct cmsghdr * cmptr=NULL; int csopen(char *,int); int s_pipe(int fd[2]); int recv_fd(int fd,ssize_t (*userfunc)(int,const void *,size_t)); int main(int argc,char * argv[]) { int n,fd; char buf[BUFFSIZE],line[MAXLINE]; printf("\n\nPlease input the name of the file:"); while(fgets(line,MAXLINE,stdin)!=NULL) { if(line[strlen(line)-1]=='\n') line[strlen(line)-1]=0; printf("\n\nnow do the csopen:line :%s\n",line); if((fd=csopen(line,O_RDONLY))<0) continue; while((n=read(fd,buf,BUFFSIZE))>0) { if(write(STDOUT_FILENO,buf,n)!=n) printf("client:write error"); } if(n<0) printf("client:read error"); close(fd); } exit(0); } int s_pipe(int fd[2]) { return(socketpair(AF_UNIX,SOCK_STREAM,0,fd)); } int csopen(char * name,int oflag) { pid_t pid; int len; char buf[10]; struct iovec iov[3]; static int fd[2]={-1,-1}; if(fd[0]<0) { if(s_pipe(fd)<0) printf("s_pipe error"); if((pid=fork())<0) printf("fork error"); else if(pid==0) { close(fd[0]); if(fd[1]!=STDIN_FILENO && dup2(fd[1],STDIN_FILENO)!=STDIN_FILENO) printf("dup2 error to stdin"); if(fd[1]!=STDOUT_FILENO && dup2(fd[1],STDOUT_FILENO)!=STDOUT_FILENO) printf("dup2 error to stdout"); if(execl("./chap17_open_server","chap17_open_server",(char *)0)<0) printf("execl error"); } close(fd[1]); } sprintf(buf," %d",oflag); iov[0].iov_base=CL_OPEN " "; iov[0].iov_len=strlen(CL_OPEN)+1; iov[1].iov_base=name; iov[1].iov_len=strlen(name); iov[2].iov_base=buf; iov[2].iov_len=strlen(buf)+1; len=iov[0].iov_len+iov[1].iov_len+iov[2].iov_len; if(writev(fd[0],&iov[0],3)!=len) printf("writev error"); return(recv_fd(fd[0],write)); } int recv_fd(int fd,ssize_t (*userfunc)(int,const void *,size_t)) { int newfd,nr,status; char * ptr; char buf[MAXLINE]; struct iovec iov[1]; struct msghdr msg; status=-1; for(;;) { iov[0].iov_base=buf; iov[0].iov_len=sizeof(buf); msg.msg_iov=iov; msg.msg_iovlen=1; msg.msg_name=NULL; msg.msg_namelen=0; if(cmptr==NULL && (cmptr=malloc(CONTROLLEN))==NULL) return(-1); msg.msg_control=cmptr; msg.msg_controllen=CONTROLLEN; if((nr=recvmsg(fd,&msg,0))<0) { printf("recvmsg error"); } else if(nr==0) { printf("connection closed by server"); return(-1); } for(ptr=buf;ptr<&buf[nr];) { if(*ptr++==0) { if(ptr!=&buf[nr-1]) printf("message format error"); status=*ptr & 0xff; if(status==0) { if(msg.msg_controllen!=CONTROLLEN) printf("status=0 but no fd"); newfd=*(int *)CMSG_DATA(cmptr); } else { newfd=-status; } nr-=2; } } if(nr>0 && (*userfunc)(STDERR_FILENO,buf,nr)!=nr) return(-1); if(status>=0) return(newfd); } } |
/*server.c #include<stdio.h>* gcc -Wall server.c -o server */ #include<stdlib.h> #include<fcntl.h> #include<unistd.h> #include<sys/socket.h> #include<sys/un.h> #include<errno.h> //#include"error.c" #define MAXLINE 200 #define CL_OPEN "open" #define MAXARGC 50 #define WHITE " " #define CONTROLLEN CMSG_LEN(sizeof(int)) char errmsg[4096]; int oflag; char * pathname; static struct cmsghdr * cmptr=NULL; int cli_args(int,char **); void request(char *,int,int); int send_fd(int fd,int fd_to_send); ssize_t writen(int fd,const void * ptr,size_t n); int send_err(int fd,int errcode,const char * msg); int buf_args(char * buf,int (*optfunc)(int,char **)); int main(int argc,char * argv[]) { printf("begin into server\n"); int nread; char buf[MAXLINE]; for(;;) { if((nread=read(STDIN_FILENO,buf,MAXLINE))<0) printf("read error on stream pipe"); else if(nread==0) break; printf("server buf :%s\n",buf); request(buf,nread,STDOUT_FILENO); } exit(0); } void request(char * buf,int nread,int fd) { write(STDOUT_FILENO,buf,strlen(buf)); int newfd; if(buf[nread-1]!=0) { sprintf(errmsg,"request not null terminated:%*.*s\n",nread,nread,buf); send_err(fd,-1,errmsg); return; } if(buf_args(buf,cli_args)<0) { send_err(fd,-1,errmsg); return; } if((newfd=open(pathname,oflag))<0) { sprintf(errmsg,"can't open %s:%s\n",pathname,strerror(errno)); send_err(fd,-1,errmsg); return; } if(send_fd(fd,newfd)<0) printf("send_fd error"); close(newfd); } int buf_args(char * buf,int (*optfunc)(int,char **)) { char * ptr,* argv[MAXARGC]; int argc; if((ptr=strtok(buf,WHITE))==NULL) return(-1); argv[argc=0]=buf; while((ptr=strtok(NULL," "))!=NULL) { if(++argc>=MAXARGC-1) return(-1); argv[argc]=ptr; } argv[++argc]=NULL; return((*optfunc)(argc,argv)); } int cli_args(int argc,char ** argv) { if(argc!=3 || strcmp(argv[0],CL_OPEN)!=0) { strcpy(errmsg,"cli_args usage:<pathname><oflag>\n\n"); return(-1); } pathname=argv[1]; oflag=atoi(argv[2]); return(0); } int send_fd(int fd,int fd_to_send) { struct iovec iov[1]; struct msghdr msg; char buf[2]; iov[0].iov_base=buf; iov[0].iov_len=2; msg.msg_iov=iov; msg.msg_iovlen=1; msg.msg_name=NULL; msg.msg_namelen=0; if(fd_to_send<0) { msg.msg_control=NULL; msg.msg_controllen=0; buf[1]=-fd_to_send; if(buf[1]==0) buf[1]=1; } else { if(cmptr==NULL && (cmptr=malloc(CONTROLLEN))==NULL) return(-1); cmptr->cmsg_level=SOL_SOCKET; cmptr->cmsg_type=SCM_RIGHTS; cmptr->cmsg_len=CONTROLLEN; msg.msg_control=cmptr; msg.msg_controllen=CONTROLLEN; *(int *)CMSG_DATA(cmptr)=fd_to_send; buf[1]=0; } buf[0]=0; if(sendmsg(fd,&msg,0)!=2) return(-1); return(0); } int send_err(int fd,int errcode,const char * msg) { int n; if((n=strlen(msg))>0) if(writen(fd,msg,n)!=n) return(-1); if(errcode>=0) errcode=-1; if(send_fd(fd,errcode)<0) return(-1); return(0); } ssize_t writen(int fd,const void * ptr,size_t n) { size_t nleft; ssize_t nwritten; nleft=n; while(nleft>0) { if((nwritten=write(fd,ptr,nleft))<0) { if(nleft==n) return(-1); else break; } else if(nwritten==0) break; nleft-=nwritten; ptr+=nwritten; } return(n-nleft); } |
运行:$./client
输入文件路径 比如 open /tmp/test.txt
关于17.4~17.5小结
17.4 传送文件描述符
讲述了如何构建struct msghdr 以及通过sendmsg和recvmsg发送和接收消息
17.5 服务器版本1
讲述了如何利用socketpair创建一对无名管道,然后通过sendmsg由服务器进程发送文件的fd给客户端进程,客户端进程通过recvmsg接收服务器进程发来的fd。
![](https://img-my.csdn.net/uploads/201212/30/1356868223_7246.jpg)