管道
1.pipe创建管道
pipe创建管道实现亲缘进程之间的通信
#include "head.h"
int main() {
pid_t pid;
int fd[2];
if (pipe(fd) < 0) {//创建管道文件
perror("pipe");
exit(1);
}
if ((pid = fork()) < 0) {
perror("fork");
exit(1);
}
//使用管道命令让父进程输入 子进程读
if (pid > 0) {
printf("i am the father! fd[0] = %d, fd[1] = %d !\n", fd[0], fd[1]);
close(fd[0]);
write(fd[1], "hello this is a test!\n", 22);
wait(NULL);
} else {
printf("i am the child! fd[0] = %d, fd[1] = %d !\n", fd[0], fd[1]);
close(fd[1]);
char buff[30] = {0};
int n = read(fd[0], buff, 22);
write(STDOUT_FILENO, buff, n);
//printf("%s", buff);
}
return 0;
}
2.mkfifo创建命名管道
- 头文件:
<sys/types.h>
、<sys/stat.h>
- 原型:
int mkfifo(const char *pathname);
- pathname:文件路径
- 返回值:成功返回0,失败返回-1
#include "head.h"
int main() {
//打开该文件并且在文件中写入并且读取
//1.创建命名管道文件
int flag;
if ((flag = mkfifo("fifo_test", 0666)) < 0) {
if (errno == EEXIST) {//errn = EEXITST特殊处理管道文件已存在
printf("fifo exit!\n");
} else {
perror("mkfifo");
exit(1);
}
}
//2.打开文件
int fd;
if ((fd = open("fifo_test", O_RDWR)) < 0) {
perror("open");
exit(1);
}
//3.向文件中写入数据并读出数据
char buff[20] = {0};
write(fd, "hello kkb\n", 10);//写入
int n = read(fd, buff, 10);//读取
write(STDOUT_FILENO, buff, n);//打印内容
return 0;
}
3.popen使用
#include "head.h"
int main() {
/**
* 创建了一个管道fork
* 选择父子进程谁写谁读
* 使用popen&pclose实现cat查看当前文件内容
*/
//1.打开一个文件 从文件流中读取文件内容
FILE *file;
if ((file = popen("/bin/cat ./3.popen.c", "r")) == NULL) {
perror("popen");
exit(1);
}
//2.父进程通过管道 将管道流的内容去读出来并打印
size_t readsize;
char buff[1024] = {0};
while (readsize = fread(buff, 1, sizeof(buff), file) != 0) {
printf("%s", buff);
}
pclose(file);
return 0;
}
注:popen如果没有pclose则将成为僵尸进程。
4.popen实现
实现自己的my_popen
程序:
#include "head.h"
#include "my_popen.h"
int main() {
/**
* 创建了一个管道fork
* 选择父子进程谁写谁读
* 使用popen&pclose实现cat查看当前文件内容
*/
//1.打开一个文件 从文件流中读取文件内容
FILE *file;
//if ((file = popen("/bin/cat ./3.popen.c", "r")) == NULL) {
if ((file = my_popen("/bin/cat ./3.popen.c", "r")) == NULL) {
perror("popen");
exit(1);
}
//2.父进程通过管道 将管道流的内容去读出来并打印
size_t readsize;
char buff[1024] = {0};
while (readsize = fread(buff, 1, sizeof(buff), file) != 0) {
printf("%s", buff);
}
//pclose(file);
my_pclose(file);
return 0;
}
//my_popen.h
#ifndef _MY_POPEN_H
#define _MY_POPEN_H
FILE *my_popen(const char *command, const char *type);//popen打开一个文件 返回文件流的指针
int my_pclose(FILE *fp);//pclose
#endif
//my_popen.c
#include "head.h"
//借助文件描述符的使用特性 实现pclose利用文件描述符访问相应进程号(进程号与文件描述符关联起来)
static pid_t *childpid = NULL;
static int maxfd = 0;//能够获得的最大数量的文件描述符个数
FILE *my_popen(const char* command, const char* type) {
FILE *fp;
//对type的错误处理
if ((type[0] != 'r' && type[0] != 'w') || type[1] != '\0') {
errno = EINVAL;
return NULL;
}
//1.pipe创建管道
int pipefd[2];
if (pipe(pipefd) < 0) return NULL;
//2.fork创建进程
pid_t pid;
if ((pid = fork()) < 0) return NULL;
//3.管道逻辑 利用子进程excel做事情
if (pid == 0) {//子进程
//3.1 关闭没有必要的文件描述符
if (type[0] == 'r') {//父进程读取操作
close(pipefd[0]);//需要将子进程用不上的文件描述符关闭
//将子进程应该输出的内容 输出到管道中(利用dup函数将stdout与管道文件关联起来) 让父进程能够拿到这些数据
if (pipefd[1] != STDOUT_FILENO) dup2(pipefd[1], STDOUT_FILENO);//同时可访问子进程的标准输出
close(pipefd[1]);
} else {//父进程写操作
close(pipefd[1]);
if (pipefd[0] != STDIN_FILENO) dup2(pipefd[0], STDIN_FILENO);
close(pipefd[0]);
}
//3.2 执行子进程该做的其他事务
execl("/bin/sh", "sh", "-c", command, NULL);
} else {//父进程
//3.3 要拿到什么数据 并作为my_popen的返回值返回
if (type[0] == 'r') {
close(pipefd[1]);
if ((fp = fdopen(pipefd[0], type)) == NULL) return NULL;//将打卡文件描述符形式 转化为文件流的形式
} else {
close(pipefd[0]);
if ((fp = fdopen(pipefd[1], type)) == NULL) return NULL;//将打卡文件描述符形式 转化为文件流的形式
}
}
//4.将进程id与文件描述符进行关联 fileno将文件流指针转换为文件描述符
if (childpid == NULL) {
maxfd = sysconf(_SC_OPEN_MAX);//sysconf拿到的宏定义(最大能够打开的文件描述符)
if ((childpid = (pid_t *)calloc(maxfd, sizeof(pid_t))) == NULL) {//为childpid申请内存空间
return NULL;
}
}
childpid[fileno(fp)] = pid;
return fp;
}
int my_pclose(FILE *fp) {
int status;
int fd = fileno(fp);//将文件流转换为文件描述符
pid_t pid = childpid[fd];//获取到文件描述符对应的进程pid信息
if (pid == 0) {
errno == EINVAL;
return -1;
}
fflush(fp);//刷新文件流
close(fd);
wait4(pid, &status, 0, NULL);//wait4等待子进程状态改变(根据进程pid拿到关闭状态)
return status;
}