1. 相关概念
无名管道就是在内核空间开辟一块区域,然后会给进程两个文件描述符,只要多个进程可以得到这两个文件描述符,就可以对同一个管道进行操作
无名管道是一个半双工的通信方式,意味着有固定的读端和写端
既然无名管道给当前进程的是两个文件描述符,所以可以通过文件IO函数对其进行操作
无名管道只能用于具有亲缘关系的进程间通信,原因是因为无名管道既然给当前进程的是两个文件描述符,那么这两个文件描述符的创建只能在当前进程的用户空间,当fork之后产生的进程,会继承原本父进程的所有的空间,所以他能得到这两个文件描述符
2. 相关函数
#include <unistd.h>
int pipe(int pipefd[2]);
功能:
创建一个无名管道
参数:
pipefd:
保存两个文件描述符的数组
pipefd[0] 用于读操作
pipefd[1] 用于写操作
返回值:
成功:
0
失败:
-1
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{
//创建一个无名管道
int pipefd[2];
if(pipe(pipefd) == -1)
{
perror("pipe error");
exit(1);
}
//printf("pipefd[0] = %d, pipefd[1] = %d\n", pipefd[0], pipefd[1]);
//向管道写入数据
write(pipefd[1], "hello world", strlen("hello world"));
write(pipefd[1], "abcdefghijklmn", strlen("abcdefghijklmn"));
//注意:管道无法使用lseek改变文件的偏移量
// if(lseek(pipefd[0], 10, SEEK_SET) == -1)
// {
// perror("lseek error");
// exit(1);
// }
//从管道中读取数据
char buf[32] = {0};
read(pipefd[0], buf, 15);
printf("buf = %s\n", buf);
memset(buf, 0, sizeof(buf));
read(pipefd[0], buf, 15);
printf("buf = %s\n", buf);
//如果管道中没有内容,读操作会阻塞
memset(buf, 0, sizeof(buf));
read(pipefd[0], buf, 15);
printf("buf = %s\n", buf);
return 0;
}
3. 无名管道的读写规律
3.1 读写端都存在,只读不写
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{
//创建一个无名管道
int pipefd[2];
if(pipe(pipefd) == -1)
{
perror("pipe error");
exit(1);
}
//向管道写入数据
write(pipefd[1], "hello world", strlen("hello world"));
//读写端都存在,只读不写
//如果管道中有数据则正常读取
//如果管道中没数据则读操作会一直阻塞
//从管道中读取数据
char buf[32] = {0};
read(pipefd[0], buf, 15);
printf("buf = %s\n", buf);
memset(buf, 0, sizeof(buf));
read(pipefd[0], buf, 15);
printf("buf = %s\n", buf);
return 0;
}
3.2 读写端都存在,只写不读
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{
//创建一个无名管道
int pipefd[2];
if(pipe(pipefd) == -1)
{
perror("pipe error");
exit(1);
}
//读写端都存在,只写不读
//会一直往管道中写数据,当管道满时,写操作会阻塞
//默认无名管道64k字节
int num = 0;
while(1)
{
num++;
write(pipefd[1], "1234", 1024);
printf("num = %d\n", num);
}
return 0;
}
3.3 关闭写端,只有读端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{
//创建一个无名管道
int pipefd[2];
if(pipe(pipefd) == -1)
{
perror("pipe error");
exit(1);
}
//向管道写入数据
write(pipefd[1], "hello world", strlen("hello world"));
//关闭写端,只有读端
//如果管道中有数据则正常读取,
//如果管道中没有数据,读操作会返回0
close(pipefd[1]);
//从管道中读取数据
ssize_t bytes;
char buf[32] = {0};
bytes = read(pipefd[0], buf, 15);
printf("bytes = %ld, buf = %s\n", bytes, buf);
memset(buf, 0, sizeof(buf));
bytes = read(pipefd[0], buf, 15);
printf("bytes = %ld, buf = %s\n", bytes, buf);
return 0;
}
3.4 关闭读端,只有写端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{
//创建一个无名管道
int pipefd[2];
if(pipe(pipefd) == -1)
{
perror("pipe error");
exit(1);
}
//关闭读端,只有写端
//当执行第一个写操作时,当前系统中会产生一个信号SIGPIPE,
//这个信号的名字称之为管道破裂,这个信号默认对当前进程的处理是
//结束进程
close(pipefd[0]);
int num = 0;
ssize_t bytes;
while(1)
{
num++;
puts("111111111111");
if((bytes = write(pipefd[1], "1234", 1024)) == -1)
{
perror("write error");
exit(1);
}
printf("bytes = %ld, num = %d\n", bytes, num);
}
return 0;
}
4. 使用无名管道实现进程间通信
由于无名管道是通过调用函数直接给当前进程两个文件描述符,所以没关系的进程无法获得相同的两个文件描述符,所以只有使用fork之后创建的进程才可以继承这两个文件描述符,所以无名管道只能使用在具有亲缘关系的进程间通信
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{
int pipefd[2];
if(pipe(pipefd) == -1)
{
perror("pipe error");
exit(1);
}
pid_t pid;
if((pid = fork()) == -1)
{
perror("fork error");
exit(1);
}
else if(pid > 0) //父进程负责发送数据
{
char buf[32] = {0};
while(1)
{
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
write(pipefd[1], buf, sizeof(buf));
}
}
else //子进程负责接收数据
{
char buf[32] = {0};
while(1)
{
memset(buf, 0, sizeof(buf));
read(pipefd[0], buf, sizeof(buf));
printf("from parent: %s\n", buf);
}
}
return 0;
}