美文网首页我爱编程
linux——进程间共享文件描述符

linux——进程间共享文件描述符

作者: AmberXiao | 来源:发表于2018-06-01 10:12 被阅读4次

    进程间共享文件描述符主要由三个函数实现:

    1. socketpair():创建一对匿名域套接字;
    2. sendmsg():从套接字一端发送数据;
    3. recvmsg():从套接字另一端接受数据。
    image.png

    1. socketpair()

    #include <sys/types.h>
    #include <sys/socket.h>
    int socketpair(
      int domain,
      int type,
      int protocol,
      int sv[2]
    );
    

    参数说明:

    1. domain:表示要建立的套接字的协议族,只能为AF_LOCAL 或AF_UNIX;
    2. type:表示协议,可以是SOCK_STREAM或SOCK_DGRAM。用SOCK_STREAM建立的是管道流,与一般管道的区别是,套接字对建立的通道是双向的,每一端都可以进行读写;
    3. protocol:表示类型,只能为0;
    4. sv[2]:存储建立的套接字对的整数数组。

    返回值:
    函数调用成功返回0,否则返回-1,并且设置errno。

    2. sendmsg()

    #include <sys/socket.h>
    ssize_t sendmsg(
      int sockfd,
      const struct msghdr *msg,
      int flags
    );
    

    参数说明:

    1. sockfd:文件描述符(sockpair创建的sv[]);
    2. *msg:需要发送的消息的头部结构体,详见下文;
    3. flags:一般置0,其他值与send()相同。

    参数*msg涉及到3个结构体:msghdr、iovec和cmasghdr,如下图:

    image.png
    msghdr
    struct msghdr
    {
      void *msg_name;
      socklen_t msg_namelen;
      struct iovec *msg_iov;
      size_t msg_iovlen;
      void *msg_control;
      size_t *msg_controllen;
      int msg_flags;
    };
    

    成员说明:

    1. *msg_name和msg_namelen表示要发送或接收数据的地址和地址长度,如果是面向连接的,这两个变量可以不用;

    2. *msg_iov用于指定要发送或接收数据的缓冲区地址,可以是数组,如下:

       struct iovec
         {
           void *iov_baes;
           //存放数据的缓冲区
           size_t iov_len;
           //数据长度
         };
      
    3. msg_iovlen表示iovec类型元素的个数;

    4. *msg_control表示控制信息,指向一个cmsghdr结构体, *msg_controllen表示控制信息的长度,详见下文;

    5. msg_flages表示发送和接受消息的标识。

    cmsghdr

    struct cmsghdr
     {
       socklen_t cmsg_len;
       int cmsg_level;
       int cmsg_type;
       /*unsigned char cmsg_data[]*/
      };
    

    成员说明:

    1. cmsg_len:控制信息的字节计数,包含结构头的数据。这个值是由CMSG_LEN()宏计算的;
    2. cmsg_level:原始的协议级别(例如,SOL_SOCKET);
    3. cmsg_type:控制信息类型(例如,SCM_RIGHTS);
    4. cmsg_data:控制信息的数据部分,实际上这个成员并不存在,数据是直接存储在cmsg_type之后的。

    对于这些控制数据的访问,必须使用Linux提供的一些专用宏来完成。这些宏包括如下几个:

    #include <sys/socket.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);
    

    其中:

    1. CMSG_FIRSTHDR()返回msg所指向的msghdr结构体中的第一个cmsghdr结构体指针;
    2. CMSG_NXTHDR()返回传入的cmsghdr类型的指针的下一个cmsghdr结构体的指针;
    3. CMSG_ALIGN()根据传入的length大小,返回一个包含了添加对齐作用的填充数据后的大小;
    4. CMSG_SPACE()中传入的参数length指的是一个控制信息元素(即一个cmsghdr结构体)后面数据部分的字节数,返回的是这个控制信息的总的字节数,即包含了头部(即cmsghdr各成员)、数据部分和填充数据的总和;
    5. CMSG_DATA根据传入的cmsghdr指针参数,返回其后面数据部分的指针;
    6. CMSG_LEN传入的参数是一个控制信息中的数据部分的大小,返回的是这个根据这个数据部分大小,需要配置的cmsghdr结构体中cmsg_len成员的值。这个大小将为对齐添加的填充数据也包含在内。

    发送文件描述符的步骤:

    1. 创建一个msghdr结构体,并初始化;
    2. 创建一个cmsghdr结构体,将该结构体的cmsg_level设置为SOL_SOCKET,cmsg_type设置为SCM_RIGHTS,其中,SCM表示socket-level control message,SCM_RIGHTS表示我们要传递访问权限;
    3. 将要传递的文件描述符赋值给cmsghdr的数据部分;
    4. 将该cmsghdr结构体分配给msg_control;
    5. 调用sendmsg()。

    具体实现:

    static const int CONTROL_LEN = CMSG_LEN(sizeof(int));
    //计算cmsg_len的值
    /*发送文件描述符 sv参数是用来传递信息的UNIX域socket, 
    *fd参数是待发送的文件描述符    */
    void send_fd(int sv, int fd)
    {
    struct msghdr msg;
    struct iovec iov[1];
    
    char buf[0];
    iov[0].iov_base = buf;
    iov[0].iov_len = 1;
    //不需要传递其他数据,所以iov数据部分为空
    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    //面向连接,可以不用
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;
    
    cmsghdr cm;
    cm.cmsg_len = CONTROL_LEN;
    //用CMSG_LEN()宏获得的值
    cm.cmsg_level = SOL_SOCKET;
    cm.cmsg_type = SCM_RIGHTS;
     //设置信息类型
    *(int*)CMSG_DATA(&cm) = fd;
    //将要传递的文件描述符赋值给cmsghdr的数据部分
    msg.msg_control = &cm; 
    //给msg_control分配设置好的cmsghdr结构体
    sendmsg(sv, &msg, 0);
    //调用sendmsg()
    };
    

    3. recvmsg()

    #include <sys/socket.h>
    ssize_t recvmsg(
    int sockfd,
    const struct msghdr *msg,
    int flags
    );
    

    接受函数与发送函数大致相同,最后一个参数flags在调用recvmsg之后,会被设置到到msg所指向的msghdr类型的msg_flags变量中。
    接收文件描述符的步骤:

    1. 创建一个msghdr结构体,并初始化;
    2. 创建一个cmsghdr结构体,
      将该结构体的cmsg_level设置为SOL_SOCKET,
      cmsg_type设置为SCM_RIGHTS,
      其中,SCM表示socket-level control message,SCM_RIGHTS表示我们要传递访问权限;
    3. 将该cmsghdr结构体分配给msg_control;
    4. 调用recvmsg();
      5.读取cmsghdr的数据部分获得文件描述符。

    具体实现:

    /*接收文件描述符
      *sv参数是用来传递信息的UNIX域socket */
    int recv_fd(int sv)
    {
    struct msghdr msg;
    struct iovec iov[1];
    
    char buf[0];
    iov[0].iov_base = buf;
    iov[0].iov_len = 1;
    //没有其他要接受的数据,iovec数据部分设为空
    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;
    //创建一个cmsghdr结构体,并分配给msg_control
    
    recvmsg(fd, &msg, 0);
    //调用recvmsg()
    
    int fd = *(int*)CMSG_DATA(&cm);
    //从cmsghdr的数据部分获取文件描述符
    
    return fd
    }
    

    4. 主函数

    #include <sys/socket.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <assert.h>
    #include <string.h>
    
    int main(int argc, char* argv[])
    {
    int sv[2];
    int fd= 0;
    /*创建父子进程管道*/
    int ret = socketpair(PF_UNIX, SOCK_DGRAM, 0, sv);
    assert(ret != -1);
    //将创建的管道符存于sv[2]中
    
    pid_t pid = fork();
    if(pid == 0)//子进程
    {
        close(sv[0]);//关闭管道符一端
        fd = open("test.txt", O_RDWR);
        //  创建一个文件描述符
        send_fd(sv[1], (fd > 0) ? fd: 0);
        //在管道符另一端发送文件描述符
        close(fd_to_pass);
        //关闭文件
        exit(0);
    }
    
    //父进程
    close(sv[1]);//关闭管道符一端
    fd= recv_fd(sv[0]);
    //在管道符另一端发送文件描述符
    char buf[1024];
    memset(buf, '\0', 1024);
    read(fd, buf, 1024);
    printf("I got fd %d and data %s\n", fd, buf);
    //从文件中读取数据
    close(fd_to_pass);
    
    return 0;
    }
    

    参考网页:

    1. https://blog.csdn.net/y396397735/article/details/51078437
    2. https://blog.csdn.net/wm_1991/article/details/52165666
    3. https://blog.csdn.net/y396397735/article/details/50684558
    4. https://blog.csdn.net/u014209688/article/details/71311973

    相关文章

      网友评论

        本文标题:linux——进程间共享文件描述符

        本文链接:https://www.haomeiwen.com/subject/zrmcsftx.html