Linux中进程间传递文件描述符的方法

在进行fork调用后,由于子进程会拷贝父进程的资源,所以父进程中打开的文件描述符在子进程中仍然保持着打开,我们很容易的就将父进程的描述符传递给了子进程。但是除了这种情况下,如果想将某个父进程在子进程创建后才打开的描述符传递给子进程,又或者是想将子进程的描述符传递给父进程时,就遇到了问题。

在Linux中,虽然文件描述符是一个整型值,但是它的传递并非只是传递这个值而已。因为这个整型值其实是文件描述符表fd_array[]的下标
在这里插入图片描述

由于不同进程的文件描述符表不同,所以要传递一个文件描述符,就是要在接收进程中的文件描述符表中创建一个新的文件描述符,并且这两个文件描述符要指向内核中相同的文件表项

如何在两个进程之间传递文件描述符呢?在Linux下,我们可以利用UNIX域socket在程序间传递特殊的辅助数据,以实现文件描述符的传递。

这里演示的是具有亲缘关系的进程之间的传递(如果需要非亲缘,就将管道换成socket即可),主要借助UNIX域socket中的以下三个函数,socketpair、sendmsg、recvmsg

int socketpair(int domain, int type, int protocol, int sv[2]);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

struct msghdr 
{
    
    
      void         *msg_name;       /* 目的IP地址 */
      socklen_t     msg_namelen;    /* 地址长度 */
      struct iovec *msg_iov;        /* 指定的内存缓冲区 */
      size_t        msg_iovlen;     /* 缓冲区的长度 */
      void         *msg_control;    /* 辅助数据 */
      size_t        msg_controllen; /* 指向cmsghdr结构,用于控制信息字节数 */
      int           msg_flags;      /* 描述接收到的消息的标志 */
};

struct cmsghdr {
    
    
    socklen_t cmsg_len;    /* 计算cmsghdr头结构加上附属数据大小 */
    int       cmsg_level;  /* 发起协议 */
    int       cmsg_type;   /*协议特定类型 */
};

//获得指向与msghadr结构关联的第一个cmsghdr结构
struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *msgh);

//计算 cmsghdr 头结构加上附属数据大小,并包括对其字段和可能的结尾填充字符
size_t CMSG_SPACE(size_t length);

//计算 cmsghdr 头结构加上附属数据大小
size_t CMSG_LEN(size_t length);

//返回一个指针和cmsghdr结构关联的数据
unsigned char *CMSG_DATA(struct cmsghdr *cmsg);

上述函数的使用在这里就不多介绍,可以通过查询man手册或者阅读相关博客来进行了解,下面直接实现功能。

//发送文件描述符
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;
}

下面进行测试一下,子进程中打开一个文件描述符,并通过socketpair发送给父进程,来判断是否能够成功读取文件描述符中的内容

#include <sys/socket.h>
#include<sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

#include <iostream>
using std::cout;
using std::endl;


int main()
{
    
    
    int pipefd[2];          //管道
    int pass_fd = 0;        //待传送描述符
    char buff[1024] = {
    
    0};  //缓冲区

    //创建socketpair管道
    if(socketpair(PF_UNIX, SOCK_DGRAM, 0, pipefd) < 0)
    {
    
    
        cout << "socketpair." << endl;
        return 1;
    }

    pid_t pid = fork(); //创建子进程
    //子进程
    if(pid == 0)
    {
    
    
        close(pipefd[0]);   //子进程关闭多余的管道描述符
        pass_fd = open("test.txt", O_RDWR, 0666);

        if(pass_fd <= 0)
        {
    
    
            cout << "open." << endl;
        }

        send_fd(pipefd[1], pass_fd);    //子进程通过管道发送文件描述符
        close(pass_fd);
        close(pipefd[1]);
        exit(0);
    }
    else if(pid < 0)
    {
    
    
        //子进程创建失败
        cout << "fork." << endl;
        return 1;
    }
    close(pipefd[1]);   //父进程关闭多余描述符
    pass_fd = recv_fd(pipefd[0]);   //父进程从管道中接收文件描述符
    
    read(pass_fd, buff, 1024);  //父进程从缓冲区中读出数据,验证收发描述符是否正确
    cout << "fd: "<< pass_fd << " recv msg : " << buff << endl;

    close(pass_fd);
    close(pipefd[0]);
    return 0;
}

测试结果
在这里插入图片描述

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_35423154/article/details/108982599