进程间通信(管道)

进程间通信目的:
<1>.数据传输:一个进程需要将它的数据发送给另一个进程。
<2>.资源共享:多个进程之间共享同样的资源。
<3>.通知事件:一个进程需要向另一个进程发送消息,通知它发生了某种事件(如进程终止时要通知父进程)。
<4>.进程控制:有些进程需要完全控制另一个进程的执行(如debug进程)。
进程间通信分类:
管道:
.匿名管道pipe
.命名管道

管道:管道是Unix中最古老的进程间通信的形式。
把从一个进程连接到另一个进程的一个数据统称为一个“管道”。
一、匿名管道:

#include<unistd.h>
功能:创建无名管道
原型:
int pipe(int fd[2]);
参数:
fd:文件描述符数组,其中fd[0]表示读端,fd[1]表示写端
返回值:成功返回0,失败返回错误码。

例1:从键盘上读取数据,写入管道,读取管道,写入屏幕

 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 
  6 int main()
  7 {
  8         int fds[2];
  9         char buf[1024]={0};
 10         int len=0;
 11         if(pipe(fds)==-1){
 12                 perror("pipe"),exit(1);}
 13         //read from stdin
 14         printf("pipe ok\n");
 15         while(1){
 16                 ssize_t read_size=read(0,buf,sizeof(buf)-1);
 17                 if(read_size<0){
 18                         perror("read");
 19                         return 1;
 20                         }
 21                 if(read_size==0){//EOF
 22                         printf("read done\n");
 23                         return 0;
 24                 }
 25                 buf[read_size]='\0';
 26                 //write to pipe
 27                 write(fds[1],buf,strlen(buf));
 28                 char out_buf[1024]={0};
 29                 //read from pipe
 30                 read_size=read(fds[0],out_buf,sizeof(out_buf)-1);
 31                 if(read_size<0){
 32                         perror("read");
 33                         return 1;
 34                         }
 35                 if(read_size==0){
 36                         printf("read done\n");
 37                         return 0;
 38                         }
 39         out_buf[read_size]='\0';
 40         //write to stdout
 41         write(1,out_buf,strlen(out_buf));
 42         }
 43         return 0;
 44 }

这里写图片描述
管道如何实现进程间的通信

(1)父进程创建管道,得到两个⽂件描述符指向管道的两端

(2)父进程fork出子进程,⼦进程也有两个⽂件描述符指向同⼀管道。

(3)父进程关闭fd[0],子进程关闭fd[1],即⽗进程关闭管道读端,⼦进程关闭管道写端(因为管道只支持单向通信)。⽗进程可以往管道⾥写,⼦进程可以从管道⾥读,管道是⽤环形队列实现的,数据从写端流⼊从读端流出,这样就实现了进程间通信。
这里写图片描述
这里写图片描述

例2:用代码实现管道通信

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 
  6 int main()
  7 {
  8         int fds[2];
  9         if(pipe(fds)==-1){
 10                 perror("pipe");
 11                 exit(1);}
 12         pid_t pid=fork();
 13         if(pid<0){
 14                 perror("fork");
 15                 return 1;
 16                 }
 17         else if(pid>0){//parent
 18                 close(fds[0]);//close read
 19                 write(fds[1],"hello xiaodu",12);
 20                 }
 21         else{//child
 22                 close(fds[1]);
 23                 char buf[1024]={0};
 24                 read(fds[0],buf,13);
 25                 printf("%s\n",buf);
 26                 }
 27         return 0;
 28 }

这里写图片描述
管道读取数据的四种的情况
(1).读端不读数据且fd[0]没有关闭(读端的引用计数>0),写端一直写。
这里写图片描述

 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 int main()
  6 {
  7          int fds[2];
  8           if(pipe(fds)==-1){
  9                  perror("pipe");
 10                 exit(1);}
 11         pid_t pid=fork();
 12          if(pid<0){
 13                  perror("fork");
 14               return 1;
 15                  }
 16          else if(pid==0){//child
 17                  close(fds[0]);//close read
 18                 int count=0;
 19                 char*msg="hello xiaodu\n";
 20                 while(1){
 21                  write(fds[1],msg,strlen(msg));
 22                 printf("%d\n",++count);
 23                  }
 24 
 25         }
 26          else{//parent
 27                  close(fds[1]);
 28                  char buf[1024]={0};
 29                 int count=0;
 30                 while(1){
 31                 }       
 32         }       
 33 return 0;
 34 }

(2).写端不写数据且fd[1]没有关闭(写端的引用计数>0),读端一直读数据。
这里写图片描述

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 int main()
  6 {
  7          int fds[2];
  8           if(pipe(fds)==-1){
  9                  perror("pipe");
 10                 exit(1);}
 11         pid_t pid=fork();
 12          if(pid<0){
 13                  perror("fork");
 14               return 1;
 15                  }
 16          else if(pid==0){//child
 17                  close(fds[0]);//close read
 18                 int count=5;
 19                 char*msg="hello xiaodu\n";
 20                 while(count--){             
 21                 sleep(10);
 22                 write(fds[1],msg,strlen(msg));
 23                  }
 24 
 25         }
 26          else{//parent
 27                  close(fds[1]);
 28                  char buf[1024]={0};
 29                 int count=0;
 30                 while(1){
 31                         ssize_t s=read(fds[0],buf,sizeof(buf)-1);
 32                         if(s>0){
 33                                 buf[s]=0;
 34                                 printf("f get msg:%s\n",buf);
 35                         }
 36                         else if(s==0){
 37                         printf("child quit\n");
 38                         }
 39                         else{
 40                                 perror("read");
 41                                 break;
 42                         }
 43                 }
 44         }
 45 return 0;
 46 }

(3).写端写了一部分数据后不再写数据且关闭fd[1](写端的引用计数=0),读端一直读且fd[0]没有关闭。
这里写图片描述

#include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 int main()
  6 {
  7          int fds[2];
  8           if(pipe(fds)==-1){
  9                  perror("pipe");
 10                 exit(1);}
 11         pid_t pid=fork();
 12          if(pid<0){
 13                  perror("fork");
 14               return 1;
 15                  }
 16          else if(pid==0){//child
 17                  close(fds[0]);//close read
 18                 int count=5;
 19                 char*msg="hello xiaodu\n";
 20                 while(count--){
 21 //                      sleep(10);
 22                  write(fds[1],msg,strlen(msg));
 23                 sleep(10);
 24                 close(fds[1]);
 25                  }
 26 
 27         }
 28          else{//parent
 29                  close(fds[1]);
 30                  char buf[1024]={0};
 31                 int count=0;
 32                 while(1){
 33                         ssize_t s=read(fds[0],buf,sizeof(buf)-1);
 34                         if(s>0){
 35                                 buf[s]=0;
 36                                 printf("f get msg:%s\n",buf);
 37                         }
 38                         else if(s==0){
 39                         printf("child quit\n");
 40                         }
 41                         else{
 42                                 perror("read");
 43                                 break;
 44                         }
 45                 }
 46         }
 47 return 0;
 48 }

(4).读端读了一部分数据后不再读数据且关闭了fd[0](读端的引用计数=0),写端一直写且fd[1]没有关闭。
这里写图片描述

..下边代码是父进程负责读,子进程负责写:
#include<stdio.h>
  2 #include <unistd.h>
  3 #include<stdlib.h>
  4 #include<string.h>
  5 int main()
  6 {
  7     int pipefd[2]={0};
  8     int ret=pipe(pipefd);
  9      if(ret==-1)
 10      {
 11         perror("pipe");
 12      }
 13      pid_t id=fork();
 14      if(id==0)
 15      {
 16           int i=0;
 17         const char*msg="hello xiaodu\n";
 18         close(pipefd[0]);
 19           while(i<10)
 20           {
 21 
 22           write(pipefd[1],msg,strlen(msg));
 23               sleep(1);
 24                i++;
 25         }
 26      }
 27      else
 28      {
 29           char buf[1024];
 30           close(pipefd[1]);
 31           int j=0;
 32      while(j<3)
 33      {
 34 
 35        ssize_t s=read(pipefd[0],buf,sizeof(buf)-1);
 36 
 37         if(s>0)
 38         {
 39              buf[s]=0;
 40         printf("father: %s\n",buf);
 41         j++;
 42         }
 43 
 44         //  sleep(10);
 45        // close(pipefd[0]);
  46         }
 47           sleep(5);
 48         close(pipefd[0]);
 49         int status=0;
 50         wait(&status);
 51         printf("child process quit not normal !\n,sig is:%d\n",status&0X7F);
 52                   }
 53 return 0;
 54 }                                                                                                                                                                           

这里写图片描述
发现进程是异常退出,退出信号为13号,即为SIGPIPE信号。

匿名管道特点
(1).只能用于具有共同祖先(具有亲缘关系)的进程之间进行通信(如父子进程,父进程创建管道,fork出子进程)。
(2).进程退出,管道释放,所以管道的生命周期随进程。
(3).内核对管道操作具有同步与互斥机制。
(4).管道在通信时是基于字节流的。
(5).管道是半手工的,数据只能从一个方向流动,需要双向通信时,则需建立两个管道。

命名管道
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,被称为命名管道。
适用于无任何关系的通信。
命名管道是一个特殊类型的文件。

创建命名管道:mkfifo filename

函数为:int mkfifo(const char*filename,mode_t mode);

举例:创建命名管道:

int main(int argc,char*argv[])
{
    mkfifo("p1",0644);
    return 0;
}

例3:用命名管道实现server&client 通信
client.c文件:用户发送信息

 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<sys/types.h>
  5 #include<sys/stat.h>
  6 #include<fcntl.h>
  7 #include<string.h>
  8 
  9 #define ERR_EXIT(m)\
 10 do{\
 11         perror(m);\
 12         exit(EXIT_FAILURE);\
 13 }while(0)
 14 
 15 int main()
 16 {
 17         int wfd=open("mypipe",O_WRONLY);
 18         if(wfd<0){
 19                 ERR_EXIT("open");
 20         }
 21         char buf[1024]={0};
 22         while(1){
 23                 buf[0]=0;
 24                 printf("please Enter# ");
 25                 fflush(stdout);
 26                 ssize_t s=read(0,buf,sizeof(buf)-1);
 27                 if(s>0){
 28                         buf[s]=0;
 29                         write(wfd,buf,strlen(buf));
 30                 }else if(s<=0){
 31                         ERR_EXIT("read");
 32                 }
 33         }
 34         close(wfd);
 35         return 0;
 36 }
~                                           
 server.c:服务器接收数据                                                                                                                                                      
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<sys/types.h>
  5 #include<sys/stat.h>
  6 #include<fcntl.h>
  7 #define ERR_EXIT(m)\
  8 do{\
  9         perror(m);\
 10         exit(EXIT_FAILURE);\
 11 }while(0)
 12 
 13 int main()
 14 {
 15         umask(0);//创建一个文件时将文件权限减了一个umask值
 16         int ret=mkfifo("mypipe",0644);
 17         if(ret<0){
 18                 ERR_EXIT("mkfifo");
 19                 }
 20         int rfd=open("mypipe",O_RDONLY);
 21         if(rfd<0){
 22                 ERR_EXIT("open");
 23         }
 24         char buf[1024]={0};
 25         while(1){
 26                 buf[0]=0;
 27                 printf("please wait...\n");
 28                 ssize_t s=read(rfd,buf,sizeof(buf)-1);
 29                 if(s>0){
 30                         buf[s-1]=0;
 31                         printf("client says#  %s\n",buf);
 32                 }else if(s==0){
 33                         printf("client quit,exit now\n");
 34                         exit(EXIT_SUCCESS);
 35                 }else{
 36                         ERR_EXIT("read");
 37                 }
 38         }
 39         close(rfd);
 40         return 0;
 41 }

这里写图片描述
在另一个终端下打开./client
这里写图片描述
这样就可以通过命名管道将两个毫无关系的进程实现进程间通信。

Linux下管道的容量及管道的数据结构
<1>.查看管道的容量
查看Linux下管道的默认大小可以使用命令ulimit -a
这里写图片描述
可以看到pipe size一次原子性写入为8*512=4096bytes,8是内核设定的管道缓冲区的大小。
我们通过ulimit -a命令查看到的pipo size定义的是内核管道缓冲区的大小,这个值的大小是由内核设定的;而pipe capacity指的是管道的最大值,即容量,是内核内存中的一个缓冲区。
pipe缓冲区是64kB。
尽管命令ulimit -a看到管道大小8块,缓冲区的大小不是4 k,因为内核动态分配最大16“缓冲条目”,即64 k。
即可算出管道容量为:16*4096bytes=65536bytes.
即为:64*1024bytes=65536bytes.
测试管道容量大小只需要将写端一直写,读端不读且不关闭fd[0],即可。

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 #include<errno.h>
  6 #include<fcntl.h>
  7 int main()
  8 {
  9         int fd[2];
 10         int ret=pipe(fd);
 11         if(ret<0){
 12                 perror("pipe");
 13                 exit(1);}
 14         pid_t pid=fork();
 15         if(pid<0){
 16                 perror("fork");
 17                 exit(1);}
 18         else if(pid>0){
 19                 close(fd[0]);
 20                 long long  count=0;
 21                 char*msg="a";
 22         while(1){
 23                 write(fd[1],msg,strlen(msg));
 24                 ++count
 25         printf("count=%lld\n",count);
 26                 }
 27         }
 **28        *else{//child
 29               close(fd[1]);
 30                 char _msg[10];
 31         while(1){
 32                 memset(_msg,'\0',sizeof(_msg));
 33                 }
 34         }***
 35         return 0;
 36                   
 37 }       
~          

这里写图片描述
即可观察到管道的最大容量为65536。

<2>.查看管道的数据结构
在Linux 中,管道的实现并没有使用专门的数据结构,而是借助了文件系统的file 结构和VFS 的索引节点inode。通过将两个 file 结构指向同一个临时的 VFS 索引节点,而这个 VFS 索引节点又指向一个物理页面而实现的。如下图所示。
这里写图片描述
两个 file 数据结构,但它们定义文件操作例程地址是不同的,其中一个是向管道中写入数据的例程地址,而另一个是从管道中读出数据的例程地址。这样,用户程序的系统调用仍然是通常的文件操作,而内核却利用这种抽象机制实现了管道这一特殊操作。一个普通的管道仅可供具有共同祖先的两个进程之间共享,并且这个祖先必须已经建立了供它们使用的管道。

猜你喜欢

转载自blog.csdn.net/xiaodu655/article/details/79871705