linux IPC 之管道

管道简介

管道顾名思义类似于我们生活中的水管,只不过其中流动的是“数据”或者说一个一个字节,只能单向流动的我们称为“半双工”,能双向流动的称为“全双工”,其有两个端点,数据流入的那一端称为“写端”,反之则称为“读端”,这两个端点实际上是两个“描述字”。这样的管道可以连接在两个进程之间,成为数据传输的通道。如下图所示:
管道通信示意图
若pipe为无名管道,则进程A和进程B具有父子关系,若A,B没有父子关系则pipe为命名管道!从上图可以看到A,B进程都可以对管道进行读写,通常情况是一个进程写入,另一个进程读出。进程端可以根据需要关闭pipe的写/读端,如下图所示:
管道读写

非命名管道

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

#define BUFF_SZ 256

int main()
{
    printf("app start...\n");

    pid_t          pid;
    int            pipe_fd[2];
    char           buf[BUFF_SZ];
    const char     data[] = "hi, this is the test data";
    int            bytes_read;
    int            bytes_write;

    //clear buffer, all bytes as 0
    memset(buf, 0, sizeof(buf));

    //creat pipe
    if(pipe(pipe_fd) < 0)
    {
        printf("[ERROR] can not create pipe\n");
        exit(1);
    }


    //fork an new process
    if(0 == (pid=fork()))
    {
        //close the write-point of pipe in child process
        close(pipe_fd[1]);

        //read bytes from read-point of pipe in child process
        if((bytes_read = read(pipe_fd[0], buf, BUFF_SZ)) > 0)
        {
            printf("%d bytes read from pipe : '%s'\n", bytes_read, buf);
        }

        //close read-point of pipe in child process
        close(pipe_fd[0]);
        exit(0);
    }


    //close read-point of pipe in parent process
    close(pipe_fd[0]);

    //write bytes to write-point of pipe in parent process
    if((bytes_write = write(pipe_fd[1], data, strlen(data))))
    {
        printf("%d bytes wrote to pipe : '%s'\n", bytes_write, data);
    }

    //close write-point of pipe in parent process
    close(pipe_fd[1]);

    //wait child process exit
    waitpid(pid, NULL, 0);

    printf("app end\n");

    return 0;
}

命名管道(fifo)

创建命名管道

我们可以使用两下函数之一来创建一个命名管道,他们的原型如下:

#include <sys/types.h>  
#include <sys/stat.h>  
int mkfifo(const char *filename, mode_t mode);  
int mknod(const char *filename, mode_t mode | S_IFIFO, (dev_t)0);  

这两个函数都能创建一个FIFO文件,注意是创建一个真实存在于文件系统中的文件,filename指定了文件名,而mode则指定了文件的读写权限。
mknod是比较老的函数,而使用mkfifo函数更加简单和规范,所以尽量使用mkfifo而不是mknod。

访问命名管道

  1. 打开创建的管道
    与打开其他文件一样,FIFO文件也可以使用open调用来打开。打开FIFO文件通常有四种方式,分别如下:
    open(const char *path, O_RDONLY);//阻塞读
    open(const char *path, O_RDONLY | O_NONBLOCK);//非阻塞读
    open(const char *path, O_WRONLY);//阻塞写
    open(const char *path, O_WRONLY | O_NONBLOCK);//非阻塞写

注意:1、程序不能以O_RDWR模式打开FIFO文件进行读写操作,而其行为也未明确定义,因为如一个管道以读/写方式打开,进程就会读回自己的输出,同时我们通常使用FIFO只是为了单向的数据传递。2、传递给open调用的是FIFO的完整路径名。

  1. 对管道进行读写
    和普通的文件读写一致。

命名管道实列

先创建一个进程A,其负责创建FIFO管道,读取pipeA.c的数据,通过创建的FIFO通道将读取的数据发送给进程B:

/*
 * file :pipeA.C 
 */

#include <unistd.h>  
#include <stdlib.h>  
#include <fcntl.h>  
#include <limits.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <stdio.h>  
#include <string.h>  

int main()  
{  
    const char *fifo_name = "/tmp/my_fifo";  
    int pipe_fd = -1;  
    int data_fd = -1;  
    int res = 0;  
    const int open_mode = O_WRONLY;  
    int bytes_sent = 0;  
    char buffer[PIPE_BUF + 1];  

    if(access(fifo_name, F_OK) == -1)  
    {  
        //管道文件不存在  
        //创建命名管道  
        res = mkfifo(fifo_name, 0777);  
        if(res != 0)  
        {  
            fprintf(stderr, "Could not create fifo %s\n", fifo_name);  
            exit(EXIT_FAILURE);  
        }  
    }  

    printf("Process %d opening FIFO O_WRONLY\n", getpid());  
    //以只写阻塞方式打开FIFO文件,以只读方式打开数据文件  
    pipe_fd = open(fifo_name, open_mode);  
    data_fd = open("pipeA.c", O_RDONLY);  
    printf("Process %d result %d\n", getpid(), pipe_fd);  

    if(pipe_fd != -1)  
    {  
        int bytes_read = 0;  
        //向数据文件读取数据  
        bytes_read = read(data_fd, buffer, PIPE_BUF);  
        buffer[bytes_read] = '\0';  
        while(bytes_read > 0)  
        {  
            //向FIFO文件写数据  
            res = write(pipe_fd, buffer, bytes_read);  
            if(res == -1)  
            {  
                fprintf(stderr, "Write error on pipe\n");  
                exit(EXIT_FAILURE);  
            }  
            //累加写的字节数,并继续读取数据  
            bytes_sent += res;  
            bytes_read = read(data_fd, buffer, PIPE_BUF);  
            buffer[bytes_read] = '\0';  
        }  
        close(pipe_fd);  
        close(data_fd);  
    }  
    else  
        exit(EXIT_FAILURE);  

    printf("Process %d finished\n", getpid());  
    exit(EXIT_SUCCESS);  
}  

然后创建进程B,它从管道中读取数据并将读取的数据写入DataFormFIFO.txt文件。最终DataFormFIFO.txt应和pipeA.c内容一致:

/*
 * file:pipeB.c
 */

#include <unistd.h>  
#include <stdlib.h>  
#include <stdio.h>  
#include <fcntl.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <limits.h>  
#include <string.h>  

int main()  
{  
    const char *fifo_name = "/tmp/my_fifo";  
    int pipe_fd = -1;  
    int data_fd = -1;  
    int res = 0;  
    int open_mode = O_RDONLY;  
    char buffer[PIPE_BUF + 1];  
    int bytes_read = 0;  
    int bytes_write = 0;  
    //清空缓冲数组  
    memset(buffer, '\0', sizeof(buffer));  

    printf("Process %d opening FIFO O_RDONLY\n", getpid());  
    //以只读阻塞方式打开管道文件,注意与fifowrite.c文件中的FIFO同名  
    pipe_fd = open(fifo_name, open_mode);  
    //以只写方式创建保存数据的文件  
    data_fd = open("DataFormFIFO.txt", O_WRONLY|O_CREAT, 0644);  
    printf("Process %d result %d\n",getpid(), pipe_fd);  

    if(pipe_fd != -1)  
    {  
        do  
        {  
            //读取FIFO中的数据,并把它保存在文件DataFormFIFO.txt文件中  
            res = read(pipe_fd, buffer, PIPE_BUF);  
            bytes_write = write(data_fd, buffer, res);  
            bytes_read += res;  
        }while(res > 0);  
        close(pipe_fd);  
        close(data_fd);  
    }  
    else  
        exit(EXIT_FAILURE);  

    printf("Process %d finished, %d bytes read\n", getpid(), bytes_read);  
    exit(EXIT_SUCCESS);  
}  

运行程序便会发现,无论是先运行A或是B,先运行起来的都会等待另外一个,这时open函数第二个参数的原因,我们可以添加O_NONBLOCK选项来取消阻塞。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值