zoukankan      html  css  js  c++  java
  • UNIX网络编程——通过UNIX域套接字传递描述符和 sendmsg/recvmsg 函数

           在前面我们介绍了UNIX域套接字编程,更重要的一点是UNIX域套接字可以在同一台主机上各进程之间传递文件描述符。

           下面先来看两个函数:

    #include <sys/types.h>
    #include <sys/socket.h>
    ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
    ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
          它们与sendto 和 recvfrom 函数相似,只不过可以传输更复杂的数据结构,不仅可以传输一般数据,还可以传输额外的数据,即文件描述符。下面来看结构体msghdr :

    struct msghdr {
        void         *msg_name;       /* optional address */
        socklen_t     msg_namelen;    /* size of address */
        struct iovec *msg_iov;        /* scatter/gather array */
        size_t        msg_iovlen;     /* # elements in msg_iov */
        void         *msg_control;    /* ancillary data, see below */
        size_t        msg_controllen; /* ancillary data buffer len */
        int           msg_flags;      /* flags on received message */
    };
           如下图所示:


                                                

    1、msg_name :即对等方的地址指针,不关心时设为NULL即可;

    2、msg_namelen:地址长度,不关心时设置为0即可;

    3、msg_iov:是结构体iovec 的指针。   

    struct iovec {
        void  *iov_base;    /* Starting address */
        size_t iov_len;     /* Number of bytes to transfer */
    };

    成员iov_base 可以认为是传输正常数据时的buf,iov_len 是buf 的大小。

    4、msg_iovlen:当有n个iovec 结构体时,此值为n;

    5、msg_control:是一个指向cmsghdr 结构体的指针

    struct cmsghdr {
        socklen_t cmsg_len;    /* data byte count, including header */
        int       cmsg_level;  /* originating protocol */
        int       cmsg_type;   /* protocol-specific type */
        /* followed by unsigned char cmsg_data[]; */
    };

    6、msg_controllen :参见下图,即cmsghdr 结构体可能不止一个;

    7、flags : 一般设置为0即可;

                              

    为了对齐,可能存在一些填充字节,跟每个系统的实现有关,但我们不必关心,可以通过一些函数宏来获取相关的值,如下:

    #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);
    下面通过封装两个函数,send_fd 和 recv_fd 来进一步认识这些函数宏的作用:
    void send_fd(int sock_fd, int send_fd)
    {
        int ret;
        struct msghdr msg;
        struct cmsghdr *p_cmsg;
        struct iovec vec;
        char cmsgbuf[CMSG_SPACE(sizeof(send_fd))];
        int *p_fds;
        char sendchar = 0;
        msg.msg_control = cmsgbuf;
        msg.msg_controllen = sizeof(cmsgbuf);
        p_cmsg = CMSG_FIRSTHDR(&msg);
        p_cmsg->cmsg_level = SOL_SOCKET;
        p_cmsg->cmsg_type = SCM_RIGHTS;
        p_cmsg->cmsg_len = CMSG_LEN(sizeof(send_fd));
        p_fds = (int *)CMSG_DATA(p_cmsg);
        *p_fds = send_fd; // 通过传递辅助数据的方式传递文件描述符
    
        msg.msg_name = NULL;
        msg.msg_namelen = 0;
        msg.msg_iov = &vec;
        msg.msg_iovlen = 1; //主要目的不是传递数据,故只传1个字符
        msg.msg_flags = 0;
    
        vec.iov_base = &sendchar;
        vec.iov_len = sizeof(sendchar);
        ret = sendmsg(sock_fd, &msg, 0);
        if (ret != 1)
            ERR_EXIT("sendmsg");
    }
    
    int recv_fd(const int sock_fd)
    {
        int ret;
        struct msghdr msg;
        char recvchar;
        struct iovec vec;
        int recv_fd;
        char cmsgbuf[CMSG_SPACE(sizeof(recv_fd))];
        struct cmsghdr *p_cmsg;
        int *p_fd;
        vec.iov_base = &recvchar;
        vec.iov_len = sizeof(recvchar);
        msg.msg_name = NULL;
        msg.msg_namelen = 0;
        msg.msg_iov = &vec;
        msg.msg_iovlen = 1;
        msg.msg_control = cmsgbuf;
        msg.msg_controllen = sizeof(cmsgbuf);
        msg.msg_flags = 0;
    
        p_fd = (int *)CMSG_DATA(CMSG_FIRSTHDR(&msg));
        *p_fd = -1;
        ret = recvmsg(sock_fd, &msg, 0);
        if (ret != 1)
            ERR_EXIT("recvmsg");
    
        p_cmsg = CMSG_FIRSTHDR(&msg);
        if (p_cmsg == NULL)
            ERR_EXIT("no passed fd");
    
    
        p_fd = (int *)CMSG_DATA(p_cmsg);
        recv_fd = *p_fd;
        if (recv_fd == -1)
            ERR_EXIT("no passed fd");
    
        return recv_fd;
    }
    来解释一下send_fd 函数:

    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    msg.msg_iov = &vec;
    msg.msg_iovlen = 1; //主要目的不是传递数据,故只传1个字符
    msg.msg_flags = 0;
    
    vec.iov_base = &sendchar;
    vec.iov_len = sizeof(sendchar);

           这几行中需要注意的是我们现在的目的不是传输正常数据,而是为了传递文件描述符,所以只定义一个1字节的char,其余参照前面对参数的解释可以理解。

           现在我们只有一个cmsghdr 结构体,把需要传递的文件描述符send_fd 长度,也就是需要传输的额外数据大小,当作参数传给CMSG_SPACE 宏,可以得到整个结构体的大小,包括一些填充字节,如上图所示,也即 

    char cmsgbuf[CMSG_SPACE(sizeof(send_fd))]; 
    也就可以进一步得出以下两行:

    msg.msg_control = cmsgbuf;
    msg.msg_controllen = sizeof(cmsgbuf);
    接着,需要填充cmsghdr 结构体,传入msghdr 指针,CMSG_FIRSTHDR宏可以得到首个cmsghdr 结构体的指针,即

    p_cmsg = CMSG_FIRSTHDR(&msg);
    然后使用指针来填充各字段,如下:

    p_cmsg->cmsg_level = SOL_SOCKET;
    p_cmsg->cmsg_type = SCM_RIGHTS;
    p_cmsg->cmsg_len = CMSG_LEN(sizeof(send_fd));

    传入send_fd 的大小,CMSG_LEN宏可以得到cmsg_len 字段的大小。

    最后,传入结构体指针 p_cmsg ,宏CMSG_DATA 可以得到准备存放send_fd 的位置指针,将send_fd 放进去,如下:

    p_fds = (int*)CMSG_DATA(p_cmsg);
    *p_fds = send_fd; // 通过传递辅助数据的方式传递文件描述符

    recv_fd 函数就类似了,不再赘述。


    可以写个小程序测试一下:

    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/un.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <fcntl.h>
    
    #define ERR_EXIT(m) 
            do 
            { 
                    perror(m); 
                    exit(EXIT_FAILURE); 
            } while(0)
    
    int main(void)
    {
        int sockfds[2];
        /* 只有unix域协议才能在进程间传递文件描述符,如果想要在没有亲缘关系的进程间
         * 传递,则不能用socketpair函数,要用socket()函数 */
        if (socketpair(PF_UNIX, SOCK_STREAM, 0, sockfds) < 0)
            ERR_EXIT("socketpair");
    
        pid_t pid;
        pid = fork();
        if (pid == -1)
            ERR_EXIT("fork");
        /* 如果是父进程打开的文件描述符,子进程可以共享
         * 这里演示的是子进程打开的文件描述符通过封装的函数传给父进程 */
        if (pid > 0)
        {
            close(sockfds[1]);
            int fd = recv_fd(sockfds[0]);
            char buf[1024] = {0};
            read(fd, buf, sizeof(buf));
            printf("buf=%s
    ", buf);
        }
        else if (pid == 0)
        {
            close(sockfds[0]);
            int fd;
            fd = open("test.txt", O_RDONLY);
            if (fd == -1);
            send_fd(sockfds[1], fd);
        }
        return 0;
    }
    我们知道,父进程在fork 之前打开的文件描述符,子进程是可以共享的,但是子进程打开的文件描述符,父进程是不能共享的,上述程序就是举例在子进程中打开了一个文件描述符,然后通过send_fd 函数将文件描述符传递给父进程,父进程可以通过recv_fd 函数接收到这个文件描述符。先建立一个文件test.txt 后输入几个字符,然后运行程序,输出如下:

    huangcheng@ubuntu:~$ cat test.txt
    ctthuangcheng
    huangcheng@ubuntu:~$ ./a.out
    buf=ctthuangcheng
    
    huangcheng@ubuntu:~$

    证明父进程确实可以打开test.txt 文件。
    最后提醒一点,只有unix域协议才能在本机进程间传递文件描述符,如果想要在没有亲缘关系的进程间传递,则不能用socketpair函数,要用socket()函数 才行。
  • 相关阅读:
    可爱的中国电信 请问我们的电脑还属于我们自己吗?
    了解客户的需求,写出的代码或许才是最优秀的............
    DELPHI DATASNAP 入门操作(3)简单的主从表的简单更新【含简单事务处理】
    用数组公式获取字符在字符串中最后出现的位置
    在ehlib的DBGridEh控件中使用过滤功能(可以不用 MemTableEh 控件 适用ehlib 5.2 ehlib 5.3)
    格式化json返回的时间
    ExtJs中使用Ajax赋值给全局变量异常解决方案
    java compiler level does not match the version of the installed java project facet (转)
    收集的资料(六)ASP.NET编程中的十大技巧
    收集的资料共享出来(五)Asp.Net 权限解决办法
  • 原文地址:https://www.cnblogs.com/wangfengju/p/6172503.html
Copyright © 2011-2022 走看看