美文网首页Linux学习之路Linux读书笔记
APUE读书笔记-17高级进程通信(11)

APUE读书笔记-17高级进程通信(11)

作者: QuietHeart | 来源:发表于2020-08-15 10:59 被阅读0次

    使用unix域套接字进行文件符号传输

    为了使用unix域套接字传递文件描述符号,我们调用sendmsg(2)和recvmsg(2)函数。两个函数会接收一个指向msghdr结构的指针,这个结构包含了我们想要接收和发送的所有信息。

    下面的定义就和你的系统上面的结构类似:

    struct msghdr {
            void         *msg_name;        /* optional address */
            socklen_t     msg_namelen;     /* address size in bytes */
            struct iovec *msg_iov;         /* array of I/O buffers */
            int           msg_iovlen;      /* number of elements in array */
            void         *msg_control;     /* ancillary data */
            socklen_t     msg_controllen;  /* number of ancillary bytes */
            int           msg_flags;       /* flags for received message */
    };
    
    • 前两个成员(msg_name/msg_namelen)用于在网络连接上面发送数据包,在每个数据包中可以指定目标地址。
    • 接下来的两个元素(msg_iov/msgiovlen)允许我们指定一个数组缓存(分散读和聚集写),和我们前面描述的readv与writev函数类似。
    • msg_flags域包含了描述消息接收的标记,前面谈到recvmsg的消息标记的时候对此进行过说明。
    • 重点是两个用来处理传递和接收的控制信息的成员(msg_control/msg_controllen)

    msg_control域指向cmsghdr(控制消息头部)结构,msg_controllen域包含控制信息的字节数目。

    struct cmsghdr  {
            socklen_t   cmsg_len;    /* data byte count, including header */
            int         cmsg_level;  /* originating protocol */
            int         cmsg_type;   /* protocol-specific type */
            /* followed by the actual control message data */
    };
    

    为发送一个文件描述符号,我们设置cmsg_len为cmsghdr结构的大小,加上一个整数(文件描述符)的大小。cmsg_level成员被设置成为SOL_SOCKET,cmsg_type被设置成为SCM_RIGHTS,以表明我们传递访问权限。(SCM代表套接字层次控制消息。)访问权限可以只通过unix域套接字来传递。文件描述符号存放在cmsg_type域后,通过使用宏CMSG_DATA来获得指向这个整数的指针。

    (注:关于cmsgher中的cmsg_len与msghdr中的msg_controllen成员之间的区别,参见后面译者注。)

    有三个宏可以被使用来访问控制数据,一个宏用来协助计算用于cmsg_len的值。

    #include <sys/socket.h>
    unsigned char *CMSG_DATA(struct cmsghdr *cp);
    

    返回:指向与cmsghdr结构相关联的数据。

    struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *mp);
    

    返回:指向和msghdr结构关联的,第一个cmsghdr结构,或者如果不存在则返回NULL。

    struct cmsghdr *CMSG_NXTHDR(struct msghdr *mp, struct cmsghdr *cp);
    

    返回:在给定了当前的cmsghdr结构的前提下,获取指向和msghdr结构关联的,下一个cmsghdr结构,或者如果到达了最后一个则返回NULL。

    unsigned int CMSG_LEN(unsigned int nbytes);
    

    返回:返回分配了nbytes大小数据对象的cmsghdr总大小。(注:具体参见译者注)

    Single UNIX Specification 定义了前面三个宏,但是忽略了CMSG_LEN。

    CMSG_LEN宏返回用来存放大小为nbytes的数据对象的字节数目,是加入了cmsghdr结构大小之后的,根据处理器结构调整了对齐的,以及向上取整的。(??????从下面代码看好象是容纳nbytes数据的cmsghdr的大小,但是翻译上确实整个msghdr大小)

    UNIX域套接字的send_fd函数

    下面是用于UNIX域套接字的send_fd函数。

    #include "apue.h"
    #include <sys/socket.h>
    /* size of control buffer to send/recv one file descriptor */
    #define CONTROLLEN  CMSG_LEN(sizeof(int))
    static struct cmsghdr   *cmptr = NULL;  /* malloc'ed first time */
    /*
     * Pass a file descriptor to another process.
     * If fd<0, then -fd is sent back instead as the error status.
     */
    int send_fd(int fd, int fd_to_send)
    {
        struct iovec    iov[1];
        struct msghdr   msg;
        char            buf[2]; /* send_fd()/recv_fd() 2-byte protocol */
    
        iov[0].iov_base = buf;
        iov[0].iov_len  = 2;
        msg.msg_iov     = iov;
        msg.msg_iovlen  = 1;
        msg.msg_name    = NULL;
        msg.msg_namelen = 0;
        if (fd_to_send < 0) {
            msg.msg_control    = NULL;
            msg.msg_controllen = 0;
            buf[1] = -fd_to_send;   /* nonzero status means error */
            if (buf[1] == 0)
                buf[1] = 1; /* -256, etc. would screw up protocol */
        } else {
            if (cmptr == NULL && (cmptr = malloc(CONTROLLEN)) == NULL)
               return(-1);
            cmptr->cmsg_level  = SOL_SOCKET;
            cmptr->cmsg_type   = SCM_RIGHTS;
            cmptr->cmsg_len    = CONTROLLEN;
            msg.msg_control    = cmptr;
            msg.msg_controllen = CONTROLLEN;
            *(int *)CMSG_DATA(cmptr) = fd_to_send;     /* the fd to pass */
            buf[1] = 0;          /* zero status means OK */
        }
        buf[0] = 0;              /* null byte flag to recv_fd() */
        if (sendmsg(fd, &msg, 0) != 2)
            return(-1);
        return(0);
    }
    

    在sendmsg调用中,我们发送协议数据(null和状态字节)以及文件描述符号。

    用于UNIX域套接字的recv_fd函数

    为接收一个文件描述符号,我们需要分配足够的空间给cmsghdr结构以及文件描述符号,设置msg_control指向分配的区域,然后调用recvmsg。我们使用CMSG_LEN宏来计算需要的空间。

    我们从套接字读取,直到读取到状态字节之前的null字节。所有到达这个null字节的都是来自发送者的错误消息,请看如下代码:

    #include "apue.h"
    #include <sys/socket.h>     /* struct msghdr */
    
    /* size of control buffer to send/recv one file descriptor */
    #define CONTROLLEN  CMSG_LEN(sizeof(int))
    static struct cmsghdr   *cmptr = NULL;      /* malloc'ed first time */
    /*
     * Receive a file descriptor from a server process.  Also, any data
     * received is passed to (*userfunc)(STDERR_FILENO, buf, nbytes).
     * We have a 2-byte protocol for receiving the fd from send_fd().
     */
    int recv_fd(int fd, ssize_t (*userfunc)(int, const void *, size_t))
    {
       int             newfd, nr, status;
       char            *ptr;
       char            buf[MAXLINE];
       struct iovec    iov[1];
       struct msghdr   msg;
    
       status = -1;
       for ( ; ; ) {
           iov[0].iov_base = buf;
           iov[0].iov_len  = sizeof(buf);
           msg.msg_iov     = iov;
           msg.msg_iovlen  = 1;
           msg.msg_name    = NULL;
           msg.msg_namelen = 0;
           if (cmptr == NULL && (cmptr = malloc(CONTROLLEN)) == NULL)
               return(-1);
           msg.msg_control    = cmptr;
           msg.msg_controllen = CONTROLLEN;
           if ((nr = recvmsg(fd, &msg, 0)) < 0) {
               err_sys("recvmsg error");
           } else if (nr == 0) {
               err_ret("connection closed by server");
               return(-1);
           }
           /*
            * See if this is the final data with null & status.  Null
            * is next to last byte of buffer; status byte is last byte.
            * Zero status means there is a file descriptor to receive.
            */
           for (ptr = buf; ptr < &buf[nr]; ) {
               if (*ptr++ == 0) {
                   if (ptr != &buf[nr-1])
                       err_dump("message format error");
                   status = *ptr & 0xFF;  /* prevent sign extension */
                   if (status == 0) {
                       if (msg.msg_controllen != CONTROLLEN)
                           err_dump("status = 0 but no fd");
                       newfd = *(int *)CMSG_DATA(cmptr);
                   } else {
                       newfd = -status;
                   }
                   nr -= 2;
               }
            }
            if (nr > 0 && (*userfunc)(STDERR_FILENO, buf, nr) != nr)
                return(-1);
            if (status >= 0)    /* final data has arrived */
                return(newfd);  /* descriptor, or -status */
       }
    }
    

    注意,我们一直在准备接收一个文件描述符号(每次调用recvmsg之前,我们设置msg_control和msg_controllen),但是,只有msg_controllen返回为非0的时候,我们才接收一个文件描述符号。

    相关文章

      网友评论

        本文标题:APUE读书笔记-17高级进程通信(11)

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