Linux系统编程---进程间管道通信
1、进程间通信
在前面几篇笔记博客文章中,主要是对Linux系统编程中多进程常间的基础知识梳理。从这篇文章开始,将对Linux系统编程的进程间通信知识进行梳理,理清知识脉络。首先会讲解Linux的管道通信(pipe),就是本篇讲解的匿名管道和有名管道通信,接着是信号(signal)部分,然后信号量与PV操作,消息队列(queue),共享内存等。
起初是想把Linux系统编程的内容全部整合到一篇笔记文章中,但是考虑到内容比较多,加上在后面学到的东西越来越多时,容易遗忘,当需要翻阅笔记时,不仅不方便关注的网友参考学习,也不方便自己去复习。因此分篇梳理,在后面一看文章题目,就能确定是否是自己需要的内容。
有需要的网友,可以查阅我的系统编程专栏参考交流:
系统编程_奔跑的蜗牛!的博客-CSDN博客https://blog.csdn.net/weixin_49337111/category_12952742.html?spm=1001.2014.3001.5482
什么是进程间通信,有什么作用?
在Linux系统中,进程间通信(Inter-Process Communication, IPC)是不同进程之间进行数据交换和同步的过程。在Linux中提供了多种机制来实现进程间的通信:管道(Pipe)、信号(Signals)、消息队列(Message Queues)、共享内存(Shared Memory)、信号量(Semaphores)、套接字(Sockets)。
为了更加直观了理解进程间通信,举个简单的例子:
./execute -> 开启了一个名字叫execute的进程。
./control -> 开启了一个名字叫control的进程。
通过进程之间的通信,使得不同的进程之间能够实现数据的交换,上面的control进程发送数据给execute进程,execute进程收到数据之后,就可以根据数据做出相应的事情。(control进程控制execute进程)
通过上面对IPC介绍,或多或少都对进程间通信有一定的了解了,不至于听到就头大,一头雾水,接下来部分开始介绍这篇博客文章的重点内容,管道通信。
2、匿名管道(PIPE)
2.1、什么是匿名管道
匿名管道,又称为无名管道,它只能作用于具有亲缘关系的进程之间的通信,例如父子进程间。无名管道就是一个没有名字的管道文件,相当于一个队列结构,fd[1]为写入端(入队),fd[0]为读出端(出队)。无名管道中的信息读出后即删除,再次读取时即为下一个信息。
不管是匿名管道还是有名管道,在Linux系统下都属于文件的范畴,区别是匿名管道没有名称,因此无法使用open创建或打开,事实上匿名管道有自己独特的创建接口(函数),但其读写方式与普通的文件一样,支持read()/write()操作。
管道文件事实上还包括网络编程中的核心概念套接字,所谓的管道指的是这些文件不能进行“定位”,只能顺序对其读写数据,就像一根水管,拧开水龙头不断读取,就可以源源不断读到水管中的数据,但如果没有水出来那只能继续等待,不能试图“跳过”部分文件去读写水管的中间地带,这是管道的最基本的特性。
在Linux命令行中,管道使用竖线 | 连接多个命令,这被称为管道符,通过管道符创建的匿名管道。
$ command1 | command2
上面这条命令的功能是将前一个命令(command1)的输出,作为后一个命令(command2)的输入。
2.2、匿名管道API
//函数原型:
#include <unistd.h>int pipe(int pipefd[2]); //执行这个函数之后,得到两个文件描述符 --> int *pipefd//函数功能:
创建一个无名管道文件//参数说明:
pipefd:一个具有2个int类型变量的数组。//返回值:
成功:0
失败:-1
说明:
1、pipefd[0] -> 读端 pipefd[1] -> 写端
2、没有名字,因此无法使用 open( )。
3、只能用于亲缘进程间(比如父子进程、兄弟进程、祖孙进程……)通信,因为他只能在一个进程中被创建出来,然后通过继承的方式将他的文件描述符传递给子进程。
4、半双工工作方式:读写端分开。
总结一句话,匿名管道适用于一对一的、具有亲缘关系的进程间的通信。
2.3、匿名管道程序示例
实现使用无名管道,实现父子进程间管道通信。
#include<stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int main(int argc, char **argv)
{int fd[2];//创建一个无名管道文件 ,创建成功之后 fd[0] 读端 fd[1] 写端pipe(fd); //注意,不可以使用open打开无名管道,因为它没有名字,创建的时候默认已经打开了printf("fd[0]:%d fd[1]:%d\n",fd[0],fd[1]);//创建一个子进程 ,实现父进程与子进程的通信pid_t id = fork();if(id == -1)//出错{printf("fork error\n");return -1;}else if(id >0)//父进程{printf("[%d]我是父进程...\n", getpid());sleep(1);//给子进程发送数据,也就是说将数据写入 fd[1] 管道中printf("[%d]父进程,给子进程发送数据:",getpid()); char sendbuf[1024] ={0};scanf("%s",sendbuf);write(fd[1],sendbuf,strlen(sendbuf));}else if(id == 0)//子进程{printf("[%d]我是子进程...\n", getpid());sleep(1);//接收父进程的信息,也就是说从 读端 管道中 读取数据 fd[0]char recvbuf[1024]={0};read(fd[0],recvbuf,sizeof(recvbuf)); printf("[%d]子进程,接收父进程数据:%s\n",getpid(),recvbuf); }int status; waitpid(-1,&status,WCONTINUED);//阻塞等待子进程的退出return 0;
}
3、有名管道(FIFO)
3.1、 什么是有名管道
有名管道文件就是一个有名字的管道文件。在linux下,所有的进程都是可以看到这个文件,所以有名管道作用范围是整个linux系统下任意的两个进程。
有名管道是跟匿名管道相对而言的,从外在形态上来看,具名管道更接近普通文件,有文件名、可以open打开、支持read()/write()等读写操作。
有名管道通常又被称为FIFO(First In First Out),这其实所有管道的基本特性,那就是放入的数据都是按顺序被读出,即所谓先进先出的逻辑。
当然,管道并不是普通文件,有名管道主要有如下特性:
(1)与PIPE一样不支持定位操作lseek( )
(2)与PIPE一样秉持相同的管道读写特性
(3)使用专门的接口来创建:mkfifo( )(匿名管道是pipe( ))
(4)在文件系统中有对应节点,支持使用 open( ) 打开管道(匿名管道不具备)
(5)支持多路同时写入(匿名管道不具备)
3.2、有名管道API
//函数原型
#include <sys/types.h>
#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);//函数功能:
创建一个有名管道文件。//参数说明:
pathname --> 需要创建的管道文件的路径名(不建议在linux虚拟机的共享路径下创建)
mode --> 创建管道文件的初始权限,如0777//返回值:
成功 返回0
失败 返回 -1 ,并设置错误码
说明:
(1)pathname是有名管道的名称,如果要新建的管道文件,需要保证创建的路径位于Linux系统内,如果在虚拟机中操作的时候,不可将管道文件创建在共享文件夹中,因为共享文件夹是windows系统,并不支持管道文件。建议创建在 /tmp 下, 因为管道文件是一个系统级别的资源文件,不会因为进程退出而消失,需要手动删除,因此在TMP下系统重启后会自动清除。
(2)mode是文件权限模式,例如0666,注意权限须为八进制,且实际管道的权限还受系统 umask 的影响。
3.3、有名管道程序示例
创建有名管道文件,并实现两个进程的通信。
(1)、进程1:写入管道数据
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>//注意 有名管道文件 不能在共享文件下 创建
#define FIFO_FILE "/home/Snail/Desktop/process/fifo"// ./fifo_w -----> 发送数据 "hello" ----> 管道文件int main(int argc, char*argv[])
{//判断管道文件是否存在,如果存在,则不再创建if(access(FIFO_FILE, F_OK) == -1) //文件不存在则返回 -1{//1、先创建一个有名管道文件,注意 仅仅是创建 ,mkfifo没有 打开功能int ret = mkfifo(FIFO_FILE, 0777);if(ret == -1){perror("mkfifo error");return -1;}}//2、打开有名管道文件int fd = open(FIFO_FILE, O_RDWR);if(fd == -1){perror("open fifo error");return -1;}while(1){printf("请输入你要发送的数据:");char sendbuf[1024] = {0};scanf("%s",sendbuf);//3、往 有名管道文件 写入数据 write(fd, sendbuf, strlen(sendbuf));}//4、关闭文件close(fd);return 0;
}
(2)、进程2:读取管道数据
#include<stdio.h>
#include <unistd.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>//注意 有名管道文件 不能在共享文件下 创建
#define FIFO_FILE "/home/Snail/Desktop/process/fifo"//./fifo_r 读取数据 从 管道文件中 int main(int argc, char*argv[])
{//先判断管道文件是否存在,如果存在,则不需要再创建了if(access(FIFO_FILE, F_OK) == -1) //文件不存在则返回 -1{//1、先创建一个有名管道文件,注意 仅仅是创建 ,mkfifo没有 打开功能int ret = mkfifo(FIFO_FILE,0777);if(ret == -1){perror("mkfifo error");return -1;}}//2、打开有名管道文件int fd = open(FIFO_FILE,O_RDWR);if(fd == -1){perror("open fifo error");return -1;}//3、从管道文件中读取数据//如果没有数据,则会阻塞 等待 管道文件中有数据,才会读取while(1){char recvbuf[1024]= {0};read(fd,recvbuf,sizeof(recvbuf));printf("recvbuf:%s\n",recvbuf);}//4、关闭文件close(fd);return 0;
}
在目标路径下,可以找到创建的有名管道文件。
4、总结
①、管道是创建在内存中,进程结束空间释放,管道不复存在。
②、无名管道和有名管道都是半双工通信,实现双向通信需要建立两个管道。
③、无名管道是linux特殊文件。
④、无名管道只用于父子进程之间,有名管道可用于任意两个进程之间。