- (꒪ꇴ꒪ ),Hello我是祐言QAQ
- 我的博客主页:C/C++语言,数据结构,Linux基础,ARM开发板,网络编程等领域UP🌍
- 快上🚘,一起学习,让我们成为一个强大的攻城狮!
- 送给自己和读者的一句鸡汤🤔:集中起来的意志可以击穿顽石!
- 作者水平很有限,如果发现错误,请在评论区指正,感谢🙏
进程间通信(IPC)是操作系统中用于不同进程之间交换数据和信息的一种机制。
当谈到进程间通信时,管道(Pipe)是一种常见且简单的通信方式。管道主要用于在两个相关进程之间传递数据,一个进程充当数据的发送者,而另一个进程充当数据的接收者。它也可以在父进程和子进程之间创建,或者在同一台机器上的不同进程之间创建。数据在管道中按照先进先出的顺序进行传递。
管道有两种类型:无名管道(Anonymous Pipe)和命名管道(Named Pipe,也称为FIFO)。下面就让我们来详细了解一下这两种管道。
一、无名管道(Anonymous Pipe)
1.概述
无名管道只能用于亲缘进程间(比如父子进程、兄弟进程、祖孙进程等)创建的一种通信方式。写入操作不具有写入原子性,因此只能用于一对一的简单通信情形。无名管道只能在创建进程时使用,无法在其他无关进程之间使用。此外还具有以下特点:
(1)单向性:数据只能从管道的写入端流向读取端;
(2)缓冲区:无名管道通常具有有限的缓冲区,用于存储在写入时尚未读取的数据;
(3)阻塞:如果管道已满(写入端写入速度过快),写入操作可能会阻塞,直到有足够的空间来容纳数据;
(4)关闭:一旦所有引用管道的进程都关闭了管道,数据就会被清空;
(5)没有名字,因此无法使用 open( )函数,也不能使用 lseek( )函数来定位。
还需要注意的是:无名管道的读写端是固定的。
fd[0] :读端,只能进行读操作;
fd[1]:写端,只能进行写操作。
2.创建无名管道
#include <unistd.h>
int pipe(int pipefd[2]);
参数:pipefd 一个至少具有 2 个 int 型数据的数组,用来存放 PIPE 的读写端描述符
3.示例
创建一个子进程,实现键盘输入要发送的消息,父进程给子进程发消息,并且能够循环发送,子进程接收消息并显示。
代码:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{
// 创建一条无名管道
int fd[2];
int p = pipe(fd); // 创建管道,fd[0] 是读端,fd[1] 是写端
if (p == -1)
{
perror("pipe fail"); // 输出错误信息
return -1;
}
pid_t x = fork(); // 创建子进程
if (x > 0) // 父进程
{
char w_buf[100];
while(1) // 无限循环
{
printf("请输入要传输的字符串:");
scanf("%s", w_buf); // 从用户输入中读取字符串
write(fd[1], w_buf, strlen(w_buf)+1); // 将字符串写入管道的写端
sleep(1); // 等待1秒钟
}
}
if (x == 0) // 子进程
{
char r_buf[100];
while(1) // 无限循环
{
bzero(r_buf, sizeof(r_buf)); // 清空读缓冲区
read(fd[0], r_buf, sizeof(r_buf)); // 从管道的读端读取数据到缓冲区
printf("father say:%s\n", r_buf); // 打印读取的数据
}
}
return 0;
}
二、命名管道(Named Pipe / FIFO)
1.概述
命名管道是一种更为通用的管道,又称有名管道,可以用于没有亲缘关系的进程之间的通信。与无名管道不同,命名管道可以在系统中存在一段时间,允许多个进程随时通过使用相同的管道名称来进行通信。其特点包括:
(1)可命名性:通过指定一个唯一的名称(FIFO),进程可以随时打开并使用管道;
(2)跨进程通信:不同进程可以通过打开相同名称的管道来进行通信;
(3)非阻塞:命名管道通常可以设置为非阻塞模式,即使管道未就绪,进程仍然可以继续运行;
(4)持久性:命名管道会保留在文件系统中,直到被明确删除;
(5)跟普通文件一样可使用统一的 read( )/write( )来读写。任何具有相应权限的进程都可以使用 open( )函数来获取 FIFO 的文件描述符。
2.创建命名管道
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
参数:
pathname FIFO 的文件名
mode 文件权限
3.示例
设计两个进程,进程间通过有名管道进行通信,两个进程都既可以写数据,也可读数据,当写入的数据是“quit”的时候,写进程关闭;读进程收到“quit”,也关闭。
完整代码:
jack.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#define PATHFIFO1 "/tmp/myfifo1"
#define PATHFIFO2 "/tmp/myfifo2"
int main(int argc, char const *argv[])
{
//创建有名管道,如果已经存在,不创建就行了
if (access(PATHFIFO1, F_OK))
{
int fifo = mkfifo(PATHFIFO1, 0777);
if (fifo == -1)
{
perror("mkfifo");
return -1;
}
}
if (access(PATHFIFO2, F_OK))
{
int fifo = mkfifo(PATHFIFO2, 0777);
if (fifo == -1)
{
perror("mkfifo");
return -1;
}
}
//打开有名管道
int fifo_fd1 = open(PATHFIFO1, O_RDWR);
if (fifo_fd1 == -1)
{
perror("open");
return -1;
}
int fifo_fd2 = open(PATHFIFO2, O_RDWR);
if (fifo_fd2 == -1)
{
perror("open");
return -1;
}
pid_t x = fork();
if (x > 0)
{
char w_buf[100];
while(1)
{
scanf("%[^\n]", w_buf);//%[^\n]:除了'\n'这个字符,我全都要,慎用 %s:接收字符串的时候,不会接收空格
while(getchar()!='\n');
//发送数据
write(fifo_fd1, w_buf, strlen(w_buf));
if (strncmp(w_buf, "quit", 4) == 0)
{
exit(0);
}
}
}
if (x == 0)
{
char r_buf[100];
while(1)
{
bzero(r_buf, sizeof(r_buf));
read(fifo_fd2, r_buf, sizeof(r_buf));
printf("rose:%s\n", r_buf);
if (strncmp(r_buf, "quit", 4) == 0)
{
//自己发个信号杀死父进程
return -1;
}
}
}
return 0;
}
rose.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#define PATHFIFO1 "/tmp/myfifo1"
#define PATHFIFO2 "/tmp/myfifo2"
int main(int argc, char const *argv[])
{
//创建有名管道,如果已经存在,不创建就行了
if (access(PATHFIFO1, F_OK))
{
int fifo = mkfifo(PATHFIFO1, 0777);
if (fifo == -1)
{
perror("mkfifo");
return -1;
}
}
if (access(PATHFIFO2, F_OK))
{
int fifo = mkfifo(PATHFIFO2, 0777);
if (fifo == -1)
{
perror("mkfifo");
return -1;
}
}
//打开有名管道
int fifo_fd1 = open(PATHFIFO1, O_RDWR);
if (fifo_fd1 == -1)
{
perror("open");
return -1;
}
int fifo_fd2 = open(PATHFIFO2, O_RDWR);
if (fifo_fd2 == -1)
{
perror("open");
return -1;
}
pid_t x = fork();
if (x > 0)
{
char w_buf[100];
while(1)
{
scanf("%[^\n]", w_buf);//%[^\n]:除了'\n'这个字符,我全都要,慎用 %s:接收字符串的时候,不会接收空格
while(getchar()!='\n');
//发送数据
write(fifo_fd2, w_buf, strlen(w_buf));
if (strncmp(w_buf, "quit", 4) == 0)
{
exit(0);
}
}
}
if (x == 0)
{
char r_buf[100];
while(1)
{
bzero(r_buf, sizeof(r_buf));
read(fifo_fd1, r_buf, sizeof(r_buf));
printf("jack:%s\n", r_buf);
if (strncmp(r_buf, "quit", 4) == 0)
{
//自己发个信号杀死父进程
return -1;;
}
}
}
return 0;
}
三、总结
总的来说,管道是一种简单且有效的进程间通信方式,特别适合在具有亲缘关系的进程之间传递数据。但对于更复杂的通信需求,可能需要考虑其他IPC方式。常见的应用呢就是写入日志文件,我们该问题进行一个简化就可得到如下的图示:
完成这个任务程序也很简单,我们需要一个总的日志文件保存程序和五个发送日子程序,这样我们就能模拟出日志文件的产出和写入,像这样:
此外,在使用管道进行进程间通信需要注意以下几点:
(1) 管道是基于字节流的,因此数据传输不会被分段;
(2)管道的写入和读取操作是阻塞的,可能需要额外的机制来处理阻塞情况;
(3) 管道通常用于在具有亲缘关系的进程(例如父子进程)之间进行通信,或者在需要共享数据的进程之间进行通信。
更多C/C++语言、Linux系统、数据结构和ARM板实战相关文章,关注专栏:
手撕C语言
玩转linux
脚踢数据结构
系统、网络编程
探索C++
6818(ARM)开发板实战
📢写在最后
- 今天的分享就到这啦~
- 觉得博主写的还不错的烦劳
一键三连喔
~ - 🎉🎉🎉感谢关注🎉🎉🎉