在网络编程中可能使用到多进程模型,主进程只负责监听是否有连接到来,真正进行负责accept的是子进程,这就涉及到了进程间的文件描述符传递。
文件描述符在linux下只是一个整形数值,它的本质其实是 进程pcb中的文件描述符表 fd_array 的下标,fd_array是一个file* 类型的数组,实现文件描述符传递不能简单的将fd发送。
演示了主进程通过fork创建子进程,并创建unix domain socket进行通信,子进程向父进程发送打开的文件fd。
代码示例
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>
#include <iostream>
#include <cassert>
using namespace std;
static const int CONTROL_LEN = CMSG_LEN(sizeof(int));
//发送文件描述符
void send_fd(int sock_fd, int fd)
{
iovec iov[1];
msghdr msg;
char buff[0];
//指定缓冲区
iov[0].iov_base = buff;
iov[0].iov_len = 1;
//通过socketpair进行通信,不需要知道ip地址
msg.msg_name = nullptr;
msg.msg_namelen = 0;
//指定内存缓冲区
msg.msg_iov = iov;
msg.msg_iovlen = 1;
//辅助数据
cmsghdr cm;
cm.cmsg_len = CMSG_LEN(sizeof(sock_fd)); //描述符的大小
cm.cmsg_level = SOL_SOCKET; //发起协议
cm.cmsg_type = SCM_RIGHTS; //协议类型
*(int *)CMSG_DATA(&cm) = fd; //设置待发送描述符
//设置辅助数据
msg.msg_control = &cm;
msg.msg_controllen = CMSG_LEN(sizeof(sock_fd));
sendmsg(sock_fd, &msg, 0); //发送描述符
}
//接收并返回文件描述符
int recv_fd(int sock_fd)
{
iovec iov[1];
msghdr msg;
char buff[0];
//指定缓冲区
iov[0].iov_base = buff;
iov[0].iov_len = 1;
//通过socketpair进行通信,不需要知道ip地址
msg.msg_name = nullptr;
msg.msg_namelen = 0;
//指定内存缓冲区
msg.msg_iov = iov;
msg.msg_iovlen = 1;
//辅助数据
cmsghdr cm;
//设置辅助数据
msg.msg_control = &cm;
msg.msg_controllen = CMSG_LEN(sizeof(sock_fd));
recvmsg(sock_fd, &msg, 0); //接收文件描述符
int fd = *(int *)CMSG_DATA(&cm);
return fd;
}
int main()
{
int fd[2];
int ret = socketpair(AF_UNIX, SOCK_DGRAM, 0, fd);
assert(ret >= 0);
// parent
if (fork() > 0)
{
close(fd[1]);
int ret = recv_fd(fd[0]);
char buf[255];
int rd = read(ret, buf, sizeof(buf) - 1);
buf[rd] = 0;
cout << buf << endl;
exit(0);
}
// child
close(fd[0]);
int open_fd = open("1.txt", O_RDWR | O_CREAT, 0666);
send_fd(fd[1], open_fd);
close(open_fd);
}
参考 – Linux高性能服务器编程