【Linux系统与网络编程】07:管道

本文介绍了三种管道通信方式:1)使用pipe创建匿名管道实现亲缘进程间通信;2)通过mkfifo创建命名管道,允许无亲缘关系的进程通信;3)运用popen函数执行命令并获取其输出,以及如何避免成为僵尸进程;4)展示了自定义的my_popen函数,实现类似popen的功能。
摘要由CSDN通过智能技术生成

管道


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;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值