通信的概念
进程之间的数据传输,资源共享,发送通知,进程控制就属于进程间的通信
数据传输:
一个进程将其数据发送给另一个进程
资源共享:
多个进程之间共享同样的资源
通知事件:
一个进程向另一个进程发送消息也可以是向一组进程发送消息
进程控制:
一个进程控制另一个进程的执行
目前通信的主要标准分类为:
POSIX — 让通信可以跨主机
System V — 聚焦在本地通信
基于文件的通信方式为:管道
通信的本质:
因为进程具有独立性,所以通信并不是进程之间的直接交互,而是由操作系统直接或间接的给通信双方的进程提供内存空间,内存空间里的数据就是公共资源,而通信的前提就是需要通信的进程都必须看到同一份公共资源。不同的通信种类就是操作系统中不同的模板提供的。由上述即可得知,通信并不是低成本的工作。
管道
管道是基于文件系统的通信方式:从一个进程连接到另一个进程的数据流称为一个管道
每个文件都会有一个属于自己的内核缓冲区,这个区域就属于是进程之间的公共资源。因此进程间的通信就可通过这个内核缓冲区去实现。而这种基于文件系统的通信就是管道通信,这个文件就称为管道文件。管道文件时内存级的文件,不需要进行IO。一般而言管道只能用来进行单向数据通信
管道的特征:
- 管道的生命周期随进程,进程退出则管道释放
- 只要是具有血缘关系的进程就可以进行管道通信
- 管道是面向字节流的(网络)
- 单向通信
- 管道具有互斥与同步的机制
管道的系统调用
int pipe(int pipefd[2]); — <unistd.h>
创建匿名管道
创建失败返回-1, 创建成功返回0.
参数为输出型参数,需要将两个文件描述符(分别对应进程读和写)传入这个参数
创建成功后,数组保存两个文件描述符:pipfd[0] — 读;pipfd[1] — 写
int mkfifo(const char *filename,mode_t mode); — <sys/types.h><sys/stat.h>
创建以权限p开头的命名管道文件
参数1为文件名(C类型的字符串),参数2为文件的权限
创建失败返回-1, 创建成功返回0.
int unlink(const char* path); — <unistd.h>
删除管道文件
删除成功返回0.
匿名管道
通过父进程创建子进程去继承文件地址的方式,使得两进程看到同一个内存级文件,此时的内存级文件没有名称就称为匿名管道
创建匿名管道的步骤:
父进程创建出管道,分别以读和写的方式打开文件(为了让子进程继承,方便后期选择对于文件的单向数据通信)
父进程创建出子进程
命名管道
父进程关闭读,子进程关闭写(根据需求可自行关闭读/写),必须一个负责读一个负责写
匿名管道的读写特征:
- 当写的速度比读的速度慢时,会导致管道中没有数据,默认会将读的进程阻塞,等待管道有数据
- 当读的速度比写的速度慢时,会导致管道空间写满,默认会将写的进程阻塞等待读端读取
- 当写关闭后,读端会将数据读完,可由用户自行设置读完后的操作
- 当读关闭后,写就失去了意义,操作系统会发信号终止写的进程
int main(){ //1、创建管道文件,打开读写端 int fds[2]; int n = pipe(fds); assert(n == 0); //2、创建子进程 pid_t id = fork(); assert(id >= 0); //3、根据需求删除进程的读或写,确保单向传输 //父进程读,子进程写 if(id == 0){ //子进程 //子进程关闭读 close(fds[0]); int count = 0; while(1){ char buff[1024]; //写入字符串 snprintf(buff, sizeof(buff), "I am child -> parent: %d\n", ++count); write(fds[1], buff, strlen(buff)); //每隔一秒写一次 sleep(1); } exit(0); } //父进程 //父进程关闭写 close(fds[1]); //父进程读 while(1){ char buff[1024]; //在管道文件中读取到父进程的buff ssize_t i = read(fds[0], buff, sizeof(buff) - 1); //将独到的字符串最后加上\0 if(i > 0) buff[i] = 0; cout << "parent: " << buff << endl; } //等待 n = waitpid(id, nullptr, 0); assert(id == n); return 0; }
命名管道
创建一个以权限p开头的文件,进程可以往文件里写入也可以从文件里读取,实现通信
命名管道文件根据名称具有唯一性,进程可以通过路径 + 文件名 找到命名管道文件,从而实现通信
管道的读端在打开文件后不会继续往后运行,直至管道的写端也打开文件后才会继续运行
命名管道的通信需要两个不相干的进程来实现,如下代码示例
name_pipe.hpp
#pragma once #include<iostream> #include<string> #include<cerrno> #include<cassert> #include<cstring> #include<sys/types.h> #include<sys/stat.h> #include<unistd.h> #include<fcntl.h> using namespace std; #define NAME_PIPE "./MyPipe"//宏定义管道文件名字与路径 //创建管道文件 bool CreatFilePipe(const string& path){ umask(0); int n = mkfifo(path.c_str(), 0666); if(n == 0) return true; else{ std::cout << errno << " " << strerror(errno) << endl; return false; } } //删除管道文件 void RemoveFifo(const string& path){ assert(unlink(path.c_str()) == 0); }
Read.cc
#include"name_pipe.hpp" using namespace std; //读取 int main(){ //创建文件 //读端负责创建管道文件 bool flags = CreatFilePipe(NAME_PIPE); assert(flags); //创建成功后,打开管道文件 int fd = open(NAME_PIPE, O_RDONLY); if(fd < 0) exit(1); char buff[1024]; while(1){ //读取管道文件中的数据 ssize_t s = read(fd, buff, sizeof(buff) - 1); //读取成功 if(s > 0){ buff[s] = 0; std::cout << "Read: " << buff << endl; } //没有读到数据 else if(s == 0){ std::cout << "stop" << endl; break; } //读取出错 else break; } close(fd); //删除管道文件 RemoveFifo(NAME_PIPE); return 0; }
Write.cc
#include"name_pipe.hpp" using namespace std; //写入 int main(){ //因为读端已经有创建除了管道文件,写端只需要打开即可 int fd = open(NAME_PIPE, O_WRONLY); if(fd < 0) exit(1); string str; while(1){ //往管道文件里写入数据 getline(cin, str); assert(write(fd, str.c_str(), strlen(str.c_str())) == strlen(str.c_str())); } close(fd); return 0; }
当写端写入数据后,读端就会立即读取。命名管道与匿名管道最大区别就在于, 匿名管道是用于有血缘关系的进程之间,而命名管道适用于毫不相干的进程之间






