linux系统编程——IPC——管道

1. 使用管道需要注意

  • 管道提供单向数据流,fd[0]读,fd[1]写
    创建全双工IPC管道的方法是 socketpair().

  • 写管道数据大小小于 PIPE_BUF 才保证原子性

  • 管道和 FIFO支持 O_NONBLOCK ,使用 fcntl 设置

  • 写一个没有读打开的管道,内核发送 SIGPIPE,若忽略该信号,write返回错误 EPIPE

  • 读一个没有写打开的管道,返回0

  • 管道是 字节流,会粘包,可用 分隔符或消息解决

  • 不要向管道写大量数据,因为管道容量有限,当空间不足时,会导致写阻塞

2. 阻塞状态下的使用

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

/**********************
 *  基于pipe回射服务器
 *  注意管道特点:
 *  1)单工
 *  2)写管道数据小于PIPE_BUF 才保住原子性
 *  3)写一个没有读打开的管道,内核发送SIGPIPE,若忽略并继续写,返回EPIPE
 *  4)读一个没有写打开的管道,返回0
 */

void srv(int rfd, int wfd);
void cli(int rfd, int wfd);

int main()
{
        pid_t pid;
        int pfd[4];

        pipe(pfd);
        pipe(pfd + 2);
        switch ((pid = fork())) {
                case -1:
                        break;
                case 0:
                        // 必须关闭不用的文件描述符,
                        // 这样对方关闭文件描述符后 struct file 才会真正释放
                        close(pfd[2]);
                        close(pfd[1]);
                        srv(pfd[0], pfd[3]);
                        break;
                default:
                        close(pfd[0]);
                        close(pfd[3]);
                        cli(pfd[2], pfd[1]);
                        waitpid(-1, NULL, 0);
                        break;
        }

        return 0;
}

#define MAXLINE 256

void srv(int rfd, int wfd)
{
        char buf[MAXLINE + 1];
        int cnt;

        signal(SIGPIPE, SIG_IGN);
        // 对端关闭,返回0
        // 错误,返回-1
        if ((cnt = read(rfd, buf, MAXLINE)) <= 0)
                return ;
        buf[cnt] = 0;

        int fd;
        if ((fd = open(buf, O_RDONLY)) < 0) {
                strcpy(buf, "can't open file\n");
                // 写管道注意:
                // 对端关闭,触发SIGPIPE,忽略信号,返回EPIPE
                write(wfd, buf, strlen(buf));
                return ;
        }

        while ((cnt = read(fd, buf, sizeof(buf)))) {
                write (wfd, buf, cnt);
        }

}

void cli(int rfd, int wfd)
{
        int cnt, len;
        char buf[MAXLINE + 1];

        signal(SIGPIPE, SIG_IGN);

        if (fgets(buf, MAXLINE, stdin) <= 0) {
                exit(0);
        }
        len = strlen(buf);
        if (buf[len - 1] == '\n')
                len--;
        if (write(wfd, buf, len) < 0) {
                if (errno == EPIPE) {
                        printf("srv pipe is closed\n");
                        return;
                }
        }

        while ((cnt = read(rfd, buf, sizeof(buf)))) {
                write (STDOUT_FILENO, buf, cnt);
        }
        return;
}

3. 非阻塞状态下的使用

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>

void delay_write(int fd);
void echo_msg(int fd);

int main(int argc, const char *argv[])
{
        pid_t pid;
        int flag, p[2];

        pipe(p);
        flag = fcntl(p[0], F_GETFL, 0);
        flag |= O_NONBLOCK;
        fcntl(p[0], F_SETFL, flag);

        flag = fcntl(p[1], F_GETFL, 0);
        flag |= O_NONBLOCK;
        fcntl(p[1], F_SETFL, flag);

        if ((pid = fork()) == 0) {
                close(p[0]);
                delay_write(p[1]);
                exit(0);
        }
        close(p[1]);
        echo_msg(p[0]);
        waitpid(-1, NULL, 0);

        return 0;
}

void delay_write(int fd)
{
        sleep(2);
        exit(0);
        // O_NONBLOCK 对写端通常没有影响,除非管道满了
        write(fd, "hello", 5);
}

#define MAXLINE 255
void echo_msg(int fd)
{
        char buf[MAXLINE + 1];
        int cnt;
__again__:
        cnt  = read(fd, buf, MAXLINE);
        if (cnt == 0) {
                // 若写管道关闭,则返回 0
                printf("peer is closed\n");
                return;
        }
        if (cnt < 0) {
                // 若写管道没有关闭,且没有数据,则返回 EAGAIN
                if (errno == EAGAIN) {
                        printf("read again\n");
                        goto __again__;
                }
                perror("read");
                return;
        }
        buf[cnt] = 0;
        printf("%s\n", buf);
}

4. 粘包问题

由于管道是字符流,所以需要用 分隔符 或 格式化的 方式 避免粘包
为了方便读写,封装如下函数

typedef struct msg_hdr {
        int len;
} msg_hdr_t;

int msg_read(int fd, char *buf, int buf_size)
{
        msg_hdr_t msg_hdr;
        int nbytes;

        nbytes = read(fd, &msg_hdr, sizeof(msg_hdr_t));

        if (nbytes < 0) {
                perror("read");
                return -1;
        }
        // 管道写端全关闭,说明对方已经停止会话
        if (nbytes == 0) {              
                return -2;
        }
        // 一次 单向通信 结束
        if (msg_hdr.len == 0) {
                return 0;
        }

        nbytes = read(fd, buf, msg_hdr.len);
        if (nbytes < 0) {
                return -1;
        }
        if (nbytes != msg_hdr.len) {
                return -1;
        }

        return msg_hdr.len;
}


int msg_send(int fd, char *buf, int n)
{

        char *p;
        int len, max;
        msg_hdr_t msg;

        // pipe只保证 数据大小小于 PIPE_BUF 的原子性,
        // 为了保证 数据报的完整,必须 小于等于 PIPE_BUF
        max = PIPE_BUF - sizeof(msg_hdr_t);

        p = buf;

        if (n == 0 || buf == NULL) {
                msg.len = 0;
                if (write(fd, &msg, sizeof(msg)) < 0) {
                        perror("write");
                        return -1;
                }
                return 0;
        }

        while (n > 0) {
                len = n > max ? max : n;
                msg.len = len;

                struct iovec iov[2];

                iov[0].iov_base = &msg;
                iov[0].iov_len=  sizeof(msg);

                iov[1].iov_base = p;
                iov[1].iov_len = msg.len;

                if (writev(fd, iov, sizeof(iov)/sizeof(*iov)) < 0) {
                        perror("writev");
                        return -1;
                }

                n -= len;
                p += len;
        }

        return 0;
}

5. 从内核看管道

管道本质是页缓存和管道文件关联,管道文件属于 特殊的文件系统 pipefs.

5.1 错误的读写管道

管道的数据流向是单向的,反向操作导致报错。

5.2 写读端关闭的管道

内核向写进程发送 SIGPIPE,并返回 EPIPE

5.3 读写关闭的管道

读出管道内剩余数据,最后读出EOF(0字节)

5.4 管道的大小

管道大小有限,默认是 65536,可以修改


由于管道大小有限,所以不适合大量数据传输。

posted on 2021-09-01 22:14  开心种树  阅读(94)  评论(0编辑  收藏  举报