管道的概念:


管道是一种最基本的IPC机制,由pipe函数创建:

#include<unistd.h>

int pipe(int fileds[2]);

调用pipe函数时在内核中开辟一块缓冲区用于通信,它有一个读端一个写端,然后通过filedes参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向管道的写端。


进程在管道间通信:

1.父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端。

2.父进程调用fork创建子进程,子进程有两个文件描述符指向同一管道。

3.父进程关闭管道读端,子进程关闭管道斜段。父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列实现的,数据从写端流入,从读端流出,这样就实现了进程间通信。


代码实现如下:

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<errno.h>
  4 #include<string.h>
  5 
  6 int main()
  7 {
  8     int _pipe[2];
  9     int ret = pipe(_pipe);
 10     if(ret == -1){
 11        printf("create pipe erro!errno code is:%d\n",errno);
 12        return 1;
 13       }
 14     pid_t id=fork();
 15     if(id<0){
 16       printf("fork error!");
 17       return 2;
 18      }else if(id == 0){
 19        close(_pipe[0]);
 20        int i=0;
 21        char* _mesg_c=NULL;
 22        while(i<100){
 23          _mesg_c="I am a child!";
 24            write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
 25            sleep(1);
 26            i++;
 27        }
 28      }else{//father
 29            close(_pipe[1]);
 30            char _mesg[100];
 31            int j=0;
 32            while(j<100) {
 33            memset(_mesg,'\0',sizeof(_mesg));
 34            read(_pipe[0],_mesg,sizeof(_mesg));
 35            printf("%s\n",_mesg);
 36                 j++;
 37            }
 38         }
 39         return 0;
 40 
 41 
 42 }

 程序运行结果如下:

wKiom1cOXvPhVVGWAAA0uFGECIc282.png

管道的四个特殊情况:


1.所有指向管道写端的文件描述符都关闭了,仍然有进程从管道的读端读取数据,当管道中剩余的数据都被读取后,再次读取会返回0,就像读到文件末尾一样。


代码实现:

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<errno.h>
  4 #include<string.h>
  5 #include<sys/wait.h>
  6 int main()
  7 {
  8     int _pipe[2];
  9     int ret = pipe(_pipe);
 10     if(ret == -1){
 11        printf("create pipe erro!errno code is:%d\n",errno);
 12        return 1;
 13       }
 14     pid_t id=fork();
 15     if(id<0){
 16       printf("fork error!");
 17       return 2;
 18      }else if(id == 0){//child
 19        close(_pipe[0]);
 20        int i=0;
 21        char* _mesg_c=NULL;
 22        while(i<10){
 23          _mesg_c="I am a child!";
 24            write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
 25            sleep(1);
 26            i++;
 27        }
 28            close(_pipe[1]);
 29      }else{//father
 30            close(_pipe[1]);
 31            char _mesg[100];
 32            int j=0;
 33            while(j<100) {
 34            memset(_mesg,'\0',sizeof(_mesg));
 35            int ret = read(_pipe[0],_mesg,sizeof(_mesg));
 36            printf("%s:code is:%d\n",_mesg,ret);
 37                 j++;
 38            }
 39            if(waitpid(id,NULL,0)<0)
 40              {
 41                return 3;
 42              }
 43         }
 44         return 0;
 45 }

运行结果如下:

wKiom1cOXxbzTfyVAAA1s_ErxcU937.png

2.指向管道写端的文件描述符没关闭,持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读取数据,当管道中剩余的数据都被读取后,再次读取会阻塞,直到管道中有数据可读了才读取数据并返回。


代码实现:

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<errno.h>
  4 #include<string.h>
  5 #include<sys/wait.h>
  6 
  7 int main()
  8 {
  9     int _pipe[2];
 10     int ret = pipe(_pipe);
 11     if(ret == -1){
 12        printf("create pipe erro!errno code is:%d\n",errno);
 13        return 1;
 14       }
 15     pid_t id=fork();
 16     if(id<0){
 17       printf("fork error!");
 18       return 2;
 19      }else if(id == 0){//child
 20        close(_pipe[0]);
 21        int i=0;
 22        char* _mesg_c=NULL;
 23        while(i<20){
 24           if(i<10){
 25               _mesg_c="I am a child!";
 26               write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
 27             }
 28            sleep(1);
 29            i++;
 30        }
 31            close(_pipe[1]);
 32      }else{//father
 33            close(_pipe[1]);
 34            char _mesg[100];
 35            int j=0;
 36            while(j<20) {
 37            memset(_mesg,'\0',sizeof(_mesg));
 38            int ret = read(_pipe[0],_mesg,sizeof(_mesg));
 39            printf("%s:code is:%d\n",_mesg,ret);
 40                 j++;
 41            }
 42            if(waitpid(id,NULL,0)<0)
 43              {
 44                return 3;
 45              }
 46         }
 47         return 0;
 48 }

运行结果:

wKioL1cOX-3wtTrDAAA2nuAqo64581.png

3.如果所有指向管道读端的文件描述符都关闭了,这时有进程向管道的写端write,该进程会收到信号SIGPIPE,会导致进程异常终止。


代码实现:

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<errno.h>
  4 #include<string.h>
  5 #include<sys/wait.h>
  6 
  7 int main()
  8 {
  9     int _pipe[2];
 10     int ret = pipe(_pipe);
 11     if(ret == -1){
 12        printf("create pipe erro!errno code is:%d\n",errno);
 13        return 1;
 14       }
 15     pid_t id=fork();
 16     if(id<0){
 17       printf("fork error!");
 18       return 2;
 19      }else if(id == 0){//child
 20        close(_pipe[0]);
 21        int i=0;
 22        char* _mesg_c=NULL;
 23        while(i<20){
 24           if(i<10){
 25               _mesg_c="I am a child!";
 26               write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
 27             }
 28            sleep(1);
 29            i++;
 30        }
 31      }else{//father
 32            close(_pipe[1]);
 33            char _mesg[100];
 34            int j=0;
 35            while(j<3) {
 36            memset(_mesg,'\0',sizeof(_mesg));
 37            int ret = read(_pipe[0],_mesg,sizeof(_mesg));
 38            printf("%s:code is:%d\n",_mesg,ret);
 39                 j++;
 40            }
 41            close(_pipe[0]);
 42            sleep(10);
 43            if(waitpid(id,NULL,0)<0)
 44              {
 45                return 3;
 46              }
 47         }
 48         return 0;
 49 }

运行结果:

wKioL1cOYAmxUl-ZAAAdd7ZtrLM788.png

4.如果有指向管道读端的文件描述符没关闭,而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。


代码实现:

 

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<errno.h>
  4 #include<string.h>
  5 #include<sys/wait.h>
  6 
  7 int main()
  8 {
  9     int _pipe[2];
 10     int ret = pipe(_pipe);
 11     if(ret == -1){
 12        printf("create pipe erro!errno code is:%d\n",errno);
 13        return 1;
 14       }
 15     pid_t id=fork();
 16     if(id<0){
 17       printf("fork error!");
 18       return 2;
 19      }else if(id == 0){//child
 20 //       close(_pipe[0]);
 21        int i=0;
 22        char* _mesg_c=NULL;
 23        while(i<20){
 24 //          if(i<10){
 25               _mesg_c="I am a child!";
 26               write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
 27   //          }
 28            sleep(1);
 29            i++;
 30        }
 31      close(_pipe[1]);
 32      }else{//father
 33            close(_pipe[1]);
 34            char _mesg[100];
 35            int j=0;
 36            while(j<3) {
 37            memset(_mesg,'\0',sizeof(_mesg));
 38            int ret = read(_pipe[0],_mesg,sizeof(_mesg));
 39            printf("%s:code is:%d\n",_mesg,ret);
 40                 j++;
 41            }
 42   //         close(_pipe[0]);
 43            sleep(10);
 44            if(waitpid(id,NULL,0)<0)
 45              {
 46                return 3;
 47              }
 48         }
 49         return 0;
 50 }

运行结果:

wKiom1cOX3ehARpSAAAhIOj448c515.png


管道通信的特点:

1.它只能用于具有亲缘关系的进程之间的通信(也就是父子进程或者兄弟进程之间) 。 

2.它是一个单向通信的通信模式,具有固定的读端和写端。 

3.管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的 read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。


命名管道:

管道应用的一个重大限制是它没有名字,因此,只能用于具有亲缘关系的进程间通信,在命名管道(namedpipe或FIFO)提出后,该限制得到了克服。

FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。值得注意的是, FIFO严格遵循先进先出(firstinfirstout),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。


系统调用形式:

#include<sys/types.h>

#include<sys/stat.h>

int mknod(const char*path,mode_t mod,dev_t dev)

int mkfifo(const  char*path,mode_t mode)

该函数的第一个参数是一个路径名,也就是创建后FIFO的名字。第二个参数与打开普通文件的open(),函数中的mode参数相同,有以下几种:

O_RDONLY:读管道

O_WRONLY:写管道

O_RDWR:读写管道

O_NONBLOCK:非阻塞

O_CREAT:

O_EXCL:

一般文件的I/O函数都可以用于FIFO,如close、read、write等等。

FIFO读规则

约定:如果一个进程为了从FIFO中读取数据而阻塞打开FIFO,那么称该进程内的读操作为设置了阻塞标志的读操作。

– 如果有进程写打开FIFO,且当前FIFO内没有数据,则对于设置了阻塞标志的读操作来说,将一直阻塞。对于没有设置阻塞标志读操作来说则返回-1,当前errno值为EAGAIN,提醒以后再试。

– 对于设置了阻塞标志的读操作说,造成阻塞的原因有两种:

 A.当前FIFO内有数据,但有其它进程在读这些数据;

 B.另外就是FIFO内没有数据。解阻塞的原因则是FIFO中有新的数据写入,不论写入数据量的大小,也不论读操作请求多少数据量。读打开的阻塞标志只对本进程第一个读操作施加作用,如果本进程内有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其它将要执行的读操作将不再阻塞,即使在执行读操作时,FIFO中没有数据也一样(此时,读操作返回0)。如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞。

FIFO写规则

约定:如果一个进程为了向FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操作为设置了阻塞标志的写操作。

对于设置了阻塞标志的写操作:

– 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作。

– 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。

对于没有设置阻塞标志的写操作:

– 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。

– 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写;


代码实现:

fiforead.c

  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<sys/stat.h>
  4 #include<unistd.h>
  5 #include<fcntl.h>
  6 #include<string.h>
  7 #define _PATH_"/tmp/file.tmp"
  8 #define _SIZE_ 100
  9 
 10 int main()
 11 {
 12   int fd = open(_PATH_,O_RDONLY);
 13   if(fd<0){
 14     printf("open file error!\n");
 15     return 1;
 16   }
 17   char buf[_SIZE_];
 18   memset(buf,'\0',sizeof(buf));
 19   while(1){
 20     int ret = read(fd,buf,sizeof(buf));
 21     if(ret<=0)//error or end of file
 22       {
 23          printf("read end or error!\n");
 24          break;
 25       }
 26   }
 27    close(fd);
 28      return 0;
 29 }

 

fifowrite.c

  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<sys/stat.h>
  4 #include<unistd.h>
  5 #include<fcntl.h>
  6 #include<string.h>
  7 #define _PATH_"/tmp/file.tmp"
  8 #define _SIZE_ 100
  9 
 10 int main()
 11 {
 12   int ret=mkfifo(_PATH_,0666|S_IFIFO);
 13   if(ret==-1){
 14       printf("mkfifo error\n");
 15       return 1;
 16     }
 17 
 18   int fd = open(_PATH_,O_RDONLY);
 19   if(fd<0){
 20     printf("open file error!\n");
 21     return 1;
 22   }
 23   char buf[_SIZE_];
 24   memset(buf,'\0',sizeof(buf));
 25   while(1){
 26     scanf("%s",buf);
 27     int ret = write(fd,buf,sizeof(buf)+1);
 28     if(ret<=0)//error or end of file
 29       {
 30          printf("write  error!\n");
 31          break;
 32       }
 33       if(strncmp(buf,"quit",4)==0){
 34         break;
 35        }
 36   }
 37    close(fd);
 38      return 0;
 39 }