❀Linux进程间通信
前言:当提及Linux系统中的进程间通信(IPC),管道(Pipes)无疑是最基础且广泛使用的一种机制。作为匿名通信的典范,管道为进程间数据交换提供了一个简单而有效的途径。在这个信息飞速传递的时代,掌握Linux管道的使用不仅是理解操作系统底层通信原理的关键一步,也是提升软件开发效率、构建复杂应用系统的必备技能
我们将详细介绍管道的创建过程、数据读写操作、管道的生命周期管理以及常见的使用场景。 同时,我们还会探讨管道在并发编程中的表现,分析其在多进程环境下的行为特性,并提供相应的优化策略。通过理论与实践相结合的方式,相信您能够全面掌握Linux进程间匿名通信的管道技术,为您的软件开发之路增添一份坚实的力量
让我们一同踏上这段探索之旅,揭开Linux管道的神秘面纱,领略其在进程间通信中的独特魅力!
📒1. 进程间通信介绍
-
原理: 进程间通信主要依赖于双方都可以访问的介质或系统空间。这些介质包括共享内存区、系统空间以及双方都可以访问的外设(如磁盘上的文件、数据库中的表项等)。然而,广义上的通过这些方式进行的通信一般不算作“进程间通信”。进程间通信更常见的是通过一组编程接口来实现,这些接口允许程序员协调不同的进程,使它们能在一个操作系统里同时运行,并相互传递、交换信息
-
必要性: 即使只有一个用户发出要求,也可能导致一个操作系统中多个进程的运行。这些进程之间必须互相通信,以协调它们的行为和共享资源。进程间通信使得一个程序能够在同一时间里处理许多用户的要求
📚2. 什么是管道
📜3. 匿名管道
创建匿名管道
#include <unistd.h>
//功能:创建一无名管道
//原型
int pipe(int fd[2]);
//参数
//fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
//返回值:成功返回0,失败返回错误代码
实例代码:
#include <iostream>
#include <cassert>
#include <cstring>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#define MAX 1024
using namespace std;
int main()
{
// 1. 建立管道
int pipefd[2] = {0};
int n = pipe(pipefd);
assert(n == 0);
// 定义 n
(void)n;
// 查看文件描述符
cout << "pipefd[0]: " << pipefd[0] << ", pipefd[1]: " << pipefd[1] << endl;
// 2. 创建子进程
pid_t id = fork();
if(id < 0)
{
perror("fork");
return 1;
}
// 子写,父读,
// 3. 关闭父子不需要的fd,形成单向通信的管道
if(id == 0)
{
// 子进程
close(pipefd[0]);
// 写入
int cnt = 10;
while(cnt)
{
char message[MAX];
snprintf(message, sizeof(message), "hello father, I am child, pid: %d, cnt: %d", getpid(), cnt);
cnt--;
write(pipefd[1], message, strlen(message));
cout << "writing cnt: " << cnt << endl;
}
exit(0);
}
// 父进程
close(pipefd[1]);
// 读取
char buffer[MAX];
while(true)
{
ssize_t n = read(pipefd[0], buffer, sizeof(buffer)-1);
if(n == 0)
{
cout << "child qiut, read tail" << endl;
break;
}
else if(n > 0)
{
buffer[n] = 0; // '\0', 当作字符串
cout << getpid() << ": " << "child say: " << buffer << " to me!" << endl;
}
}
pid_t rid = waitpid(id, nullptr, 0);
if(rid == id)
{
cout << "wait seccess" << endl;
}
return 0;
}
🌞fork共享管道原理
🌙结合文件描述符
⭐站在内核角度
📝4. 管道的读写情况与特点
🎈管道的读写情况
我们让读端一直读,而写端在写入部分文件后让它sleep一段时间,我们这是来观察一下读端的情况
代码示例:(C++):
if(id == 0)
{
// 子进程
close(pipefd[0]);
// 写入
int cnt = 10000;
while(cnt)
{
char message[MAX];
snprintf(message, sizeof(message), "hello father, I am child, pid: %d, cnt: %d", getpid(), cnt);
cnt--;
write(pipefd[1], message, strlen(message));
// 在正常写入一次后,sleep,父进程读取不做修改
sleep(4);
}
exit(0);
}
当我们的管道被写满了的时候,写端就不能在进行写入了,我们必须等待读端将数据读取走才能继续往管道里面写入,我们让读端休眠上几面,让写端一直写
代码示例:(C++):
if(id == 0)
{
// 子进程
close(pipefd[0]);
// 写入
int cnt = 0;
while(true)
{
char message[MAX];
snprintf(message, sizeof(message), "hello father, I am child, pid: %d, cnt: %d", getpid(), cnt);
cnt++;
write(pipefd[1], message, strlen(message));
// 在正常写入一次后,sleep,父进程读取不做修改
cout << "writing cnt: " << cnt << endl;
}
exit(0);
}
写端关闭代码示例:(C++):
if(id == 0)
{
// 子进程
close(pipefd[0]);
// 写入
int cnt = 0;
while(true)
{
char message[MAX];
snprintf(message, sizeof(message), "hello father, I am child, pid: %d, cnt: %d", getpid(), cnt);
cnt++;
write(pipefd[1], message, strlen(message));
//sleep(2);
cout << "writing cnt: " << cnt << endl;
// 在写入两次时,我们将子进程的写入关闭
if(cnt == 2)
{
close(pipefd[1]);
break;
}
}
exit(0);
}
// 父进程
close(pipefd[1]);
// 读取
char buffer[MAX];
while(true)
{
sleep(4);
ssize_t n = read(pipefd[0], buffer, sizeof(buffer)-1);
// 当 n == 0 时,代表read已经读到文件结尾了
if(n == 0)
{
cout << "child qiut, read tail" << endl;
break;
}
else if(n > 0)
{
buffer[n] = 0; // '\0', 当作字符串
cout << getpid() << ": " << "child say: " << buffer << " to me!" << endl;
}
}
读端关闭代码示例:(C++):
// 父进程
close(pipefd[1]);
// 读取
char buffer[MAX];
while(true)
{
//sleep(4);
ssize_t n = read(pipefd[0], buffer, sizeof(buffer)-1);
if(n == 0)
{
cout << "child qiut, read tail" << endl;
break;
}
else if(n > 0)
{
buffer[n] = 0; // '\0', 当作字符串
cout << getpid() << ": " << "child say: " << buffer << " to me!" << endl;
}
cout << "father return val(n)" << n << endl;
sleep(1);
// 打印一次后,我们退出循环
break;
}
// 关闭 pipefd[0],停止读取
cout << "close point read" << endl;
close(pipefd[0]);
sleep(3);
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if(rid == id)
{
cout << "wait seccess, exit sig: " << (status&0x7f) << endl;
}
🎩管道的特性
在了解完管道的这些情况和特征后,我们可以利用管道来写一个简单的线程池
线程池代码链接
📖5. 总结
但Linux提供的进程间通信机制远不止于此。命名管道、消息队列、共享内存、信号量以及套接字等多种IPC方式,各自拥有独特的优势和适用场景。在未来的学习与实践中,我们可以继续深入探索这些机制,以更加灵活多样的方式实现进程间的协同工作
让我们以更加饱满的热情和坚定的信心,继续前行在Linux系统编程的学习之路上!
希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!
谢谢大家支持本篇到这里就结束了,祝大家天天开心!