Linux进程间通信---管道(pipe)
概述
管道又称无名管道、匿名管道,是被所有UNIX like系统支持的古老通信方式。
管道是单向字节流,在Linux中管道是通过指向同一个临时的VFS inode的两个file数据结构来实现的,此VFS inode指向内存中的同一个物理页面。这就隐藏了读写管道和读写普通文件的差别。管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,单独构成一种文件系统,并且只存在与内存中。数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
管道在应用程序中体现为2个打开的文件描述符:
+--------------------+ +------------------------------+ +--------------------+
| | | | | |
| | | +-----------------+ | | |
| | | | | | | |
| fd[0]<------------+ Pipe +<--------------+fd[1] |
| | | +-----------------+ | | |
| | | | | |
+--------------------+ +------------------------------+ +--------------------+
User Application Kernel User Application
特点
- 只有具有亲缘关系的进程才可以通过管道进行通信;
- 半双工,同一时刻,数据只能往一个方向流动;
- 写入管道的顺序遵循先入先出(FIFO)原则;
- 从管道读取数据是一次性操作,数据一旦读取,则从管道中删除;
+-----------+ +-----------+ +---------------------+ +-----------+ +-------------+
| | | | | | | | | |
| | | | | +------+ | | | | |
| | w | |Y | | | | R | | Y | |
| Write +---> Writable? +--> Write | Pipe | Read +---> Readable? +---> Write |
|Application| |(not full) | | End | | End | |(not empty)| | Application |
| | | | | +------+ | | | | |
| | | | | | | | | |
+-----------+ +------+----+ +---------------------+ +-----+-----+ +-------------+
| |
N| | N
| |
+------v----+ +-----v-----+
| | | |
| Sleep | | Sleep |
| | | |
+-----------+ +-----------+
相关API
#include <unistd.h>
/* 创建无名管道
* @filedes: 为 int 型数组的首地址,其存放了管道的文件描述符 filedes[0]、filedes[1]。
* 当一个管道建立时,它会创建两个文件描述符 fd[0] 和 fd[1]。其中 fd[0] 固定用于读管道,而 fd[1] 固定用于写管道。
* 一般文件 I/O 的函数都可以用来操作管道( lseek() 除外)。
* @return 成功:0,失败:-1 */
int pipe(int filedes[2]);
举例
一般情况: 子进程写、父进程读
一般情况下,在父进程中创建管道,并fork出子进程,这样在父子进程中分别用fd[0]和fd[1]进行读写,例如,父进程中创建fd_pipe[2], 并fork子进程, 子进程向fd[1]中写入“Hello, Father"字符串,父进程中通过fd_pipe[0]读取该字符串并打印。(如果需要同时读写,为避免混乱,应使用2个管道)
#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_pipe[2] = {0};
pid_t pid;
if( pipe(fd_pipe) < 0 ){// 创建无名管道
perror("pipe");
}
pid = fork(); // 创建进程
if( pid < 0 ){ // 出错
perror("fork");
exit(-1);
}
if( pid == 0 ){ // 子进程
char str[] = "Hello, father";
write(fd_pipe[1], str, strlen(str));
printf("[child] write done. \n");
_exit(0);
}else if( pid > 0){// 父进程
wait(NULL); // 等待子进程结束,回收其资源
char str[50] = {0};
printf("[father] before read\n");
read(fd_pipe[0], str, sizeof(str));
printf("[father] after read\n");
printf("[father] str=[%s]\n", str); // 打印数据
}
return 0;
}
管道空: read会阻塞,直到有数据
管道满 : write会阻塞,直到可写
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#define debug(fmt, args...) do { now(); printf(fmt, ##args); } while(0)
char* now()
{
time_t rawtime;
struct tm * timeinfo;
char *t;
time( &rawtime );
timeinfo = localtime( &rawtime );
t = asctime(timeinfo);
t[strlen(t) - 1] = 0;
printf("[%s] ", t);
}
int main(int argc, char *argv[])
{
int fd_pipe[2] = {0};
pid_t pid;
char str[1024] = "";
memset(str, 'a', sizeof(str));
if( pipe(fd_pipe) < 0 ){// 创建无名管道
perror("pipe");
}
pid = fork(); // 创建进程
if( pid < 0 ){ // 出错
perror("fork");
exit(-1);
}
if( pid == 0 ){ // 子进程
int i = 0;
int len = 0;
while(1) {
i++;
len = write(fd_pipe[1], str, sizeof(str)); // 写满后会阻塞
debug("[child] i ===%d. write %d byte\n", i, len);
}
_exit(0);
}else if( pid > 0){// 父进程
char str[2048] = {0};
int len = 0;
while(1) {
sleep(2);
len = read(fd_pipe[0], str, sizeof(str));
debug("[father] read %d byte data\n", len);
}
}
return 0;
}
输出结果:
[Tue Mar 12 17:14:22 2019] [child] i ===1. 1024
[Tue Mar 12 17:14:22 2019] [child] i ===2. 1024
[Tue Mar 12 17:14:22 2019] [child] i ===3. 1024
... ...
[Tue Mar 12 17:14:22 2019] [child] i ===62. 1024
[Tue Mar 12 17:14:22 2019] [child] i ===63. 1024
[Tue Mar 12 17:14:22 2019] [child] i ===64. 1024 // 此处写完后,需要等待3s之后,父进程将数据读出来之后才可以继续写入)
[Tue Mar 12 17:14:24 2019] [father] read 2048
[Tue Mar 12 17:14:26 2019] [father] read 2048
[Tue Mar 12 17:14:26 2019] [child] i ===65. 1024
[Tue Mar 12 17:14:26 2019] [child] i ===66. 1024
[Tue Mar 12 17:14:26 2019] [child] i ===67. 1024
[Tue Mar 12 17:14:26 2019] [child] i ===68. 1024
[Tue Mar 12 17:14:28 2019] [father] read 2048
读端关闭,导致SIGPIPE
当管道的所有读端均关闭时,如果再往里写入数据时会抛出SIGPIPE信号,其默认处理动作是中断当前进程。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <signal.h>
#define debug(fmt, args...) do { now(); printf(fmt, ##args); } while(0)
char* now()
{
time_t rawtime;
struct tm * timeinfo;
char *t;
time( &rawtime );
timeinfo = localtime( &rawtime );
t = asctime(timeinfo);
t[strlen(t) - 1] = 0;
printf("[%s] ", t);
}
static void sig_handler(int signo)
{
debug("[father] get signal %d\n", signo);
}
int main(int argc, char *argv[])
{
int fd_pipe[2] = {0};
pid_t pid;
char str[16] = "";
struct sigaction psa;
psa.sa_handler = sig_handler;
sigaction(SIGPIPE, &psa, NULL);
memset(str, 'a', sizeof(str));
if( pipe(fd_pipe) < 0 ){// 创建无名管道
perror("pipe");
}
pid = fork(); // 创建进程
if( pid < 0 ){ // 出错
perror("fork");
exit(-1);
}
if( pid == 0 ){ // 子进程
sleep(3);
close(fd_pipe[0]); // 主动关闭读端
sleep(100);
_exit(0);
} else if ( pid > 0){// 父进程
int len = 0;
close(fd_pipe[0]);
while(1) {
len = write(fd_pipe[1], str, sizeof(str));
// 当所有读端都关闭后,才会抛出SIGPIPE信号
sleep(1);
debug("[father] write %d byte data\n", len);
}
}
return 0;
}
输出结果:3秒后,子进程关闭读端,写入错误,抛出SIGPIPE信号。
[Tue Mar 12 18:29:27 2019] [father] write 16 byte data
[Tue Mar 12 18:29:28 2019] [father] write 16 byte data
[Tue Mar 12 18:29:29 2019] [father] write 16 byte data
[Tue Mar 12 18:29:29 2019] [father] get signal 13
[Tue Mar 12 18:29:30 2019] [father] write -1 byte data
[Tue Mar 12 18:29:30 2019] [father] get signal 13
[Tue Mar 12 18:29:31 2019] [father] write -1 byte data
[Tue Mar 12 18:29:31 2019] [father] get signal 13
非阻塞方式使用PIPE
在非阻塞模式下,write/read函数会直接返回-1, 而不是阻塞等待。
fcntl(fd, F_SETFL, 0); // 设置为阻塞模式, 默认情况
fcntl(fd, F_SETFL, O_NONBLOCK); // 设置为非阻塞模式
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <fcntl.h>
#define debug(fmt, args...) do { now(); printf(fmt, ##args); } while(0)
char* now()
{
time_t rawtime;
struct tm * timeinfo;
char *t;
time( &rawtime );
timeinfo = localtime( &rawtime );
t = asctime(timeinfo);
t[strlen(t) - 1] = 0;
printf("[%s] ", t);
}
int main(int argc, char *argv[])
{
int fd_pipe[2] = {0};
pid_t pid;
char str[1024] = "";
memset(str, 'a', sizeof(str));
if( pipe(fd_pipe) < 0 ){// 创建无名管道
perror("pipe");
}
// 设置读写均为非阻塞
fcntl(fd_pipe[0], F_SETFL, O_NONBLOCK);
fcntl(fd_pipe[1], F_SETFL, O_NONBLOCK);
pid = fork(); // 创建进程
if( pid < 0 ){ // 出错
perror("fork");
exit(-1);
}
if( pid == 0 ){ // 子进程
int i = 0;
int len = 0;
while(1) {
i++;
len = write(fd_pipe[1], str, sizeof(str));
debug("[child] i ===%d. write %d byte\n", i, len);
sleep(3); // 确保写入速度低
}
_exit(0);
}else if( pid > 0){// 父进程
char str[2048] = {0};
int len = 0;
while(1) {
// 非阻塞,len为-1
len = read(fd_pipe[0], str, sizeof(str));
debug("[father] read %d byte data\n", len);
sleep(1);
}
}
return 0;
}
输出结果:
每隔3s,才能读出1024字节数据,其他时候返回-1, 并不会阻塞。
[Tue Mar 12 18:38:46 2019] [father] read -1 byte data
[Tue Mar 12 18:38:46 2019] [child] i ===1. write 1024 byte
[Tue Mar 12 18:38:47 2019] [father] read 1024 byte data
[Tue Mar 12 18:38:48 2019] [father] read -1 byte data
[Tue Mar 12 18:38:49 2019] [child] i ===2. write 1024 byte
[Tue Mar 12 18:38:49 2019] [father] read 1024 byte data
[Tue Mar 12 18:38:50 2019] [father] read -1 byte data
[Tue Mar 12 18:38:51 2019] [father] read -1 byte data
[Tue Mar 12 18:38:52 2019] [child] i ===3. write 1024 byte