其实开心好简单 最紧要好玩
进程通信方式之管道
进程间通信方式
管道——无名管道/匿名管道
无名管道相关API
int pipe(int pipefd[2]);//创建无名管道pipe
管道——有名管道
有名管道API
进程间通信方式
- 管道
- 无名管道 pipe:适用于亲缘关系进程间的、一对一的通信
- 有名管道 fifo :适用于任何进程间的一对一、多对一的通信
- 套接字 socket:适用于跨网络的进程间通信
- 信号:异步通信方式
- system-V IPC对象
- 共享内存:效率最高的通信方式
- 消息队列:相当于带标签的增强版管道
- 信号量
- 信号量组:参数复杂,功能强大到臃肿
- POSIX有名信号量:适用于多进程,参数简单,接口明晰,老少咸宜
- POSIX无名信号量:适用于多线程,参数简单,接口明晰,童叟无欺
管道——无名管道/匿名管道
无名管道属性:
- 它只能作用于亲缘进程(匿名管道的创建他使用pipe创建,诞生出两个操作管道读写端的文件描述符,只有创建子进程的时候才会被继承过去,无法通过别的方式打开同一个管道)
- 半双工通信(在读的时候不能写入,在写入的时候不能读取)
- 操作无原子性
- 无法用lseek定位操作位置
无名管道相关API
int pipe(int pipefd[2]);//创建无名管道pipe
#include <unistd.h> int pipe(int pipefd[2]);
函数功能:
- 随机创建一个匿名管道,并且返回管道的两个通信文件描述符给传入的数组
函数参数:
- Pipefd:这是一个两个int型元素的数组,管道创建成功后会将读端文件描述符放到第0个元素中,将写端文件描述符放到第1个元素中
返回值:
- 成功返回0,失败返回-1
注意:
- pipe 拥有两个文件描述符,一个专用于读fd[0],一个专用于写fd[1]
- 创建出来的 pipe 的描述符,只能通过子进程继承的方式传递给别的进程,因此只能用于亲缘进程间的通信,其他非亲缘进程无法获取 pipe 的描述符。
- 管道打开时,必须同时有读者和写者,否则 open 会阻塞
- 不能有多个进程同时对 pipe 进行写操作,否则数据有可能被覆盖
- 总结: pipe 适用于一对一的、具有亲缘关系的进程间的通信。
例程
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>int main(void)
{int pipe_fd[2];//管道文件描述符int retval;//管道返回值pid_t cpid_a, cpid_b;//子进程a、b的PIDchar buffer[1024];//ssize_t rd_ret,wr_ret;//读写返回值//创建一个无名管道//并且往pipe_fd数组当中的0元素填充管道的读端文件描述符//往1元素填充管道写端文件描述符retval = pipe(pipe_fd);if(retval == -1){perror("create pipe error\n");return -1;}bzero(buffer, sizeof(buffer));//清零cpid_a = fork();//创建子进程aif(cpid_a == 0){//往管道写入数据wr_ret = write(pipe_fd[1], "hello", strlen("hello"));if(wr_ret == -1){perror("A进程写入失败\n");exit(EXIT_FAILURE);}exit(EXIT_SUCCESS);}cpid_b = fork();//创建子进程bif(cpid_b == 0){//读取管道的数据rd_ret = read(pipe_fd[0], buffer, sizeof(buffer));if(rd_ret == -1){perror("子进程读取失败\n");exit(EXIT_FAILURE);}printf("B子进程读取到=%s\n", buffer);//往管道写入数据wr_ret = write(pipe_fd[1], "hello world", strlen("hello world"));if(wr_ret == -1){perror("B进程写入失败\n");exit(EXIT_FAILURE);}exit(EXIT_SUCCESS);}//等待子进程子进程a、b结束,回收资源waitpid(cpid_a, NULL, 0);waitpid(cpid_b, NULL, 0);//读取管道的数据rd_ret = read(pipe_fd[0], buffer, sizeof(buffer));if(rd_ret == -1){perror("子进程读取失败\n");exit(EXIT_FAILURE);}printf("父进程读取到=%s\n", buffer);close(pipe_fd[0]);close(pipe_fd[1]);return 0;
}
管道文件的读写特性:
|
有写者 |
没有写者 |
||
有数据 |
无数据 |
有数据 |
无数据 |
|
读操作 |
正常读取 |
阻塞 |
正常读取 |
立即返回0 |
|
有读者 |
没有读者 |
||
缓冲区未满 |
缓冲区已满 |
缓冲区未满 |
缓冲区已满 |
|
写操作 |
正常写入 |
阻塞 |
立即被系统发出的SIGPIPE杀死 |
|
备注:
- 读者 —— 对管道拥有读权限的进程
- 写者 —— 对管道拥有写权限的进程
应用场景:
- 根据调用第三方程序的特点,文件描述符会被继承过去第三方程序中,如果你的第三方程序想要引用也是可以的(不方便)(不具备第三方调度程序的实用性)
- 守护进程的特点是将进程变成一个后台服务,并非同步运行多个进程(不具备引用匿名管道的实用性)
- 灵活使用:管道的特点在于他本身拥有读写阻塞特性,你可以利用这一个特性制造出属于自己的特殊的缓冲区(数据的同步互斥特性)
管道——有名管道
有名管道属性:
- 他能够作用无任何亲缘关系的进程
- 全双工通信
- 操作有原子性
- 无法用lseek定位操作位置
有名管道API
int mkfifo(const char *pathname, mode_t mode);//创建有名管道文件
#include <sys/types.h>
#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);
函数功能:
- 创建一个有名管道文件
函数参数:
- pathname:创建的管道文件名字(可以附加路径)(注意:一定得是linux下面的文件系统才能够创建出来,禁止使用windows的共享文件夹)
- mode:创建出来的文件的权限值(参照open函数的mode参数)
返回值:
- 成功返回0,失败返回-1
应用场景:
- 因为其操作具备原子性,经常拿过来作为一个多入口缓冲区
- 进程间通信
读端fifo_read.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <strings.h>#define FIFO_NAME "myfifo" //管道文件名(可加路径)int main(void)
{int retval;int fifo_fd;//有名管道描述符ssize_t rd_ret;char buffer[1024];//判断FIFO_NAME代表的文件是否存在,不存在则进入判断语句中if(access( FIFO_NAME, F_OK) != 0){//创建一个有名管道文件//FIFO_NAME:代表文件名,0644:代表创建文件的权限mkfifo(FIFO_NAME, 0644);}//打开管道fifo_fd = open(FIFO_NAME, O_RDWR);if(fifo_fd == -1){perror("打开管道出错\n");return -1;}while(1){bzero(buffer, sizeof(buffer));//读取管道内容rd_ret = read(fifo_fd, buffer, sizeof(buffer));if(rd_ret == -1){perror("读取管道数据异常");break;}printf("读到大小为%ld,内容为 %s 的数据\n", rd_ret, buffer);}close(fifo_fd);return 0;
}
写段fifo_write.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>#define FIFO_NAME "myfifo" //管道文件名(可加路径)int main(void)
{int retval;int fifo_fd;//有名管道描述符ssize_t wr_ret;char buffer[1024];//判断FIFO_NAME代表的文件是否存在,不存在则进入判断语句中if(access( FIFO_NAME, F_OK) != 0){//创建一个有名管道文件FIFO_NAME:代表文件名,0644:代表创建文件的权限mkfifo(FIFO_NAME, 0644);}fifo_fd = open(FIFO_NAME, O_RDWR);if(fifo_fd == -1){perror("打开管道出错\n");return -1;}while(1){//获取用户输入fgets(buffer, sizeof(buffer), stdin);//向管道写入数据wr_ret = write(fifo_fd, buffer, strlen(buffer));if(wr_ret == -1){perror("写入管道数据异常");break;}}close(fifo_fd);return 0;
}
int access(const char *pathname, int mode);//检查指定文件权限或是否存在
#include <unistd.h>int access(const char *pathname, int mode);
函数功能:
- 检查指定文件的权限(存在与否)
函数参数:
- 指定查的文件名(可以附加路径)
- mode:代表检查的权限
- R_OK:判断是否可读
- W_OK:判断是否可写
- X_OK:判断是否可执行
- F_OK:判断文件是否存在
返回值:
- 如果权限可以,成功返回0,失败返回-1