【进程间通信】管道

  • (꒪ꇴ꒪ ),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]:写端,只能进行写操作。

【进程间通信】管道_第1张图片

2.创建无名管道

#include 
int pipe(int pipefd[2]);
参数:pipefd 一个至少具有 2 个 int 型数据的数组,用来存放 PIPE 的读写端描述符

3.示例

        创建一个子进程,实现键盘输入要发送的消息,父进程给子进程发消息,并且能够循环发送,子进程接收消息并显示。

        代码:

#include 
#include 
#include 

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
#include
int mkfifo(const char *pathname, mode_t mode);
参数:
    pathname FIFO 的文件名
    mode 文件权限

3.示例

        设计两个进程,进程间通过有名管道进行通信,两个进程都既可以写数据,也可读数据,当写入的数据是“quit”的时候,写进程关闭;读进程收到“quit”,也关闭。

【进程间通信】管道_第2张图片

【进程间通信】管道_第3张图片

         完整代码:

jack.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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 
#include 
#include 
#include 
#include 
#include 
#include 

#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方式。常见的应用呢就是写入日志文件,我们该问题进行一个简化就可得到如下的图示:【进程间通信】管道_第4张图片

        完成这个任务程序也很简单,我们需要一个总的日志文件保存程序和五个发送日子程序,这样我们就能模拟出日志文件的产出和写入,像这样:

【进程间通信】管道_第5张图片

        此外,在使用管道进行进程间通信需要注意以下几点:

        (1) 管道是基于字节流的,因此数据传输不会被分段;
        (2)管道的写入和读取操作是阻塞的,可能需要额外的机制来处理阻塞情况;
        (3) 管道通常用于在具有亲缘关系的进程(例如父子进程)之间进行通信,或者在需要共享数据的进程之间进行通信。

        更多C/C++语言Linux系统数据结构ARM板实战相关文章,关注专栏:

   手撕C语言

            玩转linux

                    脚踢数据结构

                            系统、网络编程

                                     探索C++

                                             6818(ARM)开发板实战

写在最后

  • 今天的分享就到这啦~
  • 觉得博主写的还不错的烦劳 一键三连喔~
  • 感谢关注

你可能感兴趣的:(系统编程,linux,网络,运维,开发语言,网络协议,嵌入式实时数据库)