一、管道的概念
1.原理
在进程3G~4G的内核空间中,创建一个特殊的文件(管道),管道的数据直接保存在内存中。
2.特性
1)管道可以看成是一个特殊的文件,一般的文件存储在外存中,而管道内容是存储在内存中。
2)管道遵循先进先出的原则。(队列形式实现)
3)对于管道的读操作是一次性的,被读取的数据会从管道中删除
4)管道是一种半双工的通信方式
a、单工:只能A发消息给B,B不能发消息给A
b、半双工:同一时间,只能A发消息给B,或者B发消息给A
c、全双工:同一时间,AB能够互相发消息
5)对于管道的操作,只能使用文件IO函数,因为需要直接操作内核空间。例如open,close,read,write。但是不能使用 lseek 函数。
6)当管道的读写端均关闭时,管道内存空间会被释放
7)管道的大小:65536bytes=64K
8)从管道中读取数据
- 当管道的读写端均存在,
- 当管道中没有数据的时候,read函数阻塞。
- 当管道中拥有的数据为10个,读取5个,实际读取5个
- 当管道中拥有的数据为5个,读取10个,实际读取5个
- 当管道的写端不存在的时候后,(父子进程中均有写端,均关闭)
- 当管道中有数据的时候,会先将管道中的所有数据读取完毕
- 当管道中没有数据的时候,read函数不会阻塞,立即返回0;
9)向管道中写入数据
- 当管道的读写端均存在,
- 将管道写满后,write函数会阻塞。
- 当管道的读端不存在的时候,(父子进程中均有读端,均关闭)
- 调用write函数,尝试向管道中写入数据会导致调用write函数的进程退出。
- 调用write函数的进程会收到管道破裂信号(SIGPIPE),管破裂信号的处理函数会让进程退出。
二、无名管道(pipe)
- 无名管道即没有名字的管道文件,即在文件系统中不可见的管道文件。
- 无名管道使用的时候无法使用open函数打开文件,因为不知道路径以及名字。
- 无名管道只能用于具有 亲缘关系 的进程间通信。
为什么无名管道只能用于具有 亲缘关系 的进程间通信。
- 无名管道在文件系统中不可见,所以两个无关的进程,无法拿到同一根管道的读写端。
- 子进程会继承父进程的文件描述符表,所以在父进程中创建一根管道拿到读写端后,调用fork函数,创建出来的子进程也会有该管道的读写端。
pipe:创建一个无名管道,同时打开无名管道的读写端
int pipe(int pipefd[2]);
参数:
int pipefd[2]:需要传入一个整型数组的首地址,且数组的容量为2。
用于存储两个打开的文件描述符;
pipefd[0]:读端
pipefd[1]: 写端
返回值: 成功,返回0; 失败,返回-1,更新errno;
三、有名管道
有名字的管道文件,但是管道的数据依然保存在内存中
有名管道的特点:
- 有名管道是有名字的管道,即在文件系统中可以看到路径以及名字的管道文件。
- 有名管道由于在文件系统中可见,所有无亲缘关系的进程,可以通过open函数打开同一根有名管道。
- 有名管道可以使用在 无亲缘关系 的进程间通信。
创建有名管道
1.shell指令:终端输入mkfifo
mkfifo ./fifo
2.用mkfifo函数创建:创建一个有名管道
int mkfifo(const char *pathname, mode_t mode);
参数:
char *pathname:指定要创建的有名管道路径以及名字;
mode_t mode:有名管道的权限,真实的权限的mode & ~umask:0664 0777
the permissions of the created file are (mode & ~umask).
返回值:
成功,返回0;
失败,返回-1,更新errno;
文件已经存在的错误是一个合法的错误,需要排除,代码允许正常运行
#define EEXIST 17
可用access()函数来判断用户是否具有访问某个文件的权限
需要包含#include<unistd.h>
int access(const char *pathname,int mode)
参数:
pathname:表示要测试的文件的路径
mode:表示测试的模式可能的值有:
R_OK:是否具有读权限
W_OK:是否具有可写权限
X_OK:是否具有可执行权限
F_OK:文件是否存在
返回值:若测试成功则返回0,否则返回-1
可用access函数来判断管道文件是否已经存在,
if(access("./myfifo",F_OK)==-1){
//如果管道文件myfifo不存在的话,则创建管道文件
mkfifo("myfifo",0666);
}
有名管道的使用
- 通过open函数打开有名管道,close关闭有名管道
- 通过read write函数读写有名管道,与读写普通文件一致。
O_RDONLY: 只读
O_WRONLY: 只写
O_RDWR : 读写
----以上三种必须,且只能包含一种-----
O_NONBLOCK 非阻塞形式
- flags == O_WRONLY
- open函数会阻塞,当有另外一个进程 以读 的方式打开同一根FIFO时候,解除阻塞。
- flags == O_RDONLY
- open函数会阻塞,当有另外一个进程 以写 的方式打开同一根FIFO时候,解除阻塞。
- flags == O_RDWR
- open函数不会阻塞,函数运行成功,管道的读写端均被打开
- flags == O_WRONLY | O_NONBLOCK
- open函数不阻塞,open函数会运行失败,此时写端打开失败
- flags == O_RDONLY | O_NONBLOCK
- open函数不阻塞,open函数会运行成功,此时读端打开成功
默认情况下,当管道只有读端,或者只有写端的方式打开,open函数会阻塞。
直到读写端均被打开了,open函数解除阻塞。
四、代码
1.写端写入
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
umask(0);
//创建有名管道
if(mkfifo("./myfifo", 0664) < 0)
{
//文件已经存在的错误是一个合法的错误,需要排除,代码允许正常运行
if(17 != errno) //EEXIST != errno
{
perror("mkfifo");
return -1;
}
}
printf("myfifo create success\n");
//以只写的方式打开有名管道, 阻塞
int fd = open("./myfifo", O_WRONLY);
if(fd < 0)
{
perror("open");
return -1;
}
printf("open fifo success wronly fd=%d\n", fd);
char buf[128] = "";
while(1)
{
printf("请输入>>>");
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf)-1] = 0;
if(write(fd, buf, sizeof(buf)) < 0)
{
perror("write");
return -1;
}
if(strcasecmp(buf, "quit") == 0) //忽略大小写的比较
break;
printf("写入成功\n");
}
close(fd);
return 0;
}
2.读端读取
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
umask(0);
//创建有名管道
if(mkfifo("./myfifo", 0664) < 0)
{
//文件已经存在的错误是一个合法的错误,需要排除,代码允许正常运行
if(17 != errno) //EEXIST != errno
{
perror("mkfifo");
return -1;
}
}
printf("myfifo create success\n");
//以只读的方式打开有名管道, 阻塞
int fd = open("./myfifo", O_RDONLY);
if(fd < 0)
{
perror("open");
return -1;
}
printf("open fifo success rdonly fd=%d\n", fd);
char buf[128] = "";
ssize_t res = 0;
while(1)
{
bzero(buf, sizeof(buf));
res = read(fd, buf, sizeof(buf));
if(res < 0)
{
perror("read");
return -1;
}
else if(0 == res)
{
printf("写端退出\n");
break;
}
printf("res=%ld : buf=%s\n", res, buf);
if(strcasecmp(buf, "quit") == 0)
break;
}
close(fd);
return 0;
}