管道是Linux环境中历史最悠久的进程间通信方式,本质上,管道就是一个操作方式为文件的内存缓冲区。Linux上的管道分两种类型,分别为匿名管道(无名管道)和有名管道(命名管道)。
目录
一、匿名管道
匿名管道是一种特殊类型的文件,完全由操作系统管理和维护,因为其存储位置只有亲缘关系的进程知道,所以只能用于亲缘关系的进程之间通信,且其内核资源会在两个通信进程退出后才自动释放。
使用管道进行父子进程间通信的步骤:
- 创建管道:父进程调用pipe()函数创建一个管道,此时,管道的读端和写端都在一个进程之中,没有多大用。
- 父进程通过fork()函数创建一子进程,此时子进程会继承父进程所创建的管道
- 确定管道的传输方向:在父、子进程中根据需要的传输方向关闭无关的读端或写端文件描述符
- 通信:在写进程中调用write()函数,在读进程中调用read()函数
- 关闭管道:调用close()关闭管道相关的文件描述符
1、创建匿名管道
#include <unistd.h>
int pipe(int pipefd[2]);
函数参数
pipefd[2]:创建出两个文件描述符,保存在pipefd中,其中pipefd[0]是读方式打开,作为管道的读描述符;pipefd[1]是写方式打开,作为管道的写描述符。
返回值
成功返回0,失败返回-1
2、注意事项
- 以阻塞方式读管道时:
- 有读进程,无写进程:
- 管道内无数据时,立即返回
- 管道内数据不足,读出所有数据
- 管道内数据充足,读出期望数据
- 有读进程,有写进程:
- 管道内无数据时,读进程阻塞
- 管道内数据不足,读出所有数据
- 管道内数据充足,读出期望数据
- 有读进程,无写进程:
- 以阻塞方式写管道时:
- 有写进程,无读进程:写进程将收到SIGPIPE信号,wirte函数返回-1
- 有写进程,有读进程,且管道内有写空间:写入成功
3、代码示例
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_DATA_LEN 256
#define DELAY_TIME 1
int main()
{
pid_t pid;
int pipe_fd[2];
char buf[MAX_DATA_LEN];
const char data[] = "Pipe Test Program";
int real_read, real_write;
memset((void*)buf, 0, sizeof(buf));
/* 创建管道 */
if (pipe(pipe_fd) < 0)
{
printf("pipe create error\n");
exit(1);
}
if ((pid = fork()) == 0)
{
/* 子进程关闭写描述符,并通过使子进程暂停3s等待父进程已关闭相应的读描述符 */
close(pipe_fd[1]);
sleep(DELAY_TIME * 3);
/* 子进程读取管道内容 */
if ((real_read = read(pipe_fd[0], buf, MAX_DATA_LEN)) > 0)
{
printf("%d bytes read from the pipe is '%s'\n", real_read, buf);
}
/* 关闭子进程读描述符 */
close(pipe_fd[0]);
exit(0);
}
else if (pid > 0)
{
/* 父进程关闭读描述符,并通过使父进程暂停1s等待子进程已关闭相应的写描述符 */
close(pipe_fd[0]);
sleep(DELAY_TIME);
if((real_write = write(pipe_fd[1], data, strlen(data))) != -1)
{
printf("Parent wrote %d bytes : '%s'\n", real_write, data);
}
close(pipe_fd[1]); /*关闭父进程写描述符*/
waitpid(pid, NULL, 0); /*收集子进程退出信息*/
exit(0);
}
return 0;
}
运行结果为:
二、有名管道
有名管道可以在系统中任意两个进程之间进行通信,且创建的管道文件存储在硬盘上,不会随着进程结束而消失。和匿名管道一样,有名管道也只能用于数据的单向传输。但有以下几点需要注意:
- 当进程以写或读的方式打开管道文件,必须有另一个进程以相对应的读或写方式也打开该文件,否则该进程将阻塞在open()位置。
- 若两个进程都已打开,但中途某进程退出,则:
- 读进程退出,返回SIGPIPE信号
- 写进程退出,读进程将不再阻塞,直接返回 0
1、管道创建
有名管道的创建有两种方式,一种是通过命令行创建,另一种是使用函数创建。
(1) 命令行创建
可以通过命令行命令 mkfifo 或 mknod 创建命名管道:
$ mkfifo /tmp/testp
$ mknod /tmp/testp p
然后可以通过 ls 命令查看命名管道的文件属性:
输出中的第一个字符为 p,表示这个文件的类型为管道。最后的 | 符号是有 ls 命令的 -F 选项添加的,也表示这个一个管道。
(2) 函数创建
在程序中创建命名管道,可以使用 mkfifo 函数,其签名如下:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
函数参数
pathname:用于存放命名管道的文件路径,文件必须不存在
mode:表示指定所创建文件的权限
返回值
成功返回0,失败返回-1
mknod ()函数也可以创建命名管道,它的前两个参数和 mkfifo 函数相同,第三个参数为0。
#include <sys/types.h>
#include <sys/stat.h>
int mknod(char *pathname, mode_t mode, dev_t dev);
函数参数
pathname:用于存放命名管道的文件路径
mode:表示指定所创建文件的权限
dev:在创建命名管道时,此参数必为0
返回值
成功返回0,失败返回-1
2、删除命名管道
刚刚说了,创建的管道文件存储在硬盘上,所以使用完后,要像普通文件一下将其删除:
rm /tmp/testp
3、代码示例
生产者代码,pipedemo.c:
#include <limits.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_NAME "/tmp/testp"
#define BUFFER_SIZE 4096
#define TEN_MEG (1024 * 1024 * 10)
int main(void)
{
int pipe_fd;
int res;
int open_mode = O_WRONLY;
int bytes_sent = 0;
char buffer[BUFFER_SIZE + 1];
if(access(FIFO_NAME, F_OK) == -1)
{
res = mkfifo(FIFO_NAME, 0777);
if(res != 0)
{
fprintf(stderr, "Could not create fifo %s\n", FIFO_NAME);
exit(EXIT_FAILURE);
}
}
printf("Process %d opening FIFO O_WRONLY\n", getpid());
pipe_fd = open(FIFO_NAME, open_mode);
printf("Process %d opened fd %d\n", getpid(), pipe_fd);
if(pipe_fd != -1)
{
while(bytes_sent < TEN_MEG)
{
res = write(pipe_fd, buffer, BUFFER_SIZE);
if(res == -1)
{
fprintf(stderr, "Write error on pipe\n");
exit(EXIT_FAILURE);
}
bytes_sent += res;
}
(void)close(pipe_fd);
}
else
{
exit(EXIT_FAILURE);
}
printf("Process %d finished\n", getpid());
exit(EXIT_SUCCESS);
}
消费者代码,pipedemo2.c:
#include <limits.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_NAME "/tmp/testp"
#define BUFFER_SIZE 4096
int main(void)
{
int pipe_fd;
int res;
int open_mode = O_RDONLY;
int bytes_read = 0;
char buffer[BUFFER_SIZE + 1];
memset(buffer, '\0', sizeof(buffer));
printf("Process %d opening FIFO O_RDONLY\n", getpid());
pipe_fd = open(FIFO_NAME, open_mode);
printf("Process %d opened fd %d\n", getpid(), pipe_fd);
if(pipe_fd != -1)
{
do
{
res = read(pipe_fd, buffer, BUFFER_SIZE);
bytes_read += res;
} while (res > 0);
(void)close(pipe_fd);
}
else
{
exit(EXIT_FAILURE);
}
printf("Process %d finished, %d bytes read\n", getpid(), bytes_read);
exit(EXIT_SUCCESS);
}
把上面的代码保存到文件 namedpipedemo2.c 中。并分别编译这两个程序:
$ gcc -Wall pipedemo.c -o pipe1
$ gcc -Wall pipedemo2.c -o pipe2
先在一个终端中执行生产者:
然后在另一个终端中执行消费者:
最后结果是二者完成数据传输后都返回了: