一、什么是管道
管道是Unix中一种进程间通信的方法,我们把从一个进程链接到另外一个进程的数据流称为“管道”。
管道是最像队列的一种进程间通信模型,生产者的进程在管道的写端写入数据,消费者的进程在管道的读端进行读取数据,这个过程类似于队列,区别就是我们并不需要关心使用的管道的线程安全和内存分配等琐碎的事情,这些事情都是由操作系统为你完成。
二、管道的分类
管道分为命名管道和匿名管道。
(1)匿名管道
匿名管道也称为无名管道,由于我们需要在父子进程进行通信,交换数据,此时就有了匿名管道,匿名管道是由pipe进行创建的。
匿名管道是半双工的,数据智能向一个方向流动,此时如果我们需要建立起双方的通信,则这个时候就需要建立两个管道。
管道的声明周期随进程而定,进程退出的时候管道被释放
(2)命名管道
由于匿名管道是在一个相关的两个进程之间进行交换数据,此时如果我们需要在不同的父子进程之间进行数据的交换,此时就出现了命名管道。
命名管道也叫具名管道,它相当于创建一个文件,通过一个进程读,一个进程来写完成进程间通信。
三、匿名管道的创建
(1)管道创建函数
#include <unistd.h>
int pipe(int pipefd[2]);
- 头文件
include <unistd.h>
- 参数
pipefd:文件描述符数组,fd[0]表示读端,fd[1]表示写端。 - 返回值
成功返回0,失败返回错误代码。
(2)管道创建图示
(3)管道创建事例
例一:管道的写入与读出
从键盘中获取输入,写入管道,并从管道的另外一端,读出数据
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char buf[100];
int fds[2];
int len;
//创建管道
if(pipe(fds)==-1)
perror("pipe"),exit(1);
//从键盘输入读取数据
while(fgets(buf,100,stdin))
{
len = strlen(buf);
if(write(fds[1],buf,len) == len)
perror("write"),exit(1);
memset(buf,0x00,sizeof(buf));
//从管道读端读数据
if((len = read(fds[0],buf,100)) == -1)
perror("read"),exit(1);
//写到标准输出
if(write(1,buf,len)!=len)
perror("write to stdout"),exit(1);
}
}
运行结果
例二:用fork()来管理管道的共享
由图例我们可知共享管道的三个步骤,先把文件描述符连接到管道的读端和写端,接着fork出子进程,最后关闭父进程的读端和子进程的写端,就可达到父进程写,子进程读的目的。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <error.h>
int main(void)
{
int fds[2];
pid_t pid;
//创建管道
if(pipe(fds) == -1)
perror("pipe"),exit(1);
//创建子进程
pid = fork();
if(pid == -1)
perror("fork()"),exit(1);
//父进程向管道写入信息,写完关闭管道
if(pid == 0)
{
close(fds[0]);
write(fds[1],"hello,l'm your father!",22);
exit(0);
close(fds[1]);
}
//子进程从管道接收信息
close(fds[1]);
char buf[1024] = {0};
read(fds[0],buf,1024);
printf("buf = %s\n",buf);
return 0;
}
四、管道的读写规则
1、当管道中没有数据可读时:若pipe采取的是不阻塞方法,则直接read返回-1;采取是阻塞时,进程阻塞直到等待数据可以读;
2、当管道满时:采用不阻塞方式将调用返回-1;反之write阻塞,直到有程序读走数据。
1、没有数据可读的时候
- O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
- O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
2、当管道内数据满了的时候
管道内数据满了,此时write调用会被阻塞,直到有进程读走数据。
- O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
如果所有管道写端对应的文件描述符被关闭,则read返回0
- 如果所有管道读端对应的文件描述符被关闭,此时会管道有可能破裂。则write操作会产生信号SIGPIPE
当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
五、命名管道的创建
(1)命名管道创建方法
命名管道的创建有两个方法:
- 命令行上使用命令
$ mkfifo filename
- 程序中创建
程序中创建需要调用函数,在man 3手册中显示mkfifo会有如下信息:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
- 头文件
#include <sys/types.h>
#inlcude<sys/stat.h>
- 返回值
成功返回0,失败返回-1。 - 参数说明
pathname:是所要创建的文件名。
mode:文件权限。
(2)创建事例
int main()
{
mkfifo("p1",0644);
return 0;
}
运行结果为:
(3)命名管道的运用
事例一:
命名管道实现简单的server和client通信。
整体思路就是一个创建管道,不停的向管道输入数据,另外一个读出管道内容即可。
实现效果如下图:
代码如下:
myclient.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
int main(int argc, char argv[])
{
char buf[1024];
int flag = open("mypipe", O_WRONLY);
if (flag == -1)
perror("open my pipe"), exit(1);
while (1)
{
buf[0] = 0;
printf("plase Enter#");
fflush(stdout);
int s = read(0, buf, sizeof(buf));
if (s <= 0)
{
perror("read buf"), exit(1);
}
else
{
buf[s] = 0;
write(flag, buf, sizeof(buf));
}
}
}
myserver.c
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
int main()
{
char buf[1024];
if ((mkfifo("mypipe", 0644)) == -1)
perror("mkfifo"), exit(1);
int rfd = open("mypipe", O_RDONLY);
if (rfd < 0)
perror("open"), exit(1);
while (1)
{
buf[0] = 0;
printf("wait...\n");
int s = read(rfd, buf, sizeof(buf) - 1);
if (s > 0)
{
buf[s - 1] = 0;
printf("Client say#%s", buf);
}
else if (s == 0)
{
printf("Client Quit!\n");
exit(0);
}
else
perror("read"), exit(1);
}
}
事例二:实现文件的拷贝
由于命名管道相当于一个文件,所以我们可以创建一个命名管道,同时向命名管道写入内容,另外一个程序读取管道内的内容,写入目标文件即可。
六、命名管道匿名管道的区别
- 匿名管道由pipe函数创建并打开
- 命名管道由mkfifo函数创建。打开用open
- FIFO(命名管道),与pipe(匿名管道)唯一的区别就是它们创建和打开的方式不同,一旦他们完成自己的工作后,他们具有相同的意义。