进程间通信之--匿名管道
来源:互联网 发布:云计算产业 编辑:程序博客网 时间:2024/06/05 18:14
一、为什么进程间通信需要管道
两个进程之间的通信,每个进程各有不同的地址空间,每个地址空间的数据信息是独立的,任何一个进程的全局变量在另一个进程中都看不到。例如:父进程中有一个变量 a = 0;在子进程中改变 a 的值是不会影响在父进程中的 a 的值,因为虽然子进程所有的数据信息都是拷贝(写时拷贝)自父进程,两个进程有各自不同的地址空间。
eg:代码验证:
//mypipe.c 1 #include<sys/types.h> 2 #include<sys/wait.h> 3 #include<stdlib.h> 4 #include<unistd.h> 5 #include<stdio.h> 6 int a=0; 7 8 int main() 9 { 10 pid_t pid=fork(); 11 if(pid < 0) 12 { 13 perror("fork"); 14 exit(1); 15 } 16 else if(pid == 0) 17 { 18 //child 19 a = 110; 20 printf("child is a= %d\n", a); 21 } 22 else 23 { 24 //father 25 sleep(1); 26 printf(" father is a= %d\n", a); 27 waitpid(pid , NULL , 0); 28 } 29 return 0; 30 }
//Makefile 文件 1 mypipe:mypipe.c 2 gcc -o $@ $^ 3 .PHONY:clean 4 clean: 5 rm -f mypipe
代码运行结果:
由上面代码和运行截图可清晰的看出,父子进程之间没有共享数据,所以两个进程是不能直接通信的。要通信就必须要通过内核,在内核中开辟一块缓冲区(第三方资源),让不同的进程看到一份公共的资源。具体实现是进程 1 把数据从用户空间拷贝到内核缓存区,进程2 再从内核缓冲区把数据读走,这就实现了进程之间的通信。
二、进程通信–管道
管道的创建:pipe
返回值:
调用成功返回 0,调用失败返回 -1;
作用:
调用pipe函数时在内核中开辟一块缓冲区(又称管道)用于通信,有一个读端和一个写端,pipe[1] 指向的管道的写端,向管道中写的话就直接调用 write(pipefd[1] ,msg ,strlen(msg)); 从管道中读直接调用read (pipefd[0] ,buf ,sizeof(buf)-1);所以管道看起来就像一个打开的文件,两个进程分别向管道(资源共享区(临界资源)(访问该资源的代码叫临界区))读写,就完成了进程之间的通信。
例一、
1 #include<unistd.h> 2 #include<stdio.h> 3 #include<stdlib.h> 4 5 int main() 6 { 7 int fd[2]; 8 int pi = pipe(fd); 9 if(pi < 0) 10 { 11 perror("pipe"); 12 exit(1); 13 } 14 printf("fd[0]=%d ,fd[1]=%d\n",fd[0],fd[1]); 15 return 0; 16 }
从上面这个例子中我们可以看到:
读端:fd[0] = 3; 写端:fd[1] = 4;
例二、
因为子进程数据资源都继承自父进程,所以子进程的文件描述符表和他父进程的文件描述表一样,以下是两个进程同时指向一个管道的例图:
代码实现如下:
1 #include<sys/types.h> 2 #include<sys/wait.h> 3 #include<stdlib.h> 4 #include<unistd.h> 5 #include<stdio.h> 6 #include<string.h> 7 8 int main() 9 { 10 int fd[2]; 11 pid_t pi = pipe(fd); 12 if(pi < 0) 13 { 14 perror("pipe"); 15 exit(1); 16 } 17 pid_t pid=fork(); 18 if(pid < 0) 19 { 20 perror("fork"); 21 exit(2); 22 } 23 else if(pid == 0) 24 { 25 //child 26 char *msg="I am child"; 27 close(fd[0]); 28 while(1) 29 { 30 31 sleep(1); 32 write(fd[1] , msg , strlen(msg)); 33 printf("child write# %s\n",msg); 34 } 35 } 36 else 37 { 38 //father 39 char buf[1024]; 40 close(fd[1]); 41 while(1) 42 { 43 int ret=read(fd[0] , buf ,sizeof(buf)-1); 44 if(ret > 0) 45 { 46 buf[ret] = 0; 47 printf("father read# %s\n", buf); 48 } 49 } 50 waitpid(pid , NULL , 0); 51 } 52 return 0; 53 }
在上面的显示结果中“I am child”这句话是由子进程写在管道中的,父进程从管道中读出来数据,这就完成了父子进程之间的单向通信。
先创建管道再创建子进程
从上面代码中,看到子进程在写之前关闭了读的文件描述符fd[0],父进程在读之前关闭了写文件描述符fd[1];这是为了预防发生错误,这就要涉及到管道的使用限制了,两个进程通过一个管道只能实现单向通信,父进程读,子进程写。要完成父进程写子进程读的话就需要重新开辟一个管道了。若在父进程读的情况下,没有关闭父进程的写fd[1],此时在父进程中去写,就会导致某些未知的错误(在父进程中不去写就不会出错,关闭是为了防止错误的出现);关闭fd[1],在父进程想写进管道时就不会成功。管道是一种半双工方式,即对于进程来说,要么只能读管道,要么只能写管道。不允许对管道又读又写。
以上的管道为匿名管道,它的特点是:
1. 单向通信;2. 通信依赖于文件系统,其生命周期是:进程退出,管道也退出(称为:随进程);3. 数据读写时是按照数据流来读写的,(流式服务)4. 匿名管道只适应于有血缘关系的进程(eg:父子进程,兄弟进程)通信。5. 自带同步机制;(若写的慢,则读的一方的速度也会慢(随着写的速度来读))
使用管道需要注意以下4种情况:(读情况都是父进程,写情况都是子进程)
1、子进程写的慢,父进程读的快;父进程从管道中读数据,管道中数据全部被读完后,父进程再次去read会阻塞,等待子进程写,直到管道中有数据可读了才读取数据并返回,例子就是上面实现的代码。
2、 子进程写的快,父进程读得慢;导致管道中数据全被写满,再次write会被阻塞,直到管道中有空位置了才写入数据并返回。
第二种情况的代码部分
1 #include<sys/types.h> 2 #include<sys/wait.h> 3 #include<stdlib.h> 4 #include<unistd.h> 5 #include<stdio.h> 6 #include<string.h> 7 8 int main() 9 { 10 int fd[2]; 11 pid_t pi = pipe(fd); 12 if(pi < 0) 13 { 14 perror("pipe"); 15 exit(1); 16 } 17 pid_t pid=fork(); 18 if(pid < 0) 19 { 20 perror("fork"); 21 exit(2); 22 } 23 else if(pid == 0) 24 { 25 //child 26 char *msg="I am child"; 27 close(fd[0]); 28 int count = 0; 29 while(1) 30 { 31 32 write(fd[1] , msg , strlen(msg)); 33 printf("count = %d\n",++count); 34 //printf("child write# %s\n",msg); 35 } 36 } 37 else 38 { 39 //father 40 char buf[1024]; 41 close(fd[1]); 42 while(1) 43 { 44 sleep(10); 45 int ret=read(fd[0] , buf ,sizeof(buf)-1); 46 if(ret > 0) 47 { 48 buf[ret] = 0; 49 printf("father read# %s\n", buf); 50 } 51 } 52 waitpid(pid , NULL , 0); 53 } 54 return 0; 55 }
写到管道的最大容量,等待读取之后在进行写入
等再次write,管道中有空余,则继续写入,知道写满,下次写入阻塞,直到管道中有空位置了才写入数据并返回。
3、父进程中读fd[0] 关闭,子进程写;子进程向管道中一写,操作系统会终止该进程,进程异常终止。
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<string.h> int main() { int fd[2] = {0,0}; if(pipe(fd) < 0){ perror("pipe error\n"); return 1; } pid_t id = fork(); if(id == 0){ //child --->write close(fd[0]); int count = 0; const char *msg = "I am child,hello father\n"; while(1){ write(fd[1],msg,strlen(msg)); // printf("child\n"); sleep(1); //printf("count:%d\n",++count); } }else{ //father--->read char buf[1024]; close(fd[1]); int count = 0; while(1){ ssize_t s = read(fd[0],buf,sizeof(buf) - 1); // printf("father\n"); printf("count:%d\n",count); if(s > 0){ buf[s] = 0; printf("%s",buf); } if(count++ > 5) { close(fd[0]); break; } } int status = 0; pid_t ret = waitpid(id,&status,0); printf("ret:%d,sig:%d,exitCode:%d\n",ret,status&0xff,(status>>8)&0xff); } return 0; }
4、进程关闭写,父进程读;子进程没有向管道中写数据,父进程从管道中读数据,管道中所以数据都被读取后,在次read会阻塞,直到管道中有数据可读了才读取数据并返回。
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<string.h> int main() { int fd[2] = {0,0}; if(pipe(fd) < 0){ perror("pipe error\n"); return 1; } pid_t id = fork(); if(id == 0){ //child --->write close(fd[0]); int count = 0; const char *msg = "I am child,hello father\n"; while(1){ write(fd[1],msg,strlen(msg)); if(count++ > 5) { close(fd[1]); break; } sleep(1); printf("count:%d\n",count); } }else{ //father--->read char buf[1024]; close(fd[1]); int count = 0; while(1){ ssize_t s = read(fd[0],buf,sizeof(buf) - 1); if(s > 0){ buf[s] = 0; printf("%s",buf); } } int status = 0; pid_t ret = waitpid(id,&status,0); printf("ret:%d,sig:%d,exitCode:%d\n",ret,status&0xff,(status>>8)&0xff); } return 0; }
由上面代码可以证实,子进程关闭写,父进程读完管道的数据,会一直阻塞等待直到有数据写入,才读取数据并返回;
进程间通信并没有结束,这只是个开始;
- 进程间通信之匿名管道通信
- linux进程间通信之匿名管道
- Python进程间通信之匿名管道
- Linux进程间通信之匿名管道
- 进程间通信之匿名管道
- 进程间通信之--匿名管道
- 进程间通信之匿名管道
- 进程间通信之管道通信(匿名管道)
- 进程间通信之匿名管道和命名管道
- Linux--进程间通信之匿名管道及命名管道
- 进程间通信-匿名管道
- 进程间通信 - 匿名管道
- 进程间通信匿名管道
- 进程间通信-匿名管道
- 进程间通信:匿名管道通信
- linux进程间通信方式之匿名管道
- linux进程间通信方式之匿名管道
- 孙鑫 第十七课进程间通信之二 匿名管道
- java删除文件
- c#:未将对象引用设置到对象的实例--可能出现的问题总结(转)
- Ubuntu17.0下安装Hadoop
- MyBatis学习总结(四)——解决字段名与实体类属性名不相同的冲突
- Android开发架构的一些思考
- 进程间通信之--匿名管道
- alphalens教程1--整理好你的数据
- 64位Mex文件在Matlab 2014b和Visual Studio 2013中的调试
- static方法能否被重写
- bootstrap中datetimepicker日期控件的使用
- MyBatis学习总结(五)——实现关联表查询
- 安卓开发入门之系统下载管理工具DownloadManager-第二部分(翻译)
- MDK分散加载脚本
- MyBatis学习总结(六)——调用存储过程