目录
一、进程间通信
1.1 通信目的
1.2 通信发展
二、管道
2.1 管道的概念和分类
2.2 匿名管道
2.2.1 匿名管道(基于父子血缘关系)
2.2.2 匿名管道单向性
2.2.3 匿名管道是内存级别文件
2.2.4 匿名管道指令实现
2.2.5 代码实现匿名管道(pipe()函数)
2.2.6 匿名管道读写规则
2.3 命名管道
2.3.1 命名管道(任何两个进程间通信)
2.3.2 指令实现命名管道
2.3.3 函数实现命名管道
2.3.4 命名管道的打开规则
2.4 匿名管道与命名管道的区别
一、进程间通信
1.1 通信目的
数据传输:一个进程需要将它的数据发送给另一个进程
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变
1.2 通信发展
管道 ->> 匿名管道和命名管道
System V进程间通信(消息队列、共享内存、信号量)
POSIX进程间通信(消息队列、共享内存、信号、互斥量、条件变量、读写锁)
二、管道
2.1 管道的概念和分类
概念:我们把从一个进程连接到另一个进程的一个数据流称为一个“管道!
管道分为匿名和有名管道!管道的存在基于文件系统!
2.2 匿名管道
2.2.1 匿名管道(基于父子血缘关系)
父进程创建子进程,子进程文件描述符表同样指向父进程打开的文件!父子进程看到同一个文件内容,这样我们就实现了父子进程的通信!这个文件我们称为匿名管道!
2.2.2 匿名管道单向性
匿名管道父子进程一个只负责读,一个只负责写!两者读写剩余一个要关闭!
2.2.3 匿名管道是内存级别文件
如果我们父子进程其中一个向文件写,一个读,OS不会采取向磁盘文件写入,然后再从磁盘读取!因为这样严重影响效率!
OS可以不用open磁盘文件也能创造文件!它可以在内存中创造打开一个文件,然后让进程文件描述符数组指向它!这个文件没有名称,我们称之为匿名管道!进程结束,文件销毁!
2.2.4 匿名管道指令实现
Linux中 | 是管道指令,| 左写入,右读取!
2.2.5 代码实现匿名管道(pipe()函数)
#include<iostream>
#include<unistd.h>//pipe fork
#include<stdio.h>//sprintf
//wait
#include<sys/types.h>
#include<sys/wait.h>
#include<string.h>//strlen
using namespace std;
int main()
{
//创建单向通信的管道
int fds[2];
int ret=pipe(fds);
if(ret==0)
{
cout<<"creat pipe success!"<<endl;
}
else{
cout<<"creat pipe error!"<<endl;
exit(-1);
}
//创建子进程
pid_t id=fork();
if(id<0)
{
perror("fork");
exit(-1);
}
else if(id==0)
{
//子进程关闭读
close(fds[0]);
int cnt=0;
const char* msg="我是子进程,我正在向父进程发消息...";
while(1)
{
char buffer[1024];
//向buffer写入
sprintf(buffer,"child->parent[%d]:%s",++cnt,msg);
//将buffer内容写入管道 !写入不带上'\0'
write(fds[1],buffer,strlen(buffer));
sleep(1);
if(cnt==5)
{
close(fds[1]);
break;
}
}
exit(0);
}
else{
//父进程关闭写
close(fds[1]);
char buffer[1024];
int cnt=0;
while(1)
{
//管道没有数据时,父进程在读取中处于阻塞状态,等待下一次读取
ssize_t sz=read(fds[0],buffer,sizeof(buffer)-1);
//测试阻塞效果展示
cout<<"you can see me!"<<endl;
if(sz>0)
{
//读返回值不为0,继续下一次读取!
buffer[sz]=0;//前面写入没带\0,所以要补上!
cout<<"父进程接收信息:"<<buffer<<" | 父进程累计读取次数:"<<++cnt<<endl;
}
else{
//写关闭,读到返回值为0,退出读取!
cout<<"子进程写端口已关闭!\n";
break;
}
}
//父进程回收子进程结果
int status=0;
pid_t n=waitpid(id,&status,0);
//sig code !=0 表示子进程由信号杀掉,起因是子进程写入而父进程关闭了读取,OS回杀掉管道写入!
cout<<"pid->"<<n<<" | sig code->"<<(status&0X7F)<<endl;
}
return 0;
}
2.2.6 匿名管道读写规则
①、当没有数据可读时
O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
②、当管道满的时候
O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
③、如果所有管道写端对应的文件描述符被关闭,则read返回0
④、如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出
2.3 命名管道
2.3.1 命名管道(任何两个进程间通信)
匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。命名管道是一种特殊类型的文件
2.3.2 指令实现命名管道
mkfifo + pipe_name!
2.3.3 函数实现命名管道
//pipe.hpp
#include<iostream>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<cstring>
#include<cassert>
#define PIPE_PATH "/tmp/name_pipe"//路径+文件名 确定唯一的文件
void creat_pipe()
{
umask(0);
int ret=mkfifo(PIPE_PATH,0666);
if(ret==0) std::cout<<"creat pipe success!\n"<<std::endl;
else std::cout<<errno<<"creat pipe fail:"<<strerror(errno)<<std::endl;
}
void delete_pipe()
{
int n=unlink(PIPE_PATH);
assert(n==0);
(void)n;
}
//server.cpp
#include"pipe.hpp"
int main()
{
std::cout<<"hello server!"<<std::endl;
//创建命名管道
creat_pipe();
//打开管道文件
int rfd=open(PIPE_PATH,O_RDONLY);
assert(rfd!=-1);//打开文件失败返回-1
//server读取信息
while(true)
{
char buffer[1024];
ssize_t n=read(rfd,buffer,sizeof(buffer)-1);
if(n>0)
{
buffer[strlen(buffer)]=0;
std::cout<<"server get message:"<<buffer<<std::endl;
}
else
{
std::cout<<"client write has done!"<<std::endl;
break;
}
}
//关闭管道文件
close(rfd);
//销毁管道
delete_pipe();
return 0;
}
//client.cpp
#include"pipe.hpp"
int main()
{
std::cout<<"hello client!"<<std::endl;
//打开管道文件
int wfd=open(PIPE_PATH,O_WRONLY|O_TRUNC);
assert(wfd!=-1);
while(true)
{
std::cout<<"client sent message to server->";
char buffer[1024];
fgets(buffer,sizeof(buffer),stdin);
buffer[strlen(buffer)-1]=0;//去掉\n
//向管道文件写入
ssize_t n=write(wfd,buffer,strlen(buffer));
assert(n!=-1);
(void)n;
}
close(wfd);
return 0;
}
2.3.4 命名管道的打开规则
如果当前打开操作是为读而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
O_NONBLOCK enable:立刻返回成功
如果当前打开操作是为写而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
O_NONBLOCK enable:立刻返回失败,错误码为ENXIO
2.4 匿名管道与命名管道的区别
①、匿名管道由pipe函数创建并打开。
②、命名管道由mkfifo函数创建,打开用open
③、FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义