🤖个人主页 :晚风相伴-CSDN博客
💖如果觉得内容对你有帮助的话,还请给博主一键三连(点赞💜、收藏🧡、关注💚)吧
🙏如果内容有误的话,还望指出,谢谢!!!
✨下一篇文章:《进程间通信之共享内存》敬请期待💪
目录
理解进程间通信的本质
因为进程具有独立性,所以每个进程都只知道自己,而不知道有另外的进程存在,所以要实现不同进程间的通信,就要让不同的进程都能看到同一块资源,这块资源不属于任意一个进程,而是强调共享,利用这块资源就可以实现进程间通信了。
总结一下要点
- 进程间通信的前提是要让不同的进程看到同一块资源
- 这一块资源不隶属于任何一个进程,而是被这些进程所共享
管道
管道想必大家都不陌生吧,在Linux命令行中我们可以通过管道( | )将一个进程输出连接到另一个进程的输入,从而实现数据的传输、连接、过滤和处理等功能。例如
管道也好理解就比如家里面的水管,一端进水,另一端出水。
管道的分类
- 匿名管道
- 命名管道
🔥匿名管道
匿名管道主要用于父子进程之间的通信。
创建匿名管道的接口
参数:fildes是一个文件描述符数组,fildes[0]表示读端,fildes[1]表示写端
返回值:成功返回0,失败则返回-1并设置对应的错误码
🔥 匿名管道如何实现进程间通信
- 父进程创建匿名管道,得到两个文件描述符,一个文件描述符用来读数据,另一个文件描述符用来写数据
- 调用fork创建子进程,创建出来的子进程会继承父进程创建管道时获得的那两个文件描述符
- 在父子进程中关闭不需要的文件描述符,比如子进程读取数据,父进程写入数据,那就在子进程中关闭写入的文件描述符,父进程中关闭读取的文件描述符,之后就可以进行相应的通信操作了。
🔥从文件描述符的角度理解匿名管道
用代码实现匿名管道
cpp
#include <iostream>
#include <cstdio>
#include <cstring>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
int main()
{
//1.创建管道
int pipefd[2] = {0};//pipefd[0]:读端,pipefd[1]:写端
int n = pipe(pipefd);
assert(n != -1);//在Debug下才有用
(void)n;//确保在release不报警告
//条件编译
#ifdef DEBUG
cout << "pipefd[0]:" << pipefd[0] << endl;
cout << "pipefd[1]:" << pipefd[1] << endl;
#endif
//2.创建子进程并构建通信信道------父进程写入,子进程读取
pid_t id = fork();
assert(id != -1);
if(id == 0)
{
//子进程
close(pipefd[1]);//关闭子进程不需要的fd
//读取数据
char buffer[1024];
while(true)
{
//sleep(10);
ssize_t s = read(pipefd[0], buffer, sizeof buffer - 1);
if(s > 0)
{
buffer[s] = 0;
cout << "child get a massage [" << getpid() << "] father say:" << buffer << endl;
}
}
close(pipefd[0]);
exit(0);
}
//父进程
close(pipefd[0]);//关闭父进程不需要的fd
//发送数据
string message = "我是父进程,我正在给你发送数据";
int count = 0;//统计发送次数
char send_buffer[1024];
while(true)
{
//sleep(10);
//将要发送的数据打入send_massage中
snprintf(send_buffer, sizeof send_buffer, "%s[%d] : %d\n", message.c_str(), getpid(), count++);
//写入数据
write(pipefd[1], send_buffer, strlen(send_buffer));
sleep(1);
}
//等待子进程退出
pid_t ret = waitpid(id, nullptr, 0);
assert(ret);
(void)ret;
close(pipefd[1]);
return 0;
}
运行结果
🔥匿名管道读写的4个特殊情况
情况一:写端很快,读端很慢,写端将缓冲区写满后就不能再写了,必须等读端读取后才能写让子进程读端sleep(10)秒钟
情况二:写端很慢,读端很快,管道中没有数据的时候,读端必须等待写端写入数据之后才能读
让父进程写端sleep(10)秒钟
情况三:写端关闭,读端读到0时表示读到了文件末尾
现象就是它会阻塞在那
情况四:读端关闭,写端继续写,当将缓冲区写满后操作系统会终止写进程
🔥管道的特点
- 管道是用于具有亲缘关系的进程之间进行进程间通信(常用于父子进程)
- 管道可以让父子进程之间进行协同,并且提供了访问控制(上面的4种情况就是访问控制的体现)
- 管道提供的是面向流式的通信服务(面向字节流)
- 管道的生命周期是随进程的
- 管道是单向通信的,也就是半双工通信的一种特殊情况
完整代码链接: 匿名管道
利用匿名管道设计一个进程池
原理:fork多个子进程,然后用个随机数式的负载均衡让多个子进程都有概率能被使用到
🔥命名管道
匿名管道的一个限制就是只能在具有亲缘关系的进程间通信,但是如果我们想在不相关的进程之间实现通信,那么就需要用到命名管道。
🔥命名管道如何实现进程间通信
要实现进程间通信本质是要让不同的进程看到同一块资源。命名管道其实是在磁盘上创建了一个管道文件,这个管道文件可以随意被命名,并且有对应的属性但是没有内容,当我们在一个进程中打开这个管道文件并且将数据写入这个文件中,另一个进程也就可以打开这个管道文件并且从这个文件中读取数据,不同的进程都能打开并使用这个管道文件,所以也就让不同的进程看到了同一块资源。并且在这读写过程中管道文件中的数据不会加载到磁盘中,所以管道文件的大小始终为0保持不变。
命名管道的创建
在命令行上可以使用mkfifo命令来创建一个命名管道
也可以使用系统接口来创建命名管道
参数:
- pathname :命名管道文件的路径
- mode:管道文件的权限
返回值:成功返回0,失败则返回-1
代码实现命名管道
comm.hpp
cpp
#ifndef _COMM_H_
#define _COMM_H_
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "log.hpp"
using namespace std;
#define MODE 0666 // 文件权限
#define SIZE 128
string ipcPath = "./myfifo.ipc"; // 管道文件路径
#endif
cpp
#include "comm.hpp"
#include <sys/wait.h>
static void getMessage(int fd)
{
// 进行通信操作
char buffer[SIZE];
while (true)
{
memset(buffer, '\0', sizeof buffer);
ssize_t s = read(fd, buffer, sizeof(buffer) - 1);
if (s > 0)
{
// 读取成功
cout << "[" << getpid() << "] "
<< "client say> " << buffer << endl;
}
else if (s == 0)
{
// 读到文件结尾
cerr << "[" << getpid() << "] "
<< "read end of file, client quit, server quit too!" << endl;
break;
}
else
{
// 读取失败
perror("read");
break;
}
}
}
int main()
{
// 创建管道文件
if (mkfifo(ipcPath.c_str(), MODE) < 0)
{
perror("mkfifo");
exit(1);
}
logMessage(NORMAL, "创建管道文件成功, step 1"); // 打印日志
// 打开管道文件
int fd = open(ipcPath.c_str(), O_RDONLY);
if (fd < 0)
{
perror("open");
exit(2);
}
logMessage(NORMAL, "打开管道文件成功, step 2");
int nums = 5;
for (int i = 0; i < nums; i++)
{
pid_t id = fork();
if (id == 0)
{
// 子进程抢占式读取消息
getMessage(fd);
exit(1);
}
}
// 等待子进程退出
for (int i = 0; i < nums; i++)
{
waitpid(-1, nullptr, 0);
}
// 关闭文件
close(fd);
logMessage(NORMAL, "关闭管道文件成功, step 3");
// 通信完成,将管道文件删除
unlink(ipcPath.c_str());
logMessage(NORMAL, "删除管道文件成功, step 4");
return 0;
}
cpp
#include "comm.hpp"
int main()
{
//获取管道文件
int fd = open(ipcPath.c_str(), O_WRONLY);
if(fd < 0)
{
perror("open");
exit(1);
}
//发送消息
string buffer;
while(true)
{
cout << "Please Enter Message Line > ";
getline(cin, buffer);
write(fd, buffer.c_str(), buffer.size());
}
//关闭文件描述符
close(fd);
return 0;
}
完整代码链接:命名管道