进程间传递文件描述符
第一步:初始化socketpair类型描述符(管道,全双工) #include <sys/types.h> #include <sys/socket.h> int socketpair(int domain, int type, int protocol, int sv\[2\]);
int fds\[2\]; //和无名管道不一样,无名管道只能用于父子进程之间,现在我们要用于网络; socketpair(AF\_LOCAL,SOCK\_STREAM,0,fds); //local,是因为控制信息只能在本地传 | 第二步:sendmsg发送描述符 ssize\_t sendmsg(int sockfd, const struct msghdr \*msg, int flags); //内核控制信息传递接口,可以传递文件描述符 sockfd 即sockpair初始化的描述符 fds\[1\]; |
1).定义结构体 struct msghdr msg; sendmsg 关键是初始化 msghdr结构体
| |||
unsigned char \*CMSG\_DATA(struct cmsghdr \*cmsg); //把结构体传进去,返回要写入信息的首地址(待会要里面放fd(一个整形数)),也就是最后一块空间的首地址,最后面/\*...\*/中要放的信息 size\_t CMSG\_LEN(size\_t length); //得出变长结构体大小16字节(这个是针对我们后面例子,传描述符,我们自己算的),待会分配空间要用到(前面三个成员指针3\*4,最后一个int型,一共16) //这个接口的原理就是拿到我们要传递的参数的大小length,进去加上12,返回16 | |||
首先定义 struct cmsghdr \*cmsg 指针 cmsg\_len 中存取cmsghdr结构体的长度,通过CMSG\_LEN进行计算,我们传递的fd的大小为整型四个字节,所以 size\_t CMSG\_LEN(size\_t length); int len = CMSG\_LEN(sizeof(int));(16字节) 然后为结构体申请空间:
|
#include<sys/uio.h> ssize\_t readv(int fd, const struct iovec \*iov, int iovcnt); | ssize\_t writev(int fd, const struct iovec \*iov, int iovcnt); //这个函数可以传多个结构体,第二个数要传的结构体指针,第三个是要传的结构体个数 |
第三步:recvmsg 接收文件描述符,接收的 msghdr 结构体初始化和 sendmsg 几乎完全一致,区别如下: ssize\_t recvmsg(int sockfd, struct msghdr \*msg, int flags); //内核控制信息传递接口,可以接受文件描述符 fd = \*fdptr; |
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<sys/uio.h> #include<sys/stat.h> #include<sys/types.h> #include<fcntl.h> | int main() { int fd=open("file1",O\_RDWR); char buf1\[10\]="hello "; char buf2\[10\]="world\\n"; struct iovec iov\[2\]; iov\[0\].iov\_base=buf1; iov\[0\].iov\_len=strlen(buf1); iov\[1\].iov\_base=buf2; iov\[1\].iov\_len=strlen(buf2); int ret=writev(fd ,iov,2); if(-1==ret) { perror("writev"); return -1; } //如果fd没有被赋值打开的文件描述符,这里传参默认0; } |
man cmsg
func.h | |||
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <sys/epoll.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/uio.h> | void send\_fd(int fds,int fd) { struct msghdr msg; memset(&msg,0,sizeof(msg)); struct iovec iov\[2\]; char buf1\[10\]="hello"; char buf2\[10\]="world"; iov\[0\].iov\_base=buf1; iov\[0\].iov\_len=5; iov\[1\].iov\_base=buf2; iov\[1\].iov\_len=5; msg.msg\_iov=iov; msg.msg\_iovlen=2; struct cmsghdr\* cmsg; int len=CMSG\_LEN(sizeof(int)); cmsg=(struct cmsghdr\*)calloc(1,len); cmsg->cmsg\_len=len; cmsg->cmsg\_level=SOL\_SOCKET; cmsg->cmsg\_type=SCM\_RIGHTS; \*(int\*)CMSG\_DATA(cmsg)=fd; msg.msg\_control=cmsg; msg.msg\_controllen=len; int ret=sendmsg(fds,&msg,0); if(-1==ret) { perror("sendmsg"); return; } } | void recv\_fd(int fds,int\* pfd) { struct msghdr msg; memset(&msg,0,sizeof(msg)); struct iovec iov\[2\]; char buf1\[10\]={0}; char buf2\[10\]={0}; iov\[0\].iov\_base=buf1; iov\[0\].iov\_len=5; iov\[1\].iov\_base=buf2; //这里是收不到对端发来的数据的,但是必须要写 iov\[1\].iov\_len=5; msg.msg\_iov=iov; msg.msg\_iovlen=2; struct cmsghdr\* cmsg; int len=CMSG\_LEN(sizeof(int)); cmsg=(struct cmsghdr\*)calloc(1,len); cmsg->cmsg\_len=len; cmsg->cmsg\_level=SOL\_SOCKET; cmsg->cmsg\_type=SCM\_RIGHTS; //后面两个可以不写的 msg.msg\_control=cmsg; msg.msg\_controllen=len; int ret=recvmsg(fds,&msg,0); if(-1==ret) { perror("recvmsg"); return; } \*pfd=\*(int\*)CMSG\_DATA(cmsg); } | #include "func.h" int main() { int fds\[2\]; //全双工 //pipe(fds); //这个管道不是前面学的无名管道 int ret; ret=socketpair(AF\_LOCAL,SOCK\_STREAM,0,fds); if(-1==ret) { perror("socketpair"); return -1; } if(!fork()) { close(fds\[1\]); int fd; recv\_fd(fds\[0\],&fd); printf("child fd=%d\\n",fd); char buf\[10\]={0}; read(fd,buf,sizeof(buf)); printf("buf=%s\\n",buf); close(fd); exit(0); }else{ close(fds\[0\]); int fd=open("file",O\_RDWR); printf("parent fd=%d\\n",fd); send\_fd(fds\[1\],fd); close(fd); //发过去,关闭,引用计数降为1 wait(NULL); return 0; } } //fds\[0\]和fds\[1\]都具有读写属性,但是也要关闭一端,剩下的那一端都可以读或者写。 |
//不用管为什么父进程是3传给子进程就变成4; 描述符是操作系统内核管控的, 传递只是把控制信息给传递过去了, 不用管具体描述符数字是多少;