管道(pipe)

管道概念

  Linux下一切皆文件,我们可以创建一个管道文件进行通信,实际上是调用pipe函数在内核中开辟一块缓冲区(称为管道)用于通信,使不同的进程看到同一份资源,管道是一种最基本的IPC机制,由pipe函数创建 。下面我们将看到两种管道-匿名管道及命名管道的实现

匿名管道通信的实现

  它有一个读端一个写端,然后通过filedes参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向管道的写端(很好记,就像0是标准输入1是标准输出一样)。所以管道在用户程序看起来就像一个打开的文件,通过read(filedes[0]);或者write(filedes[1]);向这个文件读写数据其实是在读写内核缓冲区。pipe函数调⽤用成功返回0,调⽤用失败返回-1。

#include <unistd.h>  
int pipe(int file_descriptor[2]);  

  我们可以看到pipe函数的定义非常特别,该函数在数组中墙上两个新的文件描述符后返回0,如果返回返回-1,并设置errno来说明失败原因。

  数组中的两个文件描述符以一种特殊的方式连接起来,数据基于先进先出的原则,写到_pipe[1]的所有数据都可以从_pipe[0]读回来。由于数据基于先进先出的原则,所以读取的数据和写入的数据是一致的。

特别提醒:

  1. pipe是基于文件描述符工作的,所以在使用pipe后,数据必须要用底层的read和write调用来读取和发送。
  2. 不要用_pipe[0]写数据,也不要用_pipe[1]读数据,其行为未定义的,但在有些系统上可能会返回-1表示调用失败。数据只能从_pipe[0]中读取,数据也只能写入到_pipe[1],不能倒过来。

这里写图片描述

首先,我们在原先的进程中创建一个管道,然后再调用fork创建一个新的进程,最后通过管道在两个进程之间传递数据。父进程进行读操作,子进程进行写操作。代码如下:

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

int main()
{
    int _pipe[2];
    int ret = pipe(_pipe);
    if(ret == -1)
    {
        printf("creat pipe error!errno code is:%d\n",errno);
        return 1;
    }
    pid_t id = fork();
    if(id < 0)
    {
        printf("fork error!");
        return 2;
    }
    else if(id==0)//child
    {
        close(_pipe[0]);//关掉读
        int i = 0;
        char* buf = NULL;
        while(i<20)
        {
            if(i<10)
            {  
                buf = " i am child!";
                write(_pipe[1], buf,strlen(buf)+1);//写入
            }
            sleep(1);
            i++;
        }
    }
    else//father
    {
        close(_pipe[1]);//关掉写
        char buf[100];
        int j = 0;
        while(j<3) //读取三次
        {
            memset(buf,'\0',sizeof(buf));//清空buf
            int ret = read(_pipe[0],buf,sizeof(buf));//读出
            printf("%s:code is %d\n",buf,ret);
            j++;
        }
        close(_pipe[0]);
    }
    return 0;
}

这里写图片描述

命名管道(双向管道)

  上述匿名管道存在一个很大的缺陷,那就是匿名管道只能用于父子进程或者说有血缘关系的进程之间,命名管道的存在就是为了解决这个缺陷。

  命名管道也被称为FIFO文件,它是一种特殊类型的文件,它在文件系统中以文件名的形式存在,但是它的行为却和之前所讲的没有名字的管道(匿名管道)类似。

  Linux中所有的事物都可被视为文件,所以对命名管道的使用也就变得与文件操作非常的统一,也使它的使用非常方便,同时我们也可以像平常的文件名一样在命令中使用。

#include<sys/types.h>
#include<sys/stat.h>

int mkfifo(const char *pathname,mode_t mode);

打开FIFO文件通常有四种方式:

1. open(const char *path, O_RDONLY); 
2. open(const char *path, O_RDONLY | O_NONBLOCK); 
3. open(const char *path, O_WRONLY); 
4. open(const char *path, O_WRONLY | O_NONBLOCK); 

在open函数的调用的第二个参数中,选项O_NONBLOCK表示非阻塞,加上这个选项后,表示open调用是非阻塞的,如果没有这个选项,则表示open调用是阻塞的。

open调用的阻塞是什么一回事呢?很简单,对于以只读方式(O_RDONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_RDONLY),除非有一个进程以写方式打开同一个FIFO,否则它不会返回;如果open调用是非阻塞的的(即第二个参数为O_RDONLY | O_NONBLOCK),则即使没有其他进程以写方式打开同一个FIFO文件,open调用将成功并立即返回。

对于以只写方式(O_WRONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_WRONLY),open调用将被阻塞,直到有一个进程以只读方式打开同一个FIFO文件为止;如果open调用是非阻塞的(即第二个参数为O_WRONLY | O_NONBLOCK),open总会立即返回,但如果没有其他进程以只读方式打开同一个FIFO文件,open调用将返回-1,并且FIFO也不会被打开。

我们来写两个程序,server和client,让他们通过管道文件实现通信。

server.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("Data.txt", 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);  
}  

client.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);  
}  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值