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

2021/9/2 7:06:07

本文主要是介绍linux系统编程——IPC——管道,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

管道提供单向数据流,fd[0]读,fd[1]写

创建全双工IPC管道的方法是 socketpair().

2. 示例

由于管道是字符流,所以需要用 分隔符 或 格式化的 方式 避免粘包

#include <linux/limits.h>
#include <sys/uio.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>


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;
}

void client(int readfd, int writefd)
{
        char buf[LINE_MAX];     // buf 大小设置为LINE_MAX,让程序不需要担心输入限制
        int len, nbytes;


        while (fgets(buf, sizeof(buf), stdin)) {        // 行输入, 用户输入EOF,退出
                len = strlen(buf);
                if (len > PIPE_BUF) {                                   // 若输入长度大于 PIPE_BUF,导致pipe不能保证原子性
                        printf("filename is too long\n");
                        continue;
                }
                if (buf[0] == '\n') {
                        continue;
                }

                nbytes = write(writefd, buf, len);              // 输入的数据以\n为分隔符
                if (nbytes < 0) {
                        perror("write");
                        continue;
                }
                if (nbytes != len) {                                    // 因为保证了输入长度小于PIPE_BUF,所以pipe必须保证输入的原子性,所以nbytes != len 是不应该的
                        printf("error in pipe write\n");
                }

                while ((nbytes = msg_read(readfd, buf, sizeof(buf))) > 0) {
                        fwrite(buf, 1, nbytes, stdout);
                }
                if (nbytes == -1) {
                        perror("msg_read error\n");
                        continue;
                }
                if (nbytes == -2) {
                        break;
                }

        }

        printf("client quit\n");
        close(readfd);                  // 向对方发送 EOF
        close(writefd);
        return;
}


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

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

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

        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];                            // 向量IO能保证原子性,并避免多余的内存消耗

                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;
}

void server(int readfd, int writefd)
{
        FILE *fp;
        int fd, nbytes;
        char buf[LINE_MAX];

        fp = fdopen(readfd, "r");                                       // readfd的数据流,使用\n作为分隔符,所以利用标准IO的行缓存机制
        if (fp == NULL) {
                perror("fdopen");
                return;
        }

        while (fgets(buf, sizeof(buf), fp)) {           // 以\n为分隔符

                char *ptr = strchr(buf, '\n');                  // 去掉分隔符
                *ptr = '\0';

                fd = open(buf, O_RDONLY);
                if (fd < 0) {                                                   // 若错误,则返回报错信息
                        strcpy(buf, strerror(errno));
                        if (msg_send(writefd, buf, strlen(buf)) < 0) {
                                perror("server write");
                                break;
                        }
                        msg_send(writefd, NULL, 0);
                        continue;
                }

                while ((nbytes = read(fd, buf, sizeof(buf))) > 0) {
                        msg_send(writefd, buf, nbytes);         // 报文头显示说明数据长度
                }
                if (nbytes < 0) {
                        strcpy(buf, strerror(errno));
                        if (msg_send(writefd, buf, strlen(buf)) < 0) {
                                perror("server write");
                                break;
                        }
                        msg_send(writefd, NULL, 0);
                }
                msg_send(writefd, NULL, 0);

                close(fd);
        }

        printf("server quit\n");
        close(writefd);
        close(fd);
        fclose(fp);
}

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

        if ((pipe(fd) < 0) || (pipe(fd + 2) < 0)) {
                perror("pipe");
                return -1;
        }

        signal(SIGPIPE, SIG_IGN);       // 写 无读端 的pipe 导致 SIGPIPE, SIGPIPE 默认动作为终止进程,忽略该信号,让write返回 EPIPE

        pid = fork();
        switch (pid) {
                case 0:
                        close(fd[2]);
                        close(fd[1]);           // 必须把 不需要的描述符关闭,否则 关闭 写端,对方无法受到 EOF
                        client(fd[0], fd[3]);
                        exit(0);
                        break;
                case -1:
                        break;
                default:
                        close(fd[0]);
                        close(fd[3]);
                        server(fd[2], fd[1]);
        }
        wait(NULL);

        return 0;
}


这篇关于linux系统编程——IPC——管道的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程