进程间的通信(管道通信)
文章目录
- 零、前言
- 一、进程通信的概念
- 二、进程通信的条件
- 三、匿名管道通信
- 1.原理
- 2.匿名管道的实现
- 3.匿名管道的特点
- 4.四种情况
- (1)读端不读或者读的慢,写端要等待读端。
- (2)读端关闭,写端收到sigpipe信号,直接终止
- (3)写端不写或写得慢,读端需要等写端
- (4)写端关闭,读端读完全部数据,读到0说明读到结尾
- 四、命名管道通信
- 1.原理
- 2.命名管道的实现
- (1)通过命令行实现
- (2)通过代码实现
- 3.利用命名管道进行通信
- (1)head.h
- (2)server.c
- (3)client.c
- 4.进程间通信的目的
- 5.命名管道通信的特点
- 五、总结
零、前言
本文将介绍进程通信的概念,以及进程之间通过匿名管道进行通信的原理。匿名管道通信一共有五个特点和四种情况,一一在文章中进行了验证。
一、进程通信的概念
进程通信本质上就是不同的进程之间的信息交换,从而使不同的进程进行协同工作。
cat test.c | wc -l
这段代码就是一个简单的进程通信,即将进程cat的数据传给进程wc,从而计算出代码的函数:
二、进程通信的条件
因为进程是具有独立性的,因此两个进程如果直接进行数据交换的代价会很高,所以需要操作系统来设计通信的方式。
由于进程具有独立性,两个进程没法看到对方的数据,因此,操作系统会开辟一块内存来作为两个进程通信的媒介。这块内存可能是一个文件,也可能是一个队列,或者就是最原始的内存块,这也就是通信方式有很多种的原因。
三、匿名管道通信
1.原理
匿名管道的通信常用于有亲属关系的进程,最常用在父子进程中。
以上的图就解释了匿名管道通信的原理,由于子进程是以父进程为模板的,因此files结构体也会被继承下来,父子进程的文件描述符3都指的是同一个文件。当父进程上层要向磁盘中写数据的时候,数据会首先被存放在内核缓冲区,再调用磁盘驱动的方法来刷新到磁盘。当OS命令内核缓冲区不向磁盘中刷新数据时,父进程上层的数据(hello world)就会存放在内核缓冲区中,此时子进程访问内核缓冲区进行读数据。就实现了数据在父子进程之间的交换。
这种方式基于文件的内核缓冲区的进程通信方式我们就称之为管道通信,内核缓冲区是由操作系统所提供的。
2.匿名管道的实现
为实现上述过程,我们通常要使用两个文件操作符分别以读形式和写形式来两次打开文件:
首先父进程以读写两种方式打开一个文件,此时它的文件描述符3表示以读打开,文件描述符4表示以写打开。生成子进程之后,子进程拷贝父进程的files结构体,因此文件读写与文件操作符的对应与父进程一致。然后父进程关闭写文件的文件描述符,子进程关闭读文件的文件描述符,这样就可以实现子进程向管道中写数据,父进程向管道中读数据了。
C语言为我们提供了pipe这一系统调用接口来实现管道。
其中它的参数pipefd数组中存放的是以读或者写打开文件的文件操作符,0下标存储的是以读方式打开文件的文件操作符,1存储的是以写方式打开文件的文件操作符。当创建管道成功返回0,失败返回-1。
下面验证一下0和1下标存储的文件操作符:
#include<stdio.h>
#include<unistd.h>
int main()
{ int pipefd[2]={0}; if(pipe(pipefd)!=0) { perror("pipe error\n"); return 1; } printf("pipefd[0]:%d\n",pipefd[0]); printf("pipefd[1]:%d\n",pipefd[1]);
}
打印的结果是:
此时我们发现函数pipe改变它的参数pipefd数组的值。且0下标对应的文件描述符是3,1下标对应的文件描述符是4。而0下标是以读打开,1下标是以写打开,因此文件描述符3表示的是以读方式打开文件,文件描述符4对应的是以写方式打开文件。
了解了这些,我们就可以建立父子进程使他们来进行通信了:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
int main()
{int pipefd[2]={0};if(pipe(pipefd)!=0){perror("pipe error\n");return 1;}printf("pipefd[0]:%d\n",pipefd[0]);printf("pipefd[1]:%d\n",pipefd[1]);if(fork()==0)//子进程{close(pipefd[0]);//关闭读文件const char* msg="hello pipe";while(1){write(pipefd[1],msg,strlen(msg));sleep(1);}exit(0);}close(pipefd[1]);//父进程关闭写文件while(1){char buffer[64]={0};ssize_t s=read(pipefd[0],buffer,sizeof(buffer));if(s==0){break;}else if(s>0){buffer[s]=0;printf("child say:# %s\n",buffer);}else {printf("read error!\n");break;}}
}
使用父进程的buffer来接收子进程的数据,我们令子进程每沉睡一秒写一次数据,此时打印的结果为:
可以看出完成了进程的通信。
3.匿名管道的特点
1.管道是一个单向传输的通道。
2.管道是面向字节流的,(只要管道里有数据,就可以一直进行读取)
3.匿名管道常用于父子进程通信。
4.管道自带同步性机制,以原子性写入(有读阻塞和写阻塞)
5.管道是文件,属于进程,当进程退出时被打开的管道会被关闭。
下面将使用四种情况,来验证管道的这些特点。
4.四种情况
(1)读端不读或者读的慢,写端要等待读端。
我们将上述代码修改如下:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
int main()
{ int pipefd[2]={0}; if(pipe(pipefd)!=0) { perror("pipe error\n"); return 1; } printf("pipefd[0]:%d\n",pipefd[0]);printf("pipefd[1]:%d\n",pipefd[1]);if(fork()==0)//子进程 { close(pipefd[0]);//关闭读文件
// const char* msg="hello pipe"; int count=0; while(1) { write(pipefd[1],"a",1); count++; printf("%d\n",count);
// sleep(1); } exit(0); } close(pipefd[1]);//父进程关闭写文件 while(1){// char buffer[64]={0};
// ssize_t s=read(pipefd[0],buffer,sizeof(buffer));
//
// if(s==0)
// {// break;
// }
// else if(s>0)
// {// buffer[s]=0;
// printf("child say:# %s\n",buffer);
// }
// else
// {// printf("read error!\n");
// break;
// }}
}
此时我们使用count来记录子进程写入的次数,而父进程不进行读取,此时我们发现子进程写入65536次后,不再进行写入:
这是因为内核缓冲区也是有大小的,65536个比特位正好是64KB的大小。此时不会再进行写入了(不会覆盖数据),因为在等待读端来读数据。
当我们让读端缓慢读取代码的时候呢?我们将父进程部分的代码更改如下:
while(1) { sleep(10); char c=0; read(pipefd[0],&c,1); printf("father take:%c\n",c);}
当我们让父进程每隔十秒读一个字节的时候,我们发现并没有什么反应。不妨将每次读的数值放大一些:
sleep(10); char c[1024*4+1]={0}; ssize_t s=read(pipefd[0],c,1024*4); c[s]=0; printf("father take:%s\n",c);
当每次读取4kb个数据的时候,我们发现写入的数据进行更新了:
如果我们每次读取2KB的数据呢?此时每隔两个读取周期,写入的数据会发生更新。
这就说明,匿名管道通信的特点4,即管道通信是自带同步机制,并以原子的方式来进行写入的。只有读取了4KB的数据之后,管道才会被重新写入数据。
(2)读端关闭,写端收到sigpipe信号,直接终止
while(1) { sleep(5); char c[64]={0}; ssize_t s=read(pipefd[0],c,63);
// c[s]=0;
// printf("father take:%s\n",c);
// char buffer[64]={0};
// ssize_t s=read(pipefd[0],buffer,sizeof(buffer));
// if(s==0) { break; } else if(s>0) { c[s]=0; printf("child say:# %s\n",c); } else { printf("read error!\n"); break; } break; } close(pipefd[0]); return 0;
}
当我们使得父进程读一次数据之后,将父进程的读操作关闭的时候,观察子进程:
需要使用命令行脚本来观察这两个进程运行的情况:
while :;do ps axj|grep mytest|grep -v grep;sleep 1;echo “###############################################”;done
此时我们发现当父进程不再进行读操作之后,子进程也会发生退出:
当我们将读端进行关闭时,写端还在继续写入,在操作系统的角度分析,这是不合理的。本质上就是在浪费操作系统的资源,OS会直接终止子进程,通过给子进程发送sigpipe的信号的方式来终止它。
我们可以查看一下该信号:
通过kill -l查看所有信号,我们发现该信号在第十三个位置上。同时在子进程退出之后,我们可以在父进程处接收一下子进程的退出码和退出信号:
int status=0; waitpid(-1,&status,0); printf("exit code:%d\n",(status>>8&0xFF)); printf("exit signal:%d\n",status&0x7F);
此时就可以得到子进程的退出码为0,退出信号为13:
(3)写端不写或写得慢,读端需要等写端
验证方式就是将写端的频率调低,使子进程进行sleep操作,而读端一直在读:
此时读端会等待写端写入数据之后再进行读数据,如果写端不写,则读端会一直等待。
这与写端在读端读取4kb的数据是一样的,都验证了匿名管道读写的原子性。
(4)写端关闭,读端读完全部数据,读到0说明读到结尾
if(fork()==0)//子进程 { close(pipefd[0]);//关闭读文件
// const char* msg="hello pipe"; int count=0; while(1) { write(pipefd[1],"a",1); count++; printf("%d\n",count); sleep(1); break; } close(pipefd[1]); exit(0); }
此时将子进程退出,父进程读到了0后可以控制进行退出操作:
四、命名管道通信
1.原理
进程之间进行通信的条件是两个通信的进程同时看到同一块空间,并通过该空间来进行通信,在管道通信中这块空间是一个文件。在进行匿名通信时,由于是父子进程之间的通信,所以可以通过相同的文件描述符找到这个文件,而是在命名管道中必须将该文件进行命名之后,两个进程(可以非亲非故)才能找到该文件。
注意,管道通信的特点在于,数据并没有被写到磁盘上,而是在文件中进行临时保存。与通过普通文件的进程通信是不同的。
命名管道的通信就是将A和B分别以读和写的方式同时打开同一个文件,并且该文件中的数据不被刷新到磁盘上。
这个文件就是命名管道文件。
2.命名管道的实现
(1)通过命令行实现
mkfifo pipe
使用mkfifo来建立命名管道,命名为pipe。
当我们向管道输入数据的时候,发现管道的大小是0,这说明数据并没有刷新到磁盘上。
同时我们也可以看见它的文件类型,是以p开头的文件。
(2)通过代码实现
创建命名管道的函数名也为mkfifo,我们可以使用man手册来进行查询:
它的第一个参数pathname表示的是 路径/命名管道,mode表示的是权限,注意这里的权限是需要与umask来进行计算的
当创建成功返回0,创建失败返回-1。我们可以通过一段简单的代码来创创建一个管道。
执行这段代码之后可以发现已经创建成功了管道文件。
同时我们发现,我们规定的权限是666,但是实际的权限是664,这是因为权限最终要与umask进行计算,如果要修改需要先修改系统的umask。
3.利用命名管道进行通信
有了管道就可以进行通信了,我们再建立一个client来进行通信,只需要进行正常的文件操作即可,将管道文件看成一个文件。
建议使用系统调用接口,因为没有用户缓冲区的干扰。
(1)head.h
#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>
#define MY_FIFO "./fifo"
(2)server.c
#include "head.h"
int main()
{ if(mkfifo(MY_FIFO,0666)<0) { perror("mkfifo"); return 1; }
int fd=open(MY_FIFO,O_RDONLY); if(fd<0) { perror("open"); return 2; } while(1) { char buffer[64]={0}; ssize_t s=read(fd,buffer,sizeof(buffer)-1); if(s>0) { buffer[s]=0; printf("client say# %s\n",buffer); } else if(s==0) { printf("client quit!\n"); break; } else { perror("read"); break; } close(fd);return 0;
}
(3)client.c
#include"head.h"
#include<string.h>
int main()
{ int fd=open(MY_FIFO,O_WRONLY); if(fd<0) { perror("open"); return 1; } while(1) { printf("请输入:"); fflush(stdout); char buffer[64]={0}; ssize_t s=read(0,buffer,sizeof(buffer)-1);//从命令行读入数据 if(s>0) { buffer[s-1]=0;//使用0将读入的回车覆盖 printf("%s\n",buffer); write(fd,buffer,strlen(buffer)); } } close(fd);
}
可以看到,当建立完管道文件之后,读写方式与文件的读写是一致的。
此时我们就可以完成进程之间的通信了:
4.进程间通信的目的
其实进程间通信的目的不仅仅只有传输数据,我们还可以通过进程间的通信来控制进程。
我们可以通过client端输入的数据来令客户端执行一些操作:
对客户端代码进行一下修改:
if(strcmp(buffer,"show")==0) { if(fork()==0) { execl("/usr/bin/ls","ls","-l",NULL); exit(-1); } waitpid(-1,NULL,0); } else if(strcmp(buffer,"run")==0) { if(fork()==0) { execl("/usr/bin/sl","sl",NULL); exit(-2); } waitpid(-1,NULL,0); }
此时我们通过client来控制server来执行相应的程序了:
5.命名管道通信的特点
当server端接收数据慢的时候,数据会暂存到管道中,但是管道大小为0。数据没有刷新到磁盘上:
我们可以再复制一个会话进行查看管道的大小:
在server中sleep(50)再接收数据:
五、总结
进程之间的通信必要的前提是两个进程需要看到同一块资源,并且这块资源不属于任何一个进程而是由操作系统来提供的。
当使用管道来进行通信的时候,这块资源表现为一个文件,可以是匿名文件(匿名管道通信),也可以是命名文件(命名管道通信),该文件的特点是在其中的数据不会被刷新到磁盘上。
进程间的通信(管道通信)相关推荐
- linux进程间通讯-有名管道
文章目录 阻塞和非阻塞概念 通过fcntl函数设置文件的阻塞特性 文件描述符概述 文件描述符的复制 有名管道 有名管道的创建 有名管道的基本读写操作 有名管道实现进程间通信 有名管道的读写规律(阻塞) ...
- 【Linux】进程间通讯之管道
进程间通信的机制包括:管道.信号量.共享内存.消息队列. 这篇博客主要介绍的是进程间通讯之管道的应用 一.管道的分类 管道都属于半双工通讯机制 管道分为有名管道和无名管道 1.有名管道 在磁盘上有一个 ...
- python 多进程共享变量manager_python 进程间共享数据 multiprocessing 通信问题 — Manager...
Python中进程间共享数据,处理基本的queue,pipe和value+array外,还提供了更高层次的封装.使用multiprocessing.Manager可以简单地使用这些高级接口. Mana ...
- 进程通信 - 管道通信
在创建子进程的时候,会将父进程中的资源复制一份给子进程,然后他们各自使用自己的资源,那如果父进程想与子进程通信,如何达到呢,如果说采用套接字的方法,那未免太慢了,是否可以创建一个共同使用的内存,双 ...
- C#进程通信 - 管道通信
语言类型:C# 使用场景:需要使不同进程间可以进行通信 关键代码: #region Pipe/// <summary> G/S:管道接收端 </summary>private ...
- linux进程间通讯-无名管道
文章目录 无名管道 无名管道的创建 -- pipe函数 无名管道的读写规律 无名管道 无名管道概述 管道(pipe)又称无名管道. 无名管道是一种特殊类型的文件,在应用层体现为两个打开的文件描述符.任 ...
- linux命名管道进程间通信,Linux进程间通讯--命名管道
IPC安全 前面总结了匿名管道,如今来看命名管道:因为匿名管道的一个限制就是:只能是有血缘关系的进程间才能够通讯,好比:有两个同祖先的子进程,父子进程等:为了突破这一个限制,想让没有任何关系的两个进程 ...
- 进程间通讯 ----- 命名管道
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础.每个进程都有一个主线程,线程则是cpu调度的基本单位,每个进程都有自己的 ...
- pipe 半双工_linux进程间通讯之管道(无名管道pipe)实现全双工双向通讯
管道是什么: 1. 管道只能用于具备亲缘关系的进程之间通讯. 2.管道是一种单工或者说半双工的通讯方式,传递信息的方向是固定的,只能由一端传递到另外一端. 头文件及函数原型: #include int ...
- 命名管道(FIFO) Linux进程进程间的通信之命名管道(FIFO)
Linux进程进程间的通信之命名管道(FIFO) 命名管道(FIFO),它和一般的管道一样.都是作为中间的邮递员来实现两个进程间的通信交流. 命名管道(FIFO)有几个特点: 1.命名管道(FIFO) ...
最新文章
- 洛谷——P2341 [HAOI2006]受欢迎的牛//POJ2186:Popular Cows
- 端到端加密(E2EE)技术分析:在移动应用中实现安全通信的利器
- 图像滤镜艺术---(Nostalgla Filter)老照片滤镜
- python中的doc_基于Python获取docx/doc文件内容代码解析
- VMWare虚拟机打不开、繁忙无法关闭、不可恢复错误(mks)(不要在虚拟机下用win+L锁屏,不然就繁忙。。。)
- apache+php windows下配置
- mysql update commit吗_MySQL需要commit么
- STM32工作笔记0071---内存管理实验
- 使用BroadcastReceiver的Android IntentService
- 一名合格的Web前端工程师需要具备的8项技能!
- 【21.09-21.10】近日Paper Quichthrough汇总
- 代码打印颜色(只要您能想到的,都行)——我的颜色控制打印工具mypycolor已经更聪明:参数可以任意接收颜色控制码、颜色描述英文单词的任意组合。
- 淘宝接口 http://ip.taobao.com/service/getIpInfo.php?ip=myip 获取不到手机ip地址
- dicom是指_DCM是什么文件
- 说不清心里对于这个孩子是什么样的感情
- sql镶嵌查询_SQL 嵌套查询
- 《简明美国史》笔记(陈勤著)
- 《Nature-Inspired Metaheuristic Algorithms》—— Random Walk
- 今日头条财经部门后台研发实习生面试
- [转]JavaScript/Node.JS 中的 Promises