进程间通信
- 一.基本概念
- 二.简单的通信-管道(匿名管道)
- 1.建立通信信道
- 2.通信接口
- 三.命名管道
- 三.模拟命名管道通信(加上日志)
- 1.完整代码
- 2.基本使用
一.基本概念
是什么
两个或多个进程实现数据层面的交互。
因为进程独立性的存在,导致进程间的通信成本比较高。
为什么
因为我们有多进程协同的需求。
怎么办
a.进程间通信的本质:必须让不同的进程看到同一份"资源"。
b.“资源”?特定形式的内存空间。
c.这个"资源"谁提供?一般是操作系统。
d.我们进程访问这个空间,进行通信,本质就是访问操作系统!进程代表的就是用户,“资源”从创建,使用(一般),释放―–需要系统调用接口!一般操作系统会有一个独立的通信模块-隶属于文件系统-IPC通信模块。
二.简单的通信-管道(匿名管道)
管道是Unix中最古老的进程间通信的形式。 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。
1.建立通信信道
管道是一个文件-内存级文件。
1.管道文件与一般文件不同,一般文件存在于磁盘里,管道文件存在于内存里。也就是说管道文件不需要将修改内容从内存刷新缓冲区到磁盘,这是它的特点。
2.一般管道文件只能具有血缘关系的进程间通信。因为只有具有血缘关系才能继承同一份files_struct。
3.一个父进程在创建管道文件时不能只是以读或者写的方式,必须两者都有。操作系统会把这个文件打开两次,分别用来读和写。但操作系统实际上只想让两个进程进行单向通信,因为如果一个进程又在读又在写,很容易会造成数据混淆,为了避免麻烦,规定只能一个进程写,另一个进程读。
这个文件不需要有名字,inode…让操作系统区分。所以这种文件也被称为匿名管道。
2.通信接口
pipe的作用就是帮助我们以读和写打开文件。它的参数是一个输出型参数,它会把分别以读和写的文件的文件描述符通过参数带出,供用户使用。pipefd[0]一般用于读,pipefd[1]一般用于写。
模拟
makefile
testPipe:TestPipe.cppg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -f testPipe
TestPipe.cpp(一个简单的通信,子进程向父进程里写信息)
#include<stdio.h>
#include<iostream>
#include<unistd.h>
#include<stdlib.h>
#include<string>
#include<cstdio>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>using namespace std;
#define N 2
#define NUM 1024//子进程
void Write(int wfd)
{//任意写几个数据测试string s="hello,i am a child";pid_t id=getpid();int num=0;char buffer[NUM];while(1){snprintf(buffer,sizeof(buffer),"%s-%d-%d\n",s.c_str(),id,num++);//将数据都变成字符存在buffer里//把数据写入管道write(wfd,buffer,strlen(buffer));sleep(1);}
}//父进程
void Read(int rfd)
{char buffer[NUM]={0};while(1){ssize_t n=read(rfd,buffer,sizeof(buffer));if(n>0){cout<<buffer<<endl;}else if(n==0) break;}}int main()
{int pipefd[N]={0};//创建管道int n=pipe(pipefd);//判断是否创建成功if(n<0) return 1;//创建子进程pid_t id=fork();if(id<0) return 2;if(id==0){//子进程//关闭读功能close(pipefd[0]);//IPC codeWrite(pipefd[1]);//退出close(pipefd[1]);exit(0);}//父进程//关闭写功能close(pipefd[1]);//IPC codeRead(pipefd[0]);//退出//回收子进程pid_t rid=waitpid(id,nullptr,0);if(rid<0) return 3;close(pipefd[0]);return 0;
}
管道的4中情况:
1.读写端正常,管道如果为空,读端就要阻塞 2读写端正常,管道如果被写满,写端就要阻塞 3.读端正常读,写端关闭,读端就会读到0,表明读到了文件(pipe)结尾,不会被阻塞
4,写端正常写入,读端被关闭。操作系统就要杀掉正在写入的进程。如何干掉?通过信号杀掉。
管道的特征:
1.具有血缘关系的进程进行进程间通信。
2.管道只能单向通信。
3.父子进程是会进程协同的,同步与互斥的—保护管道文件的数据安全 4.管道是面向字节流的。
5.管道是基于文件的,而文件的生命周期是随进程的!
三.命名管道
很明显上面的匿名管道只使用于具有血缘关系的通信是远远不够的,为了解决这个问题,又有了命名管道的概念。
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
命名管道是一种特殊类型的文件。
makefile
创建一个命名管道命名为myfifo
三.模拟命名管道通信(加上日志)
1.完整代码
日志(log.hpp)
#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>#define SIZE 1024#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4#define Screen 1
#define Onefile 2
#define Classfile 3#define LogFile "log.txt"class Log
{
public:Log(){//初始化打印方式和路径printMethod=Screen;path="./log/";}~Log(){}//改变打印方式void Enable(int methed){printMethod=methed;}//把整形转换成字符串std::string levelToString(int level){switch (level){case Info:return "Info";case Debug:return "Debug";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "None";}}void printOneFile(const std::string &logname, const std::string &logtxt){std::string _logname=path+logname;//连接文件路径int fd=open(_logname.c_str(),O_WRONLY|O_CREAT|O_APPEND,0666);//打开文件if (fd < 0) return;//向文件里写入write(fd, logtxt.c_str(), logtxt.size());close(fd); }void printClassFile(int level,const std::string &logtxt){std::string filename=LogFile+'.'+levelToString(level);//拼接路径printOneFile(filename,logtxt);}void printLog(int level,const std::string &logtxt){//选择打印方式switch (printMethod){case Screen://向屏幕打印std::cout << logtxt << std::endl;break;case Onefile://向一个文件里打印printOneFile(LogFile, logtxt);break;case Classfile://向多个文件里打印printClassFile(level, logtxt);break;default:break;}}//重载括号,让其能像log s; s(...)一样使用void operator()(int level,const char*format, ...){//首先将时间加入日志time_t t=time(nullptr);struct tm*ctime=localtime(&t);char leftbuffer[SIZE];snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,ctime->tm_hour, ctime->tm_min, ctime->tm_sec);//接着将用户输入部分加入//处理可变参数va_list s;va_start(s,format);char rightbuffer[SIZE];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);//将两个部分合并char logtxt[2*SIZE];snprintf(logtxt,sizeof(logtxt),"%s %s\n",leftbuffer,rightbuffer);//打印到对应位置printLog(level, logtxt);}
private:int printMethod;std::string path;
};
创建管道(comm.hpp)
#pragma once#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>#define FIFO_FILE "./myfifo"
#define MODE 0664enum
{FIFO_CREATE_ERR = 1,FIFO_DELETE_ERR,FIFO_OPEN_ERR
};class Init
{
public:Init(){// 创建管道int n = mkfifo(FIFO_FILE, MODE);if (n == -1){perror("mkfifo");exit(FIFO_CREATE_ERR);}}~Init(){int m = unlink(FIFO_FILE);if (m == -1){perror("unlink");exit(FIFO_DELETE_ERR);}}
};
管道通信读取方(server.cc)
#include "comm.hpp"
#include "log.hpp"using namespace std;// 管理管道文件
int main()
{Init init;Log log;// log.Enable(Onefile);log.Enable(Onefile);// 打开管道int fd = open(FIFO_FILE, O_RDONLY); // 等待写入方打开之后,自己才会打开文件,向后执行, open 阻塞了!if (fd < 0){log(Fatal, "error string: %s, error code: %d", strerror(errno), errno);exit(FIFO_OPEN_ERR);}log(Info, "server open file done, error string: %s, error code: %d", strerror(errno), errno);log(Warning, "server open file done, error string: %s, error code: %d", strerror(errno), errno);log(Fatal, "server open file done, error string: %s, error code: %d", strerror(errno), errno);log(Debug, "server open file done, error string: %s, error code: %d", strerror(errno), errno);// 开始通信while (true){char buffer[1024] = {0};int x = read(fd, buffer, sizeof(buffer));if (x > 0){buffer[x] = 0;cout << "client say# " << buffer << endl;}else if (x == 0){log(Debug, "client quit, me too!, error string: %s, error code: %d", strerror(errno), errno);break;}elsebreak;}close(fd);return 0;
}
管道通信写入方(client.cc)
#include <iostream>
#include "comm.hpp"using namespace std;int main()
{int fd = open(FIFO_FILE, O_WRONLY);if(fd < 0){perror("open");exit(FIFO_OPEN_ERR);}cout << "client open file done" << endl;string line;while(true){cout << "Please Enter@ ";getline(cin, line);write(fd, line.c_str(), line.size());}close(fd);return 0;
}
makefile
.PHONY:all
all:server clientserver:server.ccg++ -o $@ $^ -g -std=c++11mkdir log
client:client.ccg++ -o $@ $^ -g -std=c++11.PHONY:clean
clean:rm -f server clientrm -r log
2.基本使用
上面代码是向log.txt文件里写入。我们首先运行读文件,再运行写文件。任意通信,然后关闭进程,再打开log.txt,就可以看到日志已经写入。