通信
通信的本质:1、数据传输,将自己的数据发送给另一个进程。2、资源共享,多个进程之间共享同样的数据。3、通知事件:一个进程向另一个或一组进程发送消息,通知他发生了某种事件,如进程终止通知父进程。4、进程控制:有些进程希望他们完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常。
进程需要通信,本质上是数据的相互交换,需要内存存储数据?那在哪里存储数据呢?因为各自进程是具有独立性的,所以不可能在本进程进行存储,所以需要操作系统直接或间接给通信双方的进程提供内存空间(存放交换数据)。
目前进程间通信有两套标准:POSIX--让通信过程可以跨主机;System V --聚焦在本地通信。还有共享内存(消息队列,信号量)等。
不同的通信种类本质就是:由操作系统提供的资源是由哪一个模块儿提供的:
如果是文件系统提供的:管道通信,由System V提供的:System V,提供一大块内存:共享内存,如果是计数器:信号量,如果是队列:消息队列。
匿名管道
对于父子进程而言:子进程会复制父进程的PCB相关内容,其中file_struct也会复制一份,而对于文件系统相关的文件,则是存储在内存里,不会复制。对于父子进程的file_struct看到的是同一份的内核资源(文件系统)。就通过这个内核资源进行通信,但是该文件不会存储在磁盘上,也不会在磁盘上IO刷新,有属于自己的内核缓冲区,是一个内存级文件,这个文件就叫做匿名管道。父子进程分别以读和写方式打开匿名管道,一个进程负责写,一个进程负责读,管道是无法完成单向通信的需求。
什么叫管道:由父进程通过调用特定的管道系统调用,以读的方式和写的方式打开一个内存级文件(这个内存级文件是有大小的,也就是说写得过快,写操作会被堵塞,当然,读过快,也会被堵塞到read命令),并通过fork()建立子进程继承下去,分别关闭父子进程的读写端,从而形成一条文件级别的通信信道。一般而言,管道只能用来进行单向数据通信!!!!
#include <iostream>
#include <cstring>
#include <cstdio>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <unistd.h>
using namespace std;
//父进程进行读取,子进程进行写入
int main()
{
int fds[2]; //用来接受创建管道中的输出型参数
int d = pipe(fds);
assert(d==0);
int id=fork();
assert(id>=0);
if(id==0)
{
//子进程进行写入,关闭读取
close(fds[0]);
//子进程
int cnt=0;
const char* s= "这是子进程给父进程发送消息";
while(true)
{
cnt++;
char buffer[1024];
//c语言的格式化字符串的输出
snprintf(buffer,sizeof buffer,"child-->parent: %s[%d][%d]",s,cnt,getpid());
//将buffer的数据通过系统调用write写到管道中
write(fds[1],buffer,strlen(buffer));
if(cnt==5)
break;
//sleep(1);
}
close(fds[1]);
exit(0);
}
//父进程进行读取,关闭写入
close(fds[1]);
while(true)
{
char buffer[1024];
//调用系统调用read从管道文件中读取字符
ssize_t n= read(fds[0],buffer,sizeof(buffer)-1);
if(n>0)
{
buffer[n]=0;
cout<<"#"<<buffer<<"| parent pid"<<getpid()<<endl;
}
else if(n==0) //当写入操作结束的时候,读取到最后一个字符的时候,可以退出
{
cout<<"管道内的内容已经被读取完成"<<endl;
break;
}
}
//父进程,需要等待子进程
int status=0;
int n=waitpid(id,&status,0);
//status的低7位是进程退出码
cout<<"pid-->"<<(status&0x7F)<<endl;
assert(n==id);
// cout<<"fds[0]:"<<fds[0]<<endl;
// cout<<"fds[1]:"<<fds[1]<<endl;
//std::cout<<"hello c++"<<std::endl;
return 0;
}
读写特征:
1、读快,写慢:会等待管道部分内容被读取之后,在写入。
2、读快,写慢:读会在read处阻塞。
3、写关闭,一直读:会读到管道的末尾处。
4、读关闭,写:OS会终止写端,给写端发送信号,13,终止写端。
管道特征:
1、管道的生命周期进程;
2、管道可以用来进行具有血缘关系的进程之间进行通信,常用于父子通信。
3、管道是面向字节流的
4、半双工,单向通信
5、互斥与同步机制-对共享资源进行保护的方案。
匿名管道的管道池
创建n个子进程和n个任务,随机将任务分配给各个子进程去执行
#include <iostream>
#include <unistd.h>
#include <string>
#include <assert.h>
#include <vector>
#include <time.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/types.h>
using namespace std;
#define NUM_Process 5
//生成随机数
#define MakeSeed() srand((unsigned long)time(nullptr) ^ getpid() ^ 0x171237 ^ rand() % 1234)
//函数指针
typedef void(*func)();
void Func1()
{
cout<<getpid()<<" :call Func1"<<endl;
sleep(1);
}
void Func2()
{
cout<<getpid()<<" :call Func2"<<endl;
sleep(1);
}
void Func3()
{
cout<<getpid()<<" : call Func3"<<endl;
sleep(1);
}
void LoadFunc(vector<func> *subFunc) //输出型参数
{
assert(subFunc);
subFunc->push_back(&Func1);
subFunc->push_back(&Func2);
subFunc->push_back(&Func3);
}
//键值对{namex : func1}
class info_Process{
public:
info_Process(pid_t pid,int id) //子进程ID和管道id(也就是文件描述符)
:pid_(pid),id_(id)
{
char namebuffer[1024];
snprintf(namebuffer,sizeof namebuffer,"Process%d[%d][%d]",cnt,pid_,id);
name_ = namebuffer;
}
public:
string name_; //每个子进程的名字
int id_; //子进程读取管道的id
pid_t pid_; //子进程的PID
static int cnt;
};
int info_Process::cnt=0;
int recvTask(int fds)
{
int code=0;
ssize_t n = read(fds,&code,sizeof code);
if(n==4) return code;
else if(n<=0) return -1;
else return 0;
}
void creatSubPro(vector<info_Process> *subPro,vector<func> &subFunc) //做输出型参数
{
for(int i=0 ; i<NUM_Process ; ++i)
{
int fds[2];
ssize_t n= pipe(fds); //成功了返回0
assert(n == 0);
(void)n;
pid_t id=fork();
if(id==0)
{
//子进程关闭write功能,只留下了读的功能
close(fds[1]);
while(true)
{
//1.获取命令码,如果没有发送,需要阻塞
int commandCode = recvTask(fds[0]);
//2.完成命令
if(commandCode >=0 && commandCode < subFunc.size())
{
subFunc[commandCode]();
}
else if(commandCode == -1) break;
}
//子进程退出
exit(0);
}
//将子进程的id信息,名字,文件口的id存储进去
//创建一个临时的info_Process变量,存储每个进程的参数
close(fds[0]);
info_Process temp_info(id,fds[1]);
subPro->push_back(move(temp_info)); //move这里把temp_info变成左值,直接记录进subPro
}
}
void SendTask(const info_Process& subProcess,const int index)
{
cout<<"send task num: "<<index<<"send to ->"<<subProcess.name_<<endl;
int n=write(subProcess.id_,&index,sizeof(index));
assert(n==sizeof(int));
(void)n; //防止出现warning警告
}
void LoadBalanceFactor(const vector<info_Process> &subPro,const vector<func> &subFunc,int num)
{
//获取到子进程的总量以及任务的总量
int processnum=subPro.size();
int tasknum = subFunc.size();
bool forever = (num==0?true:false);
while(true)
{
//1.那个子进程去执行任务
int IndexProcess = rand()%processnum;
//2.规定什么任务
int IndexTask = rand()%tasknum;
//3.分发任务
SendTask(subPro[IndexProcess],IndexTask);
sleep(1);
if(!forever)
{
num--;
if(num==0) break; //如果任务数量为0的话退出进程
}
}
//当退出while循环的时候,这里需要对子进程进行结束和等待
//防止出现僵尸进程
//这里关闭的时候应该注意:在创建的时候是循环创建的,所以第2个子进程会继承父进程的
//所有的文件描述符,也就是说 假设文件描述符3与进程1相连接 子进程2的文件描述符中有一个文件描述符3也指向进程1
//也就是越到后面的进程,就会继承父进程的所有的文件描述符去指向上面的子进程
//所以在关闭子进程的时候,应该注意如果是waitpid的方式,需要从末尾开始关闭,如果从头开始关闭的话,是不能完全关闭的
//
//这里的关闭方法是close,在关掉最后一个子进程的时候,文件描述符被关闭,所以也能正常关闭
// write 被关闭的时候,read方也会被关闭
for(int i=0;i<subPro.size();++i)
{
close(subPro[i].id_);
}
}
void waitProcess(vector<info_Process> processes)
{
int processnum = processes.size();
for(int i = 0; i < processnum; i++)
{
waitpid(processes[i].pid_, nullptr, 0);
std::cout << "wait sub process success ...: " << processes[i].pid_ << std::endl;
}
}
int main()
{
MakeSeed();
//1.首先创建子进程的Vector 以及 任务的vector
vector<info_Process> subPro;
vector<func> subFunc;
//将所有的函数任务放入到subFunc中去
LoadFunc(&subFunc);
creatSubPro(&subPro,subFunc);
//父进程
int task_num=10;
//父进程负责分发命令给子进程
LoadBalanceFactor(subPro,subFunc,task_num);
waitProcess(subPro);
return 0;
}
命名管道
匿名管道针对的是父子进程或者亲戚进程的相互通信,对于不同进程之间的通信,可以使用命名管道,命名管道的文件属性是p,当生成命名管道的时候,一端向文件写内容,一端向文件读内容。同时通信的时候,命名管道是内存级文件,也就是向文件写内容的时候,不会刷新到磁盘中。
命名管道的函数时mkfifo(pathname,mode)。
不同进程之间的通信如下代码所示:
首先需要一份公共资源需要两端都能看见
#include <iostream>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <assert.h>
#include <cerrno>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
#define NAMED_PIPE "/tmp/Namedpipe"
bool creatFifo(const string &path)
{
umask(0); //首先设置umask的值为0
int n=mkfifo(path.c_str(),0666); //创建的文件的权限是 rw
if(n==0)
return true;
else
{
std::cout<<"errno: "<<errno <<"err string :" <<strerror(errno)<<endl;
return false;
}
}
void removeFifo(const string &path)
{
int n = unlink(path.c_str());
assert(n==0); //debug中的assert才有效,release模式下不执行assert
(void)n;
}
然后需要对接收端也就是服务端的代码
#include "Comm.hpp"
//这边是接受信息,文件的创建和删除都在这边
int main()
{
bool n = creatFifo(NAMED_PIPE);
assert(n);
(void)n;
//开始从命名管道读数据
int rfd = open(NAMED_PIPE,O_RDONLY);
if(rfd<0) exit(-1);
char buffer[1024];
while(true)
{
ssize_t s = read(rfd,buffer,sizeof(buffer)-1);
if(s>0)
{
buffer[s]=0;
cout<<"client->server# "<<buffer<<endl;
}
else if(s==0)
{
cout<<"client quit,me too!"<<endl;
break;
}
else{
cout<<"err string:"<<strerror(errno)<<endl;
break;
}
}
remove(NAMED_PIPE);
return 0;
}
然后是发送端也就是客户端的内容
#include "Comm.hpp"
//这边是发送信息
int main()
{
int wrd = open(NAMED_PIPE,O_WRONLY);
if(wrd<0) exit(-1);
char buffer[1024];
while(true)
{
cout<<"Please say# ";
fgets(buffer,sizeof(buffer),stdin); //先往字符串写入字符
if(strlen(buffer)>0) buffer[strlen(buffer)-1]=0; //把最后一个字符改成/0
ssize_t n =write(wrd,buffer,strlen(buffer));
assert(n==strlen(buffer));
(void)n;
}
close(wrd);
return 0;
}