前言
进程间通信的必要性:
之前写的代码都是单进程的。是无法使用并发能力,并且无法实现多进程协同
传输数据,同步执行流,消息通知等
进程间通信不是目的,而是一种手段。
进程间通信的技术背景
进程是具有独立性的。虚拟地址空间+页表。保证进程运行的独立性(进程内核数据结构+进程的代码和数据)
通信成本比较高。
如何理解进程间通信?
进程运行具有独立性!——进程想通信,难度比较大——进程间通信的本质:需要中间媒介,先让不同的进程看到同一份资源(内存空间)
所谓的进程看到同一块“内存”,属于哪一个进程?
不能隶属于任何一个进程,而应该强调共享。
为什么要进行进程间通信?
——需要交互数据、控制、通知等目标
1. 进程间通信方式的一些标准:
进程间通信发展
- 管道——linux原生能提供
- System V进程间通信——多进程——单机通信
- POSIX进程间通信——多线程——网络通信
进程间通信分类
-
管道
匿名管道pipe 命名管道 -
System V IPC
System V 消息队列
System V 共享内存
System V 信号量 -
POSIX IPC
消息队列
共享内存
信号量
互斥量
条件变量
读写锁
标准更多是在我们使用者看来,在接口上具有一定的规律
2. 管道
2.1 什么是管道
管道是Unix中最古老的进程间通信的形式。
我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
竖画线就是管道“|”
只能单向通信,传输的都是资源(数据)
有入口,有出口
计算机通信领域的设计者,设计了一种单向通信的方式——管道
2.2 管道的原理
管道通信背后是进程之间通过管道进行通信
纯内存级通信方式——没必须要写到磁盘中
管道的底层原理就是通过文件实现的
文件是属于内核的(OS)
1.分别溢读写方式打开同一个文件
2.fork()创建子进程——进程具有独立性,此时子进程也应自己创建一个文件描述符表,然后父进程相关的数据会拷贝给子进程——拷贝只是第一次拷贝,之后不会存在父子进程相互影响的情况
3.双方进程各自关闭自己不需要的文件描述符
让不同的进程看到了同一份资源
2.3 匿名管道
#include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码
2.3.1 实例代码
1. demo代码
如何做到让不同的进程看到同一份资源呢?
fork让子进程继承的——能够让具有血缘关系的进程进行进程间通信——常用父子进程
int pipe(int pipefd[2]);
int pipefd[2]是输出型参数,期望通过调用它,得到被打开的文件fd
int pipe创建成功返回值为0,失败就是-1
Makefile
mypipe:mypipe.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f mypipe
条件编译:
#ifdef DEBUG
//…
#endif
取消注释,宏定义就开始编译。
g++ -o $@ $^ -std=c++11 #-DDEBUG#调试
mypipe.cc
#include <iostream>
#include <unistd.h>
#include <assert.h>
#include <string>
#include <cstdio>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
int main()
{
//1.创建管道
int pipefd[2] = {
0};//保存管道的一个数组,这个数组父子进程都能看到
//pipefd[0]:读端, pipefd[1]:写端
int n = pipe(pipefd);
assert(n != -1);//debug下断言才有效
(void)n;//release下要加这句代码才有效,有使用有定义
#ifdef DEBUG
cout << "pipefd[0]: " << pipefd[0] << endl;//3
cout << "pipefd[1]: " << pipefd[1] << endl;//4
#endif
//2.创建子进程
pid_t id = fork();
assert(id != -1);
if(id ==0)
{
//子进程——读
//3.构建单向通信的信道,父进程写入,子进程读取
//3.1关闭子进程不需要的fd
close(pipefd[1]);
char buffer[1024];
while(true)
{
ssize_t s = read(pipefd[0],buffer,sizeof(buffer)-1);
if(s>0)
{
buffer[s] = 0;//通信时传的是字符串
cout << "Father# " << buffer << endl;
}
}
exit(0);//终止
}
//父进程——写
//3.构建单向通信的信道,父进程写入,子进程读取
//3.1关闭父进程不需要的fd
close(pipefd[0]);
string message = "我是父进程,我正在给你发消息";
int count = 0;
char send_buffer[1024];
while(true)
{
//3.2构建一个变化的字符串
//snprintf:安全的进行格式化显示
snprintf(send_buffer,sizeof(send_buffer),"%s[%d] : %d",
message.c_str(), getpid(), count++);
//3.3写入
write(pipefd[1],send_buffer,strlen(send_buffer));//文件不需要+1
//3.4故意sleep
sleep(1);
}
pid_t ret = waitpid(id,nullptr,0);
assert(ret>0);
(void)ret;
return 0;
}
这就叫做管道。
为什么不定义全局buffer来进行通信呢?
因为有写时拷贝的存在,无法更改通信!
#include <iostream>
#include <unistd.h>
#include <assert.h>
#include <string>
#include <cstdio>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
int main()
{
//1.创建管道
int pipefd[2] = {
0};//保存管道的一个数组,这个数组父子进程都能看到
//pipefd[0]:读端, pipefd[1]:写端
int n = pipe(pipefd);
assert(n != -1);//debug下断言才有效
(void)n;//release下要加这句代码才有效,有使用有定义
#ifdef DEBUG
cout << "pipefd[0]: " << pipefd[0] << endl;//3 默认从3开始,因为012被占用,标准输出...
cout << "pipefd[1]: " << pipefd[1] << endl;//4
#endif
//2.创建子进程
pid_t id = fork();
assert(id != -1);
if(id ==0)
{
//子进程——读
//3.构建单向通信的信道,父进程写入,子进程读取
//3.1关闭子进程不需要的fd
close(pipefd[1]);
char buffer[1024 * 8];
while(true)
{
//写入的一方,fd没有关闭,如果有数据就读,没有数据就等
//写入的一方,fd关闭,读取的一方read会返回0,表示读到了文件的结尾
ssize_t s = read(pipefd[0],buffer,sizeof(buffer)-1);
if(s>0)
{
buffer[s] = 0;//通信时传的是字符串
cout <<"child get a message["<<getpid()<< "] Father# " << buffer << endl;
}
else if(s == 0)
{
cout << "write quit(father), me quit!!!" << endl;
break;
}
}
//close(pipefd[0]);
exit(0);//终止
}
//父进程——写
//3.构建单向通信的信道,父进程写入,子进程读取
//3.1关闭父进程不需要的fd
close(pipefd[0]);
string message = "我是父进程,我正在给你发消息";
int count = 0;
char send_buffer[1024*8];
while(true)
{
//3.2构建一个变化的字符串
//snprintf:安全的进行格式化显示
snprintf(send_buffer,sizeof(send_buffer),"%s[%d] : %d",
message.c_str(), getpid(), count++);
//3.3写入
write(pipefd[1],send_buffer,strlen(send_buffer));//文件不需要+1
//3.4故意sleep
sleep(1);
cout<<count<<endl;
if(count == 5)
{
cout<<"write quit(father)"<<endl;
break;
}
}
<