进程间通信-IPC(管道)

1.匿名管道pipe

2.命名管道mkfifo


1.匿名管道pipe

  • 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;
  • 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);
  • 单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中;
  • 数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

(1) 测试一下管道的大小:

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

int main()
{
  int fd[2];
  int count = 0;
  
  if(pipe(fd)<0)
  {
    perror("Fail to create pipe");
    exit(EXIT_FAILURE);
  }

  while(1)
  {
    write(fd[1],"a",sizeof(char));
    printf("count = %d.\n",++count);
  }
  return 0;
}

结果如下:
在这里插入图片描述
单独创建一个无名管道,并没有实际的意义。我们一般是在一个进程在由pipe()创建管道后,一般再由fork一个子进程,然后通过管道实现父子进程间的通信(因此也不难推出,只要两个进程中存在亲缘关系,这里的亲缘关系指的是具有共同的祖先,都可以采用管道方式来进行通信)

(2)从管道中读写数据

A.通过标准输入stdin给管道里输入数据并且通过标准输出读取管道中的数据
#include<unistd.h>
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>

#define N 10
#define MAX 100

int child_read_pipe(int fd)
{
  char buf[N];
  int n = 0;
  while(1)
  {
    n = read(fd,buf,sizeof(buf));//从fd文件中读取(整个buf的size:sizeof(buf))数据到buf
    buf[n] = '\0';				 //sizeof(数组名)计算的是整个数组的大小

    printf("read %d bytes:%s.\n",n,buf);

    if(strncmp(buf,"quit",4)==0)//如果读到的是quit 则结束循环
      break;
  }
return 0;
}


int parent_write_pipe(int fd)
{
 char buf[MAX] = {0};

 while(1)
 {
   printf(">");
   fgets(buf,sizeof(buf),stdin);//从stdin(标准输入文件(键盘输入))读取数据到buf
   buf[strlen(buf)-1] = '\0';////  给读取完的buf最后的字符加上\0,作为字符串的结束标志,用于strlen(buf)
   write(fd,buf,strlen(buf));//将buf(实际大小strlen(buf))的数据写入到fd文件中去
   usleep(500);

  if(strncmp(buf,"quit",4)==0)//如果写入的是quit 则结束循环
    break;
 }
 return 0;
}


int main()
{
  int pid;
  int fd[2];

  if(pipe(fd)<0)
  {
    perror("Fail to pipe");
    exit(EXIT_FAILURE);
  }

  if((pid = fork())<0)
  {
    perror("Fail to fork");
    exit(EXIT_FAILURE);
  }
  else if(pid == 0)
  {
    close(fd[1]);//子进程用于读,则关闭写段
    child_read_pipe(fd[0]);
  }
  else 
  {
    close(fd[0]);//父进程用于写,则关闭读端
    parent_write_pipe(fd[1]);
  }
  exit(EXIT_SUCCESS);
}

输出结果:
在这里插入图片描述

从以上验证我们可以看到:
<1>当写端存在时,管道中没有数据时,读取管道时将阻塞
<2>当读端请求读取的数据大于管道中的数据时,此时读取管道中实际大小的数据
<3>当读端请求读取的数据小于管道中的数据时,此时放回请求读取的大小数据

B.通过读取文件中的数据到管道,并且通过管道输出到另一个新文件中去
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>

#define MAX 100

int child_work(int pfd,char *fname)//创建文件fname,并且从管道pfd读数据到该文件中去
{
    int n,fd;
    char buf[MAX];

    if((fd = open(fname,O_WRONLY | O_CREAT | O_TRUNC,0666)) < 0)//创建新文件并打开
    {
        fprintf(stderr,"Fail to open %s : %s.\n",fname,strerror(errno));
        return -1;
    }

    while( n = read(pfd,buf,sizeof(buf)) )//将数据从管道读到buf中
    {
        write(fd,buf,n);//将buf中的数据写入到fd文件中去
    }
    
    close(pfd);
    close(fd);

    return 0;
}

int father_work(int pfd,char *fname)//从fname文件中读取数据到管道pfd
{
    int fd,n;
    char buf[MAX];

    if((fd = open(fname,O_RDONLY)) < 0)//打开已经存在的fname文件
    {
        fprintf(stderr,"Fail to open %s : %s.\n",fname,strerror(errno));
        return -1;
    }

    while(n = read(fd,buf,sizeof(buf)))//从fname文件中读取数据到buf中
    {
        write(pfd,buf,n);//将buf中的数据写入到pfd管道中
    }
    
    close(pfd);
    close(fd);

    return 0;
}

int main()
{
    int pid;
    int fd[2];

    if(pipe(fd) < 0)
    {
        perror("Fail to pipe");
        exit(EXIT_FAILURE);
    }

    if((pid = fork()) < 0)
    {
        perror("Fail to fork");
        exit(EXIT_FAILURE);
    
    }else if(pid == 0){
        
        close(fd[1]);
        child_work(fd[0],"pipe_read");
    
    }else{
    
        close(fd[0]);
        father_work(fd[1],"pipe_write");
        wait(NULL);
    }

    exit(EXIT_SUCCESS);
}
//注意:文件中使用了两个buf:是管道和文件链接的媒介

输出结果:
在这里插入图片描述


2.命名管道mkfifo

  • 匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。

  • 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。

  • 命名管道可以从命令行上创建,命令行方法是使用下面这个命令:

$ mkfifo filename

  • 命名管道也可以从程序里创建,相关函数有:

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

A.程序使用mkfifo函数创建一个命名管道文件test.fifo,将Makefile 的文件都读取到test.fifo文件中

mkfifo_write.c

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

int main()
{
  //管道操作
  umask(0);
  int ret;
  ret = mkfifo("./test.fifo",0664);
  if(ret < 0)
  {
    perror("mkfifo error");
    exit(EXIT_FAILURE);
  }

  //管道文件操作
  int fd;
  printf("start open ----------\n");
  fd = open("./test.fifo",O_WRONLY,0664);
  printf("end open ----------\n");
  if(fd < 0)
  {
    perror("open error");
    exit(EXIT_FAILURE);
  }
  printf("./test.fifo open success\n");

  //管道写数据
  while(1)
  {
    char buf[10] = {0};
    printf("I say:");
    fflush(stdout);
    scanf("%s",buf);
    //write("./test.fifo",buf,strlen(buf));
    write(fd,buf,strlen(buf));
  }

  close(fd);
  
  return 0;
}

mkfifo_read.c

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

int main()
{
    umask(0);
    int ret = mkfifo("./test.fifo", 0664);
    if (ret < 0) {
        if (errno != EEXIST) {
            perror("mkfifo error");
            return -1;
        }
    }

    printf("start open -------\n");
    int fd = open("./test.fifo", O_RDONLY);
    printf("end open -------\n");
    if (fd < 0) {
        perror("open error");
        return -1;
    }
    printf("fifo:%s open success!!\n", "./test.fifo");

    while(1) {
        sleep(5);
        char buf[10] = {0};
        read(fd, buf, 9;
        printf("peer say: %s\n", buf);
    }
    close(fd);
    return 0;
}

写入端输出结果:
在这里插入图片描述
输出端输出结果:
在这里插入图片描述

B.创建管道将一个文件中的数据拷贝到另一个文件中:

(1) 首先通过命令创建管道(也可通过函数mkfifo)

在这里插入图片描述

(2) 写两个文件:mkfifo_read.c 和 mkfifo_write.c

mkfifo_read.c

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

//这个代码是  将makefile文件里的数据写入到管道文件(test.fifo)中去
int main()
{

  //以只读的方式打开makefile文件
  int infd;
  infd = open("makefile",O_RDONLY);
  if(infd == -1)
  {
    perror("open error");
    exit(EXIT_FAILURE);
  }
  printf("open makefile success\n");

  //以只写的方式打开管道test.fifo
  int outfd;
  outfd = open("test.fifo",O_WRONLY);
  if(outfd == -1)
  {
    perror("open error");
    exit(EXIT_FAILURE);
  }
  printf("open test.fifo success\n");

  //通过字符数组buf,将文件makefile里的数据写入管道
  char buf[1024];
  int n;
  while((n = read(infd,buf,1023))>0)
  {
    write(outfd,buf,strlen(buf));
  }

  //最后别忘记关闭两个文件:makefile文件 和 管道文件
  close(infd);
  close(outfd);

  return 0;
}

mkfifo_write.c

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<sys/stat.h>
#include<signal.h>
#include<errno.h>

//这个代码是 将管道(test.fifo)中的文件写入到新文件makefile2中去

int main()
{
  umask(0);
  //首先以可写方式创建并打开新文件makefile2
  int outfd;
  outfd = open("makefile2",O_WRONLY|O_CREAT|O_TRUNC,0664);
  if(outfd == -1)
  {
    perror("open error");
    exit(EXIT_FAILURE);
  }
  printf("create makefile2 success\n");

  //然后以可读方式打开管道
  int infd;
  infd = open("test.fifo",O_RDONLY);
  if(infd == -1)
  {
    perror("open error");
    exit(EXIT_FAILURE);
  }
  printf("open test.fifo success\n");

  //最后借助字符数组buf,将管道中的数据写入到makefile2文件中
  char buf[1024];
  int n;
  while((n = read(infd,buf,1023))>0)
  {
    write(outfd,buf,strlen(buf));
  }

  //关闭两个文件:makefile2文件 和 管道文件
  close(infd);
  close(outfd);

  unlink("test.fifo");
  return 0;
}

(3) 再写一个makefile文件

all: mkfifo_write mkfifo_read 
mkfifo_write:mkfifo_write.c
	gcc $^ -o $@
mkfifo_read:mkfifo_read.c
	gcc $^ -o $@

(4) 然后运行读取程序 会阻塞在这一步

在这里插入图片描述

(5) 阻塞时在另一个终端运行写入文件程序

在这里插入图片描述

原终端读取程序不再阻塞,运行成功
在这里插入图片描述
文件通过管道拷贝成功

猜你喜欢

转载自blog.csdn.net/PNUHC/article/details/92079653