目录
- 一、认识进程间通信
- 二、匿名管道
- 三、命名管道
一、认识进程间通信
进程间不能直接传递数据,因为进程具有独立性,直接传递会破坏进程的独立性。
进程间通信是什么?
一个进程把自己的数据交给另一个进程。
为什么要有进程间通信?
需要多个进程协同,共同完成一些事(数据共享、资源共享、通知事件、进程控制)
怎么做?
先让不同的进程看到同一份资源,这个同一份资源由操作系统提供。交换数据的内存,不能由通信双方的任何一个提供。
具体:因为操作系统提供空间有不同的方式,所以有不同的通信方式——管道、共享内存、消息队列、信号量
二、匿名管道
一个进程的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;
}