文章目录
- 前言
- 为什么要进程间通信
- 进程间通信的理论依据
- 管道
- 管道的原理
- 创建匿名管道
- 管道的特点
- 管道的场景
- 利用管道控制子进程
- 命名管道
- 命名管道的打开规则
- 命名管道和匿名管道的区别
- 用命名管道实现server和client通信
前言
大家好久不见,今天开始我们将进入进程间通信章节的学习。
为什么要进程间通信
现在看来,无非是为了资源共享。
进程间通信的理论依据
我们知道进程是独立的,如果我们要进行通信,前提是让两个进程能看到同一份资源。
管道
管道是unix中古老的通信方式,通过管道,我们可以完成进程间通信。
我们把从一个进程连接到另一个进程的数据流称之为管道。
管道的原理
父进程fork的时候,子进程会继承父进程打开的文件描述符,利用这个特性,设计出管道来完成进程间通信。
即让父进程创建管道,并fork创建子进程,再关闭对应写/读端
创建匿名管道
#include <unistd.h>
int pipe(int fd[2]);
功能:创建匿名管道
参数:fd,文件描述符数组,输出型参数,fd[0]读,fd[1]写
返回值:成功返回0,失败返回错误代码
管道的演示代码:
#include <iostream>
#include <string>
#include <cerrno>
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
using namespace std;
int main()
{
//1.创建管道
int pipeid[2];
int n = pipe(pipeid);
if(n < 0)
{
cout << errno << strerror(errno) << endl;
}
cout << pipeid[0] << endl; //0是张嘴 就是读
cout << pipeid[1] << endl; //1是个笔 就是写
//2.创建子进程
pid_t id = fork();
assert(id != -1);
if(id == 0)
{
//3.关闭不需要的fd,父进程读取,子进程写
close(pipeid[0]);
//5.通信开始
const string namestr = "hello,i am child";
int cnt = 1;
char buffer[1024];
while(true)
{
snprintf(buffer,sizeof(buffer),"%s,my cnt = %d,myPID = %d\n",namestr.c_str(),cnt++,getpid());
write(pipeid[1],buffer,strlen(buffer));
sleep(1);
}
close(pipeid[1]);
//让下面的代码被父进程独享
exit(0);
}
//4.父进程关闭写
close(pipeid[1]);
//5.开始父子进程通信:要结合某种场景!!!!
char buffer[1024];
while(true)
{
int n = read(pipeid[0],buffer,sizeof(buffer)-1);
//这里-1是给 \0 留位置
cout << "没有被阻塞住" << endl;
//cout << n << endl;
if(n > 0)
{
buffer[n] = '\0';
cout << "child send me message:" << buffer << endl;
}
}
return 0;
}
管道的特点
1、单向通信,管道是半双工的特殊情况。半双工即上课模式,老师讲完,学生问问题。
2、管道本质是文件,有对应的文件描述符fd,因此管道的生命周期随进程。
3、管道通信通常在有血缘关系的进程间通信,如父子进程。
4、管道通信写入和读取次数不是严格匹配的,两者没有强相关。原因在于 字节流
5、具有一定协同能力,让读和写能按照一定步骤进行通信。自带同步机制
管道的场景
1、如果read读取了所有的管道数据,对方不发,只能等待
2、管道写满后不可以再写,因为管道是有大小的
3、如果write关闭,读完管道数据,再读就会返回0,表明读到文件的结尾
4、写端一直写,读端关闭,OS会杀死一直写入却不读的进程,13)SIGPIPE
利用管道控制子进程
管道不只可以用来进程间通信,也可以利用管道控制子进程,比如父进程发送指令使得子进程退出。
命名管道
上述由继承得到的管道关系被称为匿名管道,匿名管道只能在具有亲缘关系的进程间进行通信,假如我们想让两个毫不相关的进程通信,可以使用命名管道。
1、在命令行上创建一个管道
mkfifo [filename]
2、函数调用
int mkfifo(const char* filename, mode_t mode)
参数:filename文件名,mode是开启管道的权限
返回值:调用成功返回0,调用失败返回-1
3、删除一个管道
unlink [filename]
命名管道的打开规则
- 如果当前操作为读
- 阻塞直到有相应进程写操作打开FIFO
- 若设置O_NONBLOCK,立即返回成功
- 如果当前操作为写
- 阻塞直到有相应进程读操作打开FIFO
- 若设置O_NONBLOCK,立即返回失败
命名管道和匿名管道的区别
1、命名管道由mkfifo创建,open开启。
2、匿名管道由pipe打开。
用命名管道实现server和client通信
server.cc
#include <iostream>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "comm.hpp"
int main()
{
//1、创建管道文件
umask(0);
int n = mkfifo(fifoname.c_str(),mode);
if(n != 0)
{
std::cout << errno << ":" << strerror(errno) << std::endl;
return -1;
}
std::cout << "管道创建完成" << std::endl;
//2、服务端开启管道文件
int r_fd = open(fifoname.c_str(),O_RDONLY);
if(r_fd < 0)
{
std::cout << errno << ":" << strerror(errno) << std::endl;
return -2;
}
std::cout << "管道文件已经成功开启,等待通信" << std::endl;
//3、正常进行通信:
char buffer[NUM];
while(true)
{
buffer[0] = '\0';
ssize_t n = read(r_fd,buffer,sizeof(buffer));
//系统调用都不去管C语言字符串的规定
//std::cout << n << std::endl;
if(n > 0)
{
buffer[n] = 0;
std::cout << "clien:" << buffer << std::endl;
}
else if(n == 0)
{
std::cout << "client quit, me quit too!" << std::endl;
break;
}
else
{
std::cout << errno << ":" << strerror(errno) << std::endl;
break;
}
}
// 关闭
close(r_fd);
unlink(fifoname.c_str());
return 0;
}
client.cc
#include <iostream>
#include <cstdio>
#include <cerrno>
#include <cstring>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "comm.hpp"
int main()
{
//以写方式打开管道
int w_fd = open(fifoname.c_str(),O_WRONLY);
if(w_fd < 0)
{
std::cout << errno << ":" << strerror(errno) << std::endl;
}
//进行通信
char buffer[NUM];
while(true)
{
std::cout << "请输入消息#";
char* msg = fgets(buffer,sizeof(buffer),stdin);
assert(msg);(void)msg;
//1 2 3 4 5 /n/0
//0 1 2 3 4 5 /0
buffer[strlen(buffer)-1] = 0;
if(strcasecmp(buffer,"quit") == 0) break;
ssize_t n = write(w_fd,buffer,sizeof(buffer)-1);
}
return 0;
}
运行即可达到如下效果: