Linux进程间通信(一) - 管道
管道(pipe)
普通的Linux shell都允许重定向,而重定向使用的就是管道。
例如:ps | grep vsftpd .管道是单向的、先进先出的、无结构的、固定大小的字节流,它把一个进程的标准输出和另一个进程的标准输入连接在一起。写进程在管道的尾端写入数据,读进程在管道的头端读出数据。数据读出后将从管道中移走,其它读进程都不能再读到这些数据。管道提供了简单的流控制机制。管道主要用于不同进程间通信。
可以通过打开两个管道来创建一个双向的管道。但需要在子进程中正确地设置文件描述符。必须在系统调用fork()前调用pipe(),否则子进程将不会继承文件描述符。当使用半双工管道时,任何关联的进程都必须共享一个相关的祖先进程。因为管道存在于系统内核之中,所以任何不在创建管道的进程的祖先进程之中的进程都将无法寻址它。而在命名管道中却不是这样。
相关函数:
//打开一个管道,2个int的数组fildes分别存储读端和写端的FD
Int pipe(int fildes[2]);
//管道读
ssize_t read(int fd, void* buf, size_t count);
//管道写
ssize_t write(int fd, const void* buf, size_t count);
代码说明:子进程写,父进程读
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include <string.h>
#define READFD 0
#define WRITEFD 1
int main(int argc, char *argv[])
{
int pipe_fd[2];
pid_t pid;
if (pipe(pipe_fd) < 0)
{
printf("pipe create errorn");
exit(1);
}
if ((pid = fork()) < 0 )
{
printf("fork errorn");
exit(1);
}
// parent
if (pid > 0)
{
char buf_r[100];
memset(buf_r, 0, sizeof(buf_r));
close(pipe_fd[WRITEFD]);
sleep(2);
while (read(pipe_fd[0], buf_r, 100))
{
printf("print from parent ==> %sn", buf_r);
}
close(pipe_fd[READFD]);
exit(0);
}
else if (pid == 0)
{
close(pipe_fd[READFD]);
write(pipe_fd[WRITEFD], "Hello", 5);
write(pipe_fd[WRITEFD], " Pipe", 5);
close(pipe_fd[WRITEFD]);
exit(0);
}
exit(0);
}
结果说明:
[root@rocket ipc]# g++ -g -o ipc_pipe ipc_pipe.cpp
[root@rocket ipc]# ./ipc_pipe
print from parent ==> Hello Pipe
命名管道(FIFO)
命名管道也被称为FIFO文件,它是一种特殊类型的文件,它在文件系统中以文件名的形式存在,但是它的行为却和之前所讲的匿名管道(pipe)类似。
由于Linux中所有的事物都可被视为文件,所以对命名管道的使用也就变得与文件操作非常的统一,也使它的使用非常方便,同时我们也可以像平常的文件名一样在命令中使用。
相关函数:
//创建命名管道
int mkfifo(const char *filename, mode_t mode);
观察命名管道:
[root@rocket tmp]# file my_fifo
my_fifo: fifo (named pipe)
可以看出,命名管道是一种特殊的文件,可以按照文件的读写方式去操作。
访问命名管道
打开FIFO文件
与打开其他文件一样,FIFO文件也可以使用open调用来打开。注意,mkfifo函数只是创建一个FIFO文件,要使用命名管道还是要调用open将其打开。
有两点要注意:
1、就是程序不能以O_RDWR模式打开FIFO文件进行读写操作,而其行为也未明确定义,因为如一个管道以读/写方式打开,进程就会读回自己的输出,我们通常使用FIFO只是为了单向的数据传递。
2、就是传递给open调用的是FIFO的路径名,而不是正常的文件。
打开FIFO文件通常有四种方式,
open(const char *path, O_RDONLY);
open(const char *path, O_RDONLY | O_NONBLOCK);
open(const char *path, O_WRONLY);
open(const char *path, O_WRONLY | O_NONBLOCK);
在open函数的调用的第二个参数中,你看到选项O_NONBLOCK,选项O_NONBLOCK表示非阻塞,加上这个选项后,表示open调用是非阻塞的,如果没有这个选项,则表示open调用是阻塞的。
open调用的阻塞是什么一回事呢?很简单,对于以只读方式(O_RDONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_RDONLY),除非有一个进程以写方式打开同一个FIFO,否则它不会返回;如果open调用是非阻塞的(即第二个参数为O_RDONLY | O_NONBLOCK),则即使没有其他进程以写方式打开同一个FIFO文件,open调用将成功并立即返回。
对于以只写方式(O_WRONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_WRONLY),open调用将被阻塞,直到有一个进程以只读方式打开同一个FIFO文件为止;如果open调用是非阻塞的(即第二个参数为O_WRONLY | O_NONBLOCK),open总会立即返回,但如果没有其他进程以只读方式打开同一个FIFO文件,open调用将返回-1,并且FIFO也不会被打开。
代码说明:fifo读写进程
写进程
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char** argv)
{
const char *fifo_name = "/tmp/my_fifo";
int pipe_fd = -1;
int res = 0;
const int open_mode = O_WRONLY;
if(access(fifo_name, F_OK) == -1)
{
//管道文件不存在
//创建命名管道
res = mkfifo(fifo_name, 0777);
if(res != 0)
{
fprintf(stderr, "Could not create fifo %sn", fifo_name);
exit(EXIT_FAILURE);
}
}
printf("Process %d opening FIFO O_WRONLYn", getpid());
//以只写阻塞方式打开FIFO文件,以只读方式打开数据文件
pipe_fd = open(fifo_name, open_mode);
printf("Process %d result %dn", getpid(), pipe_fd);
if(pipe_fd != -1)
{
write(pipe_fd, "hello", 5);
write(pipe_fd, " fifo", 5);
printf("Process write finishedn", getpid());
close(pipe_fd);
}
else
{
exit(EXIT_FAILURE);
}
printf("Process %d finishedn", getpid());
exit(EXIT_SUCCESS);
}
读进程
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#include <string.h>
int main(int argc, char** argv)
{
const char *fifo_name = "/tmp/my_fifo";
int pipe_fd = -1;
int res = 0;
int open_mode = O_RDONLY;
char buffer[PIPE_BUF + 1];
int bytes_read = 0;
//清空缓冲数组
memset(buffer, ' ', sizeof(buffer));
if(access(fifo_name, F_OK) == -1)
{
//管道文件不存在
//创建命名管道
res = mkfifo(fifo_name, 0777);
if(res != 0)
{
fprintf(stderr, "Could not create fifo %sn", fifo_name);
exit(EXIT_FAILURE);
}
}
printf("Process %d opening FIFO O_RDONLYn", getpid());
//以只读阻塞方式打开管道文件,注意与fifowrite.c文件中的FIFO同名
pipe_fd = open(fifo_name, open_mode);
printf("Process %d result %dn",getpid(), pipe_fd);
sleep(3); //这里sleep一下,先等写进程写数据
if (pipe_fd != -1)
{
//读取FIFO中的数据
res = read(pipe_fd, buffer, PIPE_BUF);
bytes_read += res;
printf("get data %s n", buffer);
close(pipe_fd);
}
else
{
exit(EXIT_FAILURE);
}
printf("Process %d finished, %d bytes readn", getpid(), bytes_read);
exit(EXIT_SUCCESS);
}
结果说明:
[root@rocket ipc]# g++ -g -o ipc_fifo_reader ipc_fifo_reader.cpp
[root@rocket ipc]# g++ -g -o ipc_fifo_writer ipc_fifo_writer.cpp
[root@rocket ipc]# ./ipc_fifo_writer
Process 74640 opening FIFO O_WRONLY
// 这里会阻塞住,直到ipc_fifo_reader调用open打开同一个fifo
Process 74640 result 3
Process write finished
Process 74640 finished
[root@rocket ipc]# ./ipc_fifo_reader
Process 74639 opening FIFO O_RDONLY
Process 74639 result 3
get data hello fifo
Process 74639 finished, 10 bytes read
两个程序都使用阻塞模式的FIFO,为了让大家更清楚地看清楚阻塞究竟是怎么一回事,首先我们运行./ipc_fifo_writer,发现其阻塞在open调用,直到./ipc_fifo_reader调用open,而./ipc_fifo_reader的open调用虽然也是阻塞模式,但是./ipc_fifo_writer早已运行,即早有另一个进程以写方式打开同一个FIFO,所以open调用立即返回。
- golang讲解(go语言)标准库分析之strings结束篇
- golang(GO语言)http详解简单基础
- 操作系统几种I/O访问类型以及异步I/O性能评估
- 用golang写的golang解析json数据的包
- 神奇的go语言(网页下载)
- 关于MySQL极限值的初步验证纠错
- 神奇的go语言(开始篇)
- GPFS 3.4 的配置变更与性能评估
- 神奇的go语言(面向对象)
- 机器学习玩转Flappy Bird全书:六大“流派”从原理到代码
- 神奇的go语言(高级应用)
- HTML5 游戏引擎深度测评
- PowerVM 环境下实施 PowerHA7.1 要点
- Oculus + Node.js + Three.js 打造VR世界
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Redis系列(十二)scan Info Object等特殊命令集合
- 使用纯CSS给网站文章中的外链添加小图标
- iOS美团同款"ZSource"二进制调试实现
- 使用picocm来进行Linux下的串口调试
- vue-drawer-layout实现手势滑出菜单栏
- iOS面试之UI大全
- 基于Ant Design Vue封装一个表单控件
- Rust闭包的虫洞穿梭
- TS 设计模式08 - 发布订阅模式
- GrowingIO 数据采集 iOS SDK 测试实践
- Kubernetes 1.19.0——Pod(2)
- 极速40分钟写出SQLite数据展示与导出功能【技术创作101训练营】
- opencv cudacodec VideoReader 报错
- Flutter中富文件标签的解决方案
- JDK 15已发布,你所要知道的都在这里!