1、管道(pipe)特点

a>.通过 pipe 建立通信管道,fork 创建子进程(传递文件描述符),且这种关系只能由父进程建立, 局限于有血缘关系的进程间的通讯

b>.管道是文件,并且只存于内存中,当进程终结时,管道也消失

c>.管道只能是单向通信,一端输入,另一端输出

d>.管道满时,写阻塞。管道空时,读阻塞。


2、创建管道

a>.父进程 pipe 管道,得到文件描述符指向管道;

b>.父进程 fork 子进程,子进程也有了文件描述符指向管道;

c>.关闭多余的文件描述符,完成单向通信。

代码如下:

#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
int main()
{
     int _pipe[2];
     int ret = pipe(_pipe);
     if(ret == -1)
     {
          perror("pipe");
          return -1;
     }
     pid_t id = fork();
     if(id < 0)
     {//创建进程失败
          perror("fork");
          return -1;
     }
     else if(id == 0)
     {//子进程
          close(_pipe[0]);
          int i = 0;
          char *_mesg_c = NULL;
          while(i++ < 10)
          {
               _mesg_c = "i am child";
               write(_pipe[1], _mesg_c, strlen(_mesg_c)+1);
               sleep(2);
          }
          close(_pipe[1]);
     }
     else
     {//父进程
          close[1];
          char _mesg[100];
          int j = 0;
          while(j++ < 10)
          {
               memset(_mesg, '\0', sizeof(_mesg));
               read(_pipe[0], _mesg, sizeof(_mesg));
               printf("%s\n", _mesg);
          }
          close(_pipe[0]);
     }
     return 0;
}

3、使用管道还要注意以下四种情况(阻塞I/O操作)

a>.指向管道写端的文件描述符关闭了,但还有进程从管道端读数据,那么当管道中数据读取完后,再次 read 会返回;

模拟代码如下:

#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>
int main()
{
     int _pipe[2];
     int ret = pipe(_pipe);
     if(ret < 0)
     {
          perror("pipe");
          return -1;
     }
     pid_t id = fork();
     if(fork < 0)
     {
          perror("fork");
          return -1;
     }
     if(fork == 0)
     {//子进程
          close(_pipe[0]);
          char *_mesg_c = NULL;
          int i = 0;
          while(i++ < 10)
          {
               char *_mesg_c = "i am child";
               write(_pipe[1], _mesg_c, strlen(_mesg_c)+1);
               sleep(2);
          }
          close(_pipe[1]);
     }
     else
     {//父进程
          close(_pipe[1]);
          char _mesg[100];
          int j = 0;
          while(j++ < 100)
          {
               memset(_mesg, '\0', sizeof(_mesg));
               read(_pipe[0], _mesg, sizeof(_mesg));
               printf("%s", _mesg);
          }
          if(waitpid(id, NULL, 0) < 0)
          {
               return 1;
          }
     }
     return 0;
}

b>.指向管道的写端文件描述符没关闭,但是又没有向管道写数据,当管道中的数据读取完成后,再次 read 会阻塞,直到管道中有数据可读了,才读取数据并返回;

模拟代码如下:

#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>
int main()
{
     int _pipe[2];
     int ret = pipe(_pipe);
     if(ret < 0)
     {
          perror("pipe");
          return -1;
     }
     pid_t id = fork();
     if(id < 0)
     {
          perror("fork");
          return -1;
     }
     else if(id == 0)
     {//子进程
          close(_pipe[0]);
          char *_mesg_c = NULL;
          int i = 0;
          while(i++ < 20)
          {
               if(i < 10)
               {
                    _mesg_c = "i am child";
                    write(_pipe[1], _mesg_c, strlen(_mesg_c)+1);
               }
               sleep(1);
          }
          close(_pipe[1]);
     }
     else
     {//父进程
          close(_pipe[1]);
          char _mesg[100];
          int j = 0;
          while(j++ < 20)
          {
               memset(_mesg, '\0', sizeof(_mesg));
               read(_pipe[0], _mesg, 0);
               printf("%s", _mesg);
          }
          if(waitpid(id, NULL, 0) < 0)
          {
               return 1;
          }
     }
     return 0;
}

c>.指向管道的读端文件描述符关闭了,这时还有进程向管道的写端写,那么进程就会收到信号 SIDPIPE ,导致进程异常终止;

模拟代码如下:

#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>
int main()
{
     int _pipe[2];
     int ret = pipe(_pipe);
     if(ret < 0)
     {
          perror("pipe");
          return -1;
     }
     pid_t id = fork();
     if(id < 0)
     {
          perror("fork");
          return -1;
     }
     else if(id == 0)
     {//子进程
          close(_pipe[0]);
          char *_mesg_c = NULL;
          int i = 0;
          while(i++ < 20)
          {
                _mesg_c = "i am child";
                write(_pipe[1], _mesg_c, strlen(_mesg_c)+1);
               sleep(1);
          }
          close(_pipe[1]);
     }
     else
     {//父进程
          close(_pipe[1]);
          char _mesg[100];
          int j = 0;
          while(j++ < 10)
          {
               memset(_mesg, '\0', sizeof(_mesg));
               read(_pipe[0], _mesg, 0);
               printf("%s", _mesg);
          }
          close(_pipe[0]);
          if(waitpid(id, NULL, 0) < 0)
          {
               return 1;
          }
     }
     return 0;
}

d>.指向管道的读端文件描述符没关闭,但是读端的进程也没有读数据,那么有进程向管道写数据,当写满了再次 write 会阻塞,直到管道中有空位置才写入数据并返回。

模拟代码如下:

#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>
int main()
{
     int _pipe[2];
     int ret = pipe(_pipe);
     if(ret < 0)
     {
          perror("pipe");
          return -1;
     }
     pid_t id = fork();
     if(id < 0)
     {
          perror("fork");
          return -1;
     }
     else if(id == 0)
     {//子进程
          close(_pipe[0]);
          char *_mesg_c = NULL;
          int i = 0;
          while(i++ < 20)
          {
               _mesg_c = "i am child";
               write(_pipe[1], _mesg_c, strlen(_mesg_c)+1);
               sleep(1);
          }
          close(_pipe[1]);
     }
     else
     {//父进程
          close(_pipe[1]);
          char _mesg[100];
          int j = 0;
          while(j++ < 20)
          {
               if(j < 5)
               {
               memset(_mesg, '\0', sizeof(_mesg));
               read(_pipe[0], _mesg, 0);
               printf("%s", _mesg);
               }
               sleep(1);
          }
          close(_pipe[0]);
          if(waitpid(id, NULL, 0) < 0)
          {
               return 1;
          }
     }
     return 0;
}