一、认识进程间通信
进程间不能直接传递数据,因为进程具有独立性,直接传递会破坏进程的独立性。
进程间通信是什么?
一个进程把自己的数据交给另一个进程。
为什么要有进程间通信?
需要多个进程协同,共同完成一些事(数据共享、资源共享、通知事件、进程控制)
怎么做?
先让不同的进程看到同一份资源,这个同一份资源由操作系统提供。交换数据的内存,不能由通信双方的任何一个提供。
具体:因为操作系统提供空间有不同的方式,所以有不同的通信方式——管道、共享内存、消息队列、信号量
二、匿名管道
一个进程的PCB对象里面有指向files_struct类型结构体的指针,该结构体里面有文件描述符表,0-2分别指向的是系统默认打开的标准输入、标准输出和标准错误。当往一个文件(磁盘中的log.txt)写入数据,然后读取数据,共两次操作,struct file会有两个,一个是写的,另一个是读的(不同指针指向同一个文件)。w操作调用系统接口,将写入的数据给缓冲区,然后再刷新到磁盘的文件中;r操作,磁盘的文件的数据先到缓冲区,然后对应的系统接口再把数据读取出来。总之,写入和读取都是在缓冲区进行的,因为缓冲区也是内存,调用系统接口,就是运行某个程序,程序和磁盘中文件要加载到内存。如果让当前进程fork后创建一个子进程,那么子进程的文件描述符表里面的下标3和4也指向父进程指向的struct file,files_struct是进程的属性之一,所以创建子进程时子进程的PCB对象也有files_struct,而struct file是文件系统的,就一份。因此,此时的父子进程如图指向struct file的w文件和r文件,w和r指向同一个内存缓冲区,然后再到磁盘中的一个文件log.txt。
既然有创建子进程,就是要子进程办事的,如果让子进程w,父进程r,那么,子进程写的通过缓冲区刷新到磁盘文件中(写的时候,文件要先被加载到内存,即缓冲区,),然后,父进程通过缓冲区读取文件的信息(也是在缓冲区,即内存中。不管读写,都要先将文件加载到内存执行)。这样,子进程写的,父进程就能看到,所以,在这里父子进程通过缓冲区,即一块内存,就能够看到同一份资源的,这种方式或者说是通信方式就叫管道。
管道只能是单向通信。父进程最开始时的权限是rw,因为这样给子进程也是rw,然后关闭权限(父进程关闭w,子进程关闭r,注意,父进程刚开始不能只有一个r或w,因为不能新打开权限),一个读,一个写。
为了实现管道通信,OS提供了系统调用pipe() 。pipefd数组得到两个fd,分别是读r和写w(输出型参数)
特点:不需要向磁盘刷新内容,它不是一个文件,磁盘中也不存在这个文件。也就是说它会有一块内存,但是与磁盘无关,它是一个内存级文件,没有名字,是匿名的,一般就叫它——管道。
匿名管道如何让不同的进程看到同一份资源?创建子进程,子进程继承父进程相关的属性信息。只能具有“血缘”关系的进程可以进程间通信,常用于父子进程。
代码:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
void reader(int rfd)
{
char buffer[1024];
while(1)
{
ssize_t n = read(rfd, buffer, sizeof(buffer));
printf("message: %s\n", buffer);
sleep(1);
}
}
void writer(int wfd)
{
char* str = "I am father";
char buf[1024];
int cnt = 1;
pid_t pid = getpid();
while(1)
{
snprintf(buf, sizeof(buf), "message: %s, pid: %d, cnt: %d", str, pid, cnt);
write(wfd, buf, sizeof(buf));
cnt++;
sleep(1);
}
}
int main()
{
//1、创建管道
int pipefd[2];
int n = pipe(pipefd);
if(n < 0) return 1;
//2、创建子进程
pid_t id = fork();
if(id == 0)
{
//子进程读
close(pipefd[1]);//关闭写
reader(pipefd[0]);//读
exit(0);
}
//父进程写
close(pipefd[0]);//关闭读
writer(pipefd[1]);//写
wait(NULL);//防止僵尸
return 0;
}
4种情况:一、管道内部没有数据,并且写端没有关闭,那么读端就要阻塞等待,直到pipe有数据;二、管道内部被写满,并且读端不关闭,那么写端写满之后,就要阻塞等待;三、对于写端,不写了,并且管道关闭,那么读端会把管道内的所有数据读完,最后读到返回值为0,表示读结束;四、读端不读并且关闭,写端在写,那么操作系统就会直接终止写入的进程
5种特性:一、自带同步机制(管道内部有数据,读端就读,没有就等待;管道没有写满,写端就写,满了就等待);二、血缘关系进程间通信,常见于父子进程;三、pipe是面向字节流的(写可能是一个一个的写,但是读可以一次性全部读出来);四、父子进程退出,管道自动释放,文件的生命周期是随进程的;五、管道只能单向通信。
命令行管道| 本质是匿名管道
三、命名管道
两个特点:1、让不同进程看到同一份资源;2、让没有亲缘关系的进程可以进行通信
怎么保证两个不同的进程打开的是同一个文件?找到文件:文件名+文件路径
系统调用接口:mkfifo、unlink
验证代码:
两个不相干的进程,一个发送消息,另一个接收
Comm.hpp
#ifndef __COMM_HPP__
#define __COMM_HPP__
#include <iostream>
#include <unistd.h>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <cstdlib>
#include <fcntl.h>
using namespace std;
#define Mode 0666
#define Path "fifo"
class Fifo
{
public:
Fifo(const string &path) : _path(path)
{
umask(0);
//创建管道
int n = mkfifo(_path.c_str(), Mode);
if(n == 0)
{
cout << "fifo create success" <<endl;
}
else
{
cout << "fifo fail, errno: " << errno << " stringerrno: " << strerror(errno) << endl;
}
}
~Fifo()
{
//删除管道,管道也是文件
int n = unlink(_path.c_str());
if(n == 0)
{
cout << "fifo remove success" <<endl;
}
else
{
cout << "fifo remove, errno: " << errno << " stringerrno: " << strerror(errno) << endl;
}
}
private:
string _path;//文件名+文件路径
};
#endif
pipeServer.cc
#include "Comm.hpp"
int main()
{
Fifo fifo(Path);//实例化
//打开文件
int rfd = open(Path, O_RDONLY);
if(rfd < 0)
{
cout << "open fail, errno: " << errno << " stringerrno: " << strerror(errno) << endl;
return 1;
}
char buffer[1024];
while(true)
{
ssize_t n = read(rfd, buffer, sizeof(buffer)-1);
if(n > 0)//读取成功
{
buffer[n] = 0;
cout << "message: " <<buffer<<endl;
}
else if(n == 0)//读停止
{
cout << "read quit.. " <<endl;
break;
}
else //读失败
{
cout << "read fail, errno: " << errno << " stringerrno: " << strerror(errno) << endl;
break;
}
}
close(rfd);//关闭文件
return 0;
}
pipeClient.cc
#include "Comm.hpp"
int main()
{
//打开文件
int wfd = open(Path, O_WRONLY);
if(wfd < 0)
{
cout << "open fail, errno: " << errno << " stringerrno: " << strerror(errno) << endl;
return 1;
}
string buffer;
while(true)
{
cout << "Please message# ";
getline(cin, buffer);
ssize_t n = write(wfd, buffer.c_str(), buffer.size());
if(n < 0)
{
cout << "write fail, errno: " << errno << " stringerrno: " << strerror(errno) << endl;
break;
}
}
close(wfd);//关闭文件
return 0;
}