目录
一、进程间通信
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(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义