一、无名管道
无名管道是UNIX系统IPC(进程间通信)的最古老形式,所有的UNIX都支持这种通信机制。
1、半双工,数据在同一时刻只能在一个方向上流动。
2、数据只能从管道的一端写入,另一端读出
3、写入管道中的数据遵循先入先出规则
4、管道所传送的数据是无格式的,这要求管道的读出方与写入方必须事先约定好数据的格式
5、管道不是普通文件,不属于某个文件系统,只存在于内存中。
6、管道在内存中对应一个缓冲区。不同的系统其大小不一定相同
7、从管道读数据时一次性操作,数据一旦被独奏,它就从管道中被抛弃,释放空间以便写更多的数据
8、管道没有名字,只能在具有公共祖先的进程(父进程与子进程,或者两个兄弟进程,具有亲缘关系)之间使用
1、pipe函数
#include<unistd.h>
int pipe(int pipefd[2]);
功能:创建无名管道
参数:
pipefd : 为int型数组的首地址,其存放了管道的文件描述符 pipefd[0] 、pipe[1] 分别固定用于读管道和写管道。一般文件 I/O的函数都可以用来操作管道(lseek()除外)
返回值:
成功:0
失败:-1
2、 测试
在下面,用父子进程来使用管道通信测试,第一步创建一个无名管道,第二步创建子进程:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#define SIZE 64
int main()
{
int fds[2];
int ret = -1;
char buf[SIZE];
pid_t pid = -1;
//第一步、创建无名管道
ret = pipe(fds);
if(ret == -1)
{
perror("pipe");
return 1;
}
//第二步、创建子进程
pid = fork();
//子进程创建失败
if(pid == -1)
{
perror("fork");
return 1;
}
//子进程 读管道
if(pid == 0)
{
//关闭写端
close(fds[1]);
memset(buf, 0, SIZE);
//读管道内容
ret = read(fds[0], buf, SIZE);
if(ret < 0)
{
perror("read");
exit(-1);//子进程退出
}
printf("child process buf: %s\n", buf);
//关闭读端
close(fds[0]);
//子进程退出
exit(0);
}
//父进程 写管道
//关闭读端
close(fds[0]);
//写管道
ret = write(fds[1], "ABCDEFGHIJ", 10);
if(ret < 0)
{
perror("write");
return 1;
}
printf("parent process write len: %d\n", ret);
//关闭写端
close(fds[1]);
//关闭文件描述符
close(fds[0]);
close(fds[1]);
return 0;
}
编译后执行:
这里有一点需要注意的是,其实并不知道父子进程谁先执行,但是读管道如果没有读到数据会发生阻塞,所以在这里子进程总能读到父进程写的内容。
3、管道读写特点
1、 如果写端没有关闭,管道中没有数据,这个时候管道进程去读管道会阻塞
如果写端没有关闭,管道中有数据,这个时候读管道进程会将数据读出,下一次读没有数据就会阻塞
2、管道所有的写端关闭,读进程去读管道的内容,读取全部内容后,最后返回0
3、所有读端没有关闭,如果管道被写满了,写管道进程写管道会被阻塞
4、所有的读端被关闭,写管道进程写管道会收到一个信号,然后退出
4、查看管道大小
使用ulimit -a命令查看,一般默认为4k
或者使用fpathconf函数来查看
#include<unistd.h>
long fpathconf(inf fd, int name);
该函数可以通过name参数查看不同的属性值
参数:
fd:文件描述符
name:
_PC_PIPE_BUF,查看管道缓冲区大小
_PC_NAME_MAX,文件名字字节数的上限
返回值:
成功:根据name返回的值的意义也不同。
失败:-1
5、 将无名管道设置为非阻塞
在上面我们知道管道默认为阻塞的,所以测试代码中,无论父子进程谁先执行,子进程总能在管道中读到数据,可以用以下方式设置为非阻塞
//获取原来的flags
int flags = fcntl(fd[0], F_GETFL);
//设置新的flags
flags |= O_NONBLOCK;// flags = flags | O_NONBLOCK
fcntl(fd[0], F_SETFL, flags);
设置读端为非阻塞之后,如果没读到数据,那么读端就会立马返回,如果只有这一个读端,此时没有了任何读端,根据管道读写特点,写端也会退出。
二、有名管道(FIFO)
无名管道只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了命名管道(FIFO),也叫有名管道、FIFO文件。
命名管道不同于无名管道之处在于它提供了一个路径名与之关联,以FIFO的文件形式存在于文件系统中,这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能彼此通过FIFO相互通信,因此,通过FIFO不相关的进程也能交换数据。
有名管道的创建
使用命令mkfifo fifo_name创建一个管道,fifo_name为管道的名字。
使用函数mkfifo(fifo_name, mode)创建有名管道(可以使用命令man 3 mkfifo查看帮助文档):
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
int main()
{
//创建一个有名管道
int ret = mkfifo("fifo", 0644);//第一个参数为管道名称,第二个参数为mode(权限)
if(ret == -1)
{
perror("mkfifo");
return 1;
}
printf("成功创建一个有名管道...\n");
return 0;
}
有名管道的使用测试
创建两个进程,分别用来读和写:
读进程:
int fd = -1; // 文件描述符
int ret = -1;
char buf[SIZE];
//1、以只读方式打开一个管道文件
fd = open("fifo", O_RDONLY);
if(fd == -1)
{
perror("open");
return 1;
}
//2、循环读管道
while(1)
{
memset(buf, 0, SIZE);
ret = read(fd, buf, SIZE);
if(ret <= 0)// 0表示读到末尾,小于0表示读取错误
{
perror("read");
break;
}
printf("buf:%s\n", buf);
}
//3、关闭文件
close(fd);
return 0;
写进程:
int i = 0;
int fd = -1; // 文件描述符
int ret = -1;
char buf[SIZE];
//1、以只写方式打开一个管道文件
fd = open("fifo", O_WRONLY);
if(fd == -1)
{
perror("open");
return 1;
}
//2、写管道
while(1)
{
memset(buf, 0, SIZE);
sprintf(buf, "hello itcast %d", i++); // 将hello itcast i写入buf
ret = write(fd, buf, strlen(buf)); //将buf写入管道,fd为写端的文件描述符
if(ret <= 0)
{
perror("write");
break;
}
printf("write fifo:%d\n", ret);//如果写成功了则会输出这一行代码
sleep(1);
}
//3、关闭文件
close(fd);
return 0;
有名管道注意事项
- 一个只为读而打开一个管道的进程会阻塞,直到另一个进程为只写打开该管道
- 一个只为写而打开一个管道的进程会阻塞,直到另一个进程为只读打开该管道