问题的提出
一: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\n",fd_to_pass,buff);
close(fd_to_pass);
}
结果呈现
函数分析
一:通用I/O函数 recvmsg 和 sendmsg
①函数头文件及函数原型
头文件 | #include<sys/socket.h> |
接受函数 | ssize_t recvmsg( int sockfd , struct msghdr * msg , int flags); |
发送数据 | ssize_t sendmsg( int sockfd , struct msghdr * msg , int flags); |
②参数介绍
sockfd | 接受或发送端的套接字(文件描述符) |
msg | 用来指定 接收来源 或者 发送目的的地址。 |
flags |
③struct msghdr结构分析
struct msghdr{
void *msg_name;
socklen_t msg_namelen;
struct iovec *msg_iov;
int msg_iovlen;
void *msg_control;
socklen_t msg_controllen;
int msg_flags;
}
msg_name和msg_namelen | 这两个成员主要用于 套接字未连接的时间(主要是未连接的UDP套接字),用来指定接受来源 或者 发送目的的地址。套接字地址及其地址大小,类似recvfrom和sendto的第二个和第三个参数。 对于已经连接的套接字,则可以直接将msg_name设置为NULL 和 msg_namelen在recv中为0,在send中为一个值-结果参数。 |
msg_iov | 用于指定数据缓冲区数组,即iovec数组。 iovc结构如下: 这样两者组合成了一个二维数组,并且每个一个数组的长度不是固定的 |
msg_iovlen | 和msg_iov一起用于指定数据缓冲区数组 |
msg_control 和 msg_controllen | 设置辅助数据的位置和大小,辅助数据(ancillary data)也叫控制信息(control information) |
flags和msg_flags | 在这两个函数中有不同的传递方式。这里不做详细介绍 |