IPC——基于STREAMS的管道

1. 概述

基于 STREAMS 的管道有三个特点:

  1. 全双工
  2. 可以传递文件描述符
  3. 可以用文件命名

2. socketpair

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

创建 互相连接 的 unix 套接字。

由于unix套接字只复制数据,没有报文封装等操作,所以高效,且可以用 套接字相关api。

3. 基于unix域套接字的服务端客户端

unix套接字编程和tcp套接字编程类似,只是bind的是文件名

服务端

#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include <sys/un.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int
main ()
{
        int fd, cnt, cfd;
        size_t size;
        char buf[200];
        struct sockaddr_un peer, un;

        fd = socket(AF_UNIX, SOCK_STREAM, 0);

        un.sun_family = AF_UNIX;
        strcpy(un.sun_path, "hello.socket");
        unlink(un.sun_path); // 若已存在同名文件,则先删除
        // bind 成功后会创建 socket 文件
        if (bind(fd, (struct sockaddr *)&un, sizeof(un)) < 0) {
                perror("bind");
                return -1;
        }
        // 不能删除socket文件,因为客户端需要利用文件系统连接套接字

        // 将套接字转成 监听套接字
        if (listen(fd, 10) < 0) {
                perror("listen");
                return -1;
        }

        while ((cfd = accept(fd, (struct sockaddr *)&peer, &size))) {

                if ((cnt = read(cfd, buf, sizeof(buf))) < 0) {
                        perror("read");
                        return -1;
                }

                buf[cnt] = 0;
                printf("read : %s\n", buf);
                write(cfd, "world", 5);
                close(cfd);
        }
        close(fd);

        return 0;
}

客户端

#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include <sys/un.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int
main ()
{
        int fd, cnt;
        struct sockaddr_un un;
        char buf[200];

        fd = socket(AF_UNIX, SOCK_STREAM, 0);

        un.sun_family = AF_UNIX;
        strcpy(un.sun_path, "hello.socket");
        if (connect(fd, (struct sockaddr *)&un, sizeof(un)) < 0) {
                perror("connect");
                return -1;
        }

        if ((cnt = write(fd, "hello", 5)) < 0) {
                perror("write");
                return -1;
        }

        if ((cnt = read(fd, buf, sizeof(buf))) < 0) {
                perror("read");
                return -1;
        }
        buf[cnt] = 0;
        printf("child read %s\n", buf);

        close(fd);

        return 0;
}

4. 进程间传递文件描述符

#include <stdio.h>
#include <stdlib.h>
#include <alloca.h>
#include <sys/ioctl.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

void parent();
void child();
int p[2];

int main()
{
        // 1. 进程之间用 unix域 套接字传递文件描述符
    socketpair(AF_UNIX, SOCK_STREAM, 0, p);

        if (fork())  {
                parent();
        }
        else {
                child();
        }

        return 0;
}

void send_fd(int sockfd, int fd_to_send)
{
        struct msghdr msg;
        struct cmsghdr *cmsg;
#define CONTRLEN        CMSG_LEN(sizeof(int))

        char buf[1];
        struct iovec iov;
        iov.iov_base = buf;
        iov.iov_len = 1;
        buf[0] = 'a';
        msg.msg_iov = &iov; // 聚合写,不能为空
        msg.msg_iovlen = 1;
        msg.msg_name = NULL;
        msg.msg_namelen = 0;
        msg.msg_flags = 0;

        // 控制信息
        cmsg = alloca(CONTRLEN);
        cmsg->cmsg_level = SOL_SOCKET;
        cmsg->cmsg_type = SCM_RIGHTS; // 用于指明传输访问权限
        cmsg->cmsg_len = CONTRLEN;
        *(int *)CMSG_DATA(cmsg) = fd_to_send; // 控制信息最后域追加 需要传递的文件描述符
        msg.msg_control = cmsg;
        msg.msg_controllen = CONTRLEN;

        if (sendmsg(sockfd, &msg, 0) < 0)  {
                perror("sendmsg");
        }
}

void recv_fd(int sockfd, int *fd_to_recv)
{
        struct msghdr msg = {0};
        struct cmsghdr *cmsg;

        struct iovec iov;
        char buf[2] = {0};
        iov.iov_base = buf;
        iov.iov_len = 2;
        msg.msg_iov = &iov;
        msg.msg_iovlen = 1;
        cmsg = alloca(CONTRLEN);
        msg.msg_control = cmsg;
        msg.msg_controllen = CONTRLEN;

        if (recvmsg(sockfd, &msg, 0) < 0) {
                perror("recvmsg");
        }

        cmsg = msg.msg_control;
        *fd_to_recv = *(int *)CMSG_DATA(cmsg);
}

void parent()
{
        int fd;

        close(p[0]);
        fd = open("./tmp", O_CREAT | O_RDWR, 0644);
        send_fd(p[1], fd);
}

void child()
{
        int fd;
        close(p[1]);
        recv_fd(p[0], &fd);
        if (write(fd, "hello world", strlen("hello world")) < 0) {
                perror("write");
        }

}

unix传输文件描述符,使用 sendmsg, recvmsg,这两个函数都有一个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 */
               socklen_t     msg_controllen; /* ancillary data buffer len */
               int           msg_flags;      /* flags on received message */
           };

msg_name 和 msg_namelen 通常用于网络连接发送报文时,指定目的地址
msg_iov 和 msg_iovlen 用于 分散读/聚合写 和 readv writev一样
msg_flags 指定 非阻塞等特性
msg_control 和 msg_controllen 处理控制信息的接受和发送,msg_control 指向 cmsghdr(控制信息头部)结构,

struct cmsghdr {
   socklen_t cmsg_len;  /* 整个 cmsghdr 的长度,字节为单位 */
   int cmsg_level;
   int cmsg_type;
   /* 紧接着是真实的控制信息数据 */
};

使用下面宏访问cmsghdr

// 返回关联控制信息数据的指针
unsigned char *CMSG_DATA(struct cmsghdr *cp);

// 若 控制信息数据的大小为 nbytes, 则返回对应 整个 cmsghdr 的大小
unsigned int CMSG_LEN(unsigned int nbytes);

5. ngx 实现的 channel

通常 soketpair + fork 能得到如下结构

问题是 老的子进程 没有 新子进程 通信的 struct file * ,所以需要传递由 父进程将新打开的 struct file * 传递给 老子进程。
以形成如下结构:

如此 所有进程互相通信的基础有了,
只需要 加上协议。

posted on 2022-04-06 17:42  开心种树  阅读(59)  评论(0编辑  收藏  举报