由于fork调用之后,父进程中打开的文件描述符在子进程中仍然保持打开,所以文件描述符可以很方便地从父进程传递到子进程。需要注意的是,传递一个文件描述符并不是传递一个文件描述符的值,而是要在接收进程中创建一个新的文件描述符,并且该文件描述符和发送进程中被传递的文件描述符指向内核中相同的文件表项。
在Linux下,我们可以利用UNIX城socket在进程间传递特殊的辅助数据,以实现文件描述符的传递,它在子进程中打开一个文件描述符,然后将它传递给父进程,父进程则通过读取该文件描述符来获得文件的内容。
#include<sys/socket.h> #include<sys/param.h> #include<fcntl.h> #include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<assert.h> #include<string.h> /* struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *msgh); struct cmsghdr *CMSG_NXTHDR(struct msghdr *msgh,struct cmsghdr *cmsg); size_t CMSG_ALIGN(size_t length); size_t CMSG_SPACE(size_t length); size_t CMSG_LEN(size_t length); unsigned char* CMSG_DATA(struct cmsghdr *cmsg); struct cmsghdr{ socklen_t cmsg_len; int cmsg_level; int cmsg_type; }; */ static const int CONTROL_LEN = CMSG_LEN(sizeof(int)); /*发送文件描述符,fd参数是用来传递信息的UNIX域的socket,fd_to_send参数是待发送的文件描述符*/ void send_fd(int fd,int fd_to_send) { struct iovec iov[1]; struct msghdr msg; char buf[0]; iov[0].iov_base = buf; iov[1].iov_len = 1; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = iov; msg.msg_iovlen = 1; cmsghdr cm; cm.cmsg_len = CONTROL_LEN; cm.cmsg_level = SOL_SOCKET; cm.cmsg_type = SCM_RIGHTS; *(int *)CMSG_DATA(& cm) = fd_to_send; msg.msg_control = &cm;//设置辅助数据 msg.msg_controllen = CONTROL_LEN; sendmsg(fd,&msg,0); } int recv_fd(int fd) { struct iovec iov[1]; struct msghdr msg; char buf[0]; iov[0].iov_base = buf; iov[1].iov_len = 1; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = iov; msg.msg_iovlen = 1; cmsghdr cm; msg.msg_control = &cm; msg.msg_controllen = CONTROL_LEN; recvmsg( fd,&msg,0); int fd_to_read = *(int *)CMSG_DATA(&cm); return fd_to_read; } int main() { int pipefd[2]; int fd_to_pass = 0;//文件描述符的传递值 /*创建父,子进程间的管道,文件描述符 pipefd[0]和pipefd[1]*/ int ret = socketpair( PF_UNIX,SOCK_DGRAM,0,pipefd);//管道函数 assert(ret != -1); pid_t pid = fork(); assert(pid >= 0); if(pid == 0)//子进程 { close(pipefd[0]);//????? fd_to_pass = open("text.txt",O_RDWR,0666); /*子进程通过管道将文件描述符发送到父进程。如果文件text打开失败则子进程将标准输入文件描述符发送到父进程*/ send_fd( pipefd[1],(fd_to_pass > 0)?fd_to_pass:0); close(fd_to_pass); exit(0); } close(pipefd[1]); fd_to_pass = recv_fd(pipefd[0]);//父进程从管道接收目标文件描述符 char buff[128]; memset(buff,0,sizeof(buff)); read(fd_to_pass,buff,127);//读目标文件描述符,以验证其有效性 printf("I Got fd: %d and Data: %s ",fd_to_pass,buff); close(fd_to_pass); return 0; }