进程间通信之管道

1.管道

IPC 有多种方式, 管道是IPC的最基本的方式.
管道是“半双工”的,即是单向的。
管道是FIFO(先进先出)的。

单进程中的管道:
int fd[2]
使用文件描述符fd[1], 向管道写数据
使用文件描述符fd[0], 从管道读数据
在这里插入图片描述
注:单进程中的管道无实际用处
管道用于多进程间通信。

2.管道的创建

使用pipe系统调用

返回值:
成功:返回 0
失败:返回 -1

注意:获取两个“文件描述符”
分别对应管道的读端和写端。
fd[0]: 是管道的读端
fd[1]: 是管道的写端
如果对fd[0]进行写操作,对fd[1]进行读操作,可能导致不可预期的错误。

3.管道的使用

实例1:单进程使用管道进行通信

  注意:创建管道后,获得该管道的两个文件描述符,

不需要普通文件操作中的open操作
如图:
在这里插入图片描述

//main1.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <fcntl.h>              /* Obtain O_* constant definitions */
#include <unistd.h>

int main(void) 
{
    int fd[2];
    int ret;
    char buff1[1024];
    char buff2[1024];

    ret = pipe(fd);
    if (ret !=0) {
        printf("create pipe failed!\n");
        exit(1);
    }

    strcpy(buff1, "Hello!");
    write(fd[1], buff1, strlen(buff1)); //fd[1]写
    printf("send information:%s\n", buff1);

    bzero(buff2, sizeof(buff2));
    read(fd[0], buff2, sizeof(buff2));//fd[0]读
    printf("received information:%s\n", buff2);

    return 0;   
}

在这里插入图片描述

实例2:多进程使用管道进行通信

注意:创建管道之后,再创建子进程,此时一共有4个文件描述符。
4个端口,父子进程分别有一个读端口和一个写端口
向任意一个写端口写数据,即可从任意一个读端口获取数据。
在这里插入图片描述

//main2.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(void) 
{
	int fd[2];
	int ret;
	char buff1[1024];
	char buff2[1024];
	pid_t pd;

	ret = pipe(fd);
	if (ret !=0) {
		printf("create pipe failed!\n");
		exit(1);
	}

	pd = fork();
	if (pd == -1) {
		printf("fork error!\n");
		exit(1);
	} else if (pd == 0) {
		bzero(buff2, sizeof(buff2));
		read(fd[0], buff2, sizeof(buff2));
		printf("process(%d) received information:%s\n", getpid(), buff2);
	} else {
		strcpy(buff1, "Hello!");
		write(fd[1], buff1, strlen(buff1)); 
		printf("process(%d) send information:%s\n", getpid(), buff1);
	}

	if (pd > 0) {
		wait();
	}
	
	return 0;	
}

实例3:子进程使用exec启动新程序时管道的使用

	有程序P1, P2
	使用管道进行通信
	P1由用户输入一个字符串,然后把该字符串发给p2
	P2接收到以后,把该字符串打印出来

P1:
创建管道
创建子进程
在子进程中用exec替换成p2, 
(在使用exec 时,把管道的读端作为exec的参数)
在父进程中,获取用户的输入,然后把所输入的字符串发送给p2
(即,父进程把字符串写入管道)

P2:
 从参数中获取管道的读端(参数即为p2的main函数的参数)
 读管道
 把读到的字符串打印出来
	

难点:子进程使用exec启动新程序运行后,
新进程能够使用原来子进程的管道(因为exec能共享原来的文件描述符)
但问题是新进程并不知道原来的文件描述符是多少!

解决方案:
  把子进程中的管道文件描述符,用exec的参数传递给新进程。
//main.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(void) 
{
	int fd[2];
	int ret;
	char buff1[1024];
	char buff2[1024];
	pid_t pd;

	ret = pipe(fd);
	if (ret !=0) {
		printf("create pipe failed!\n");
		exit(1);
	}

	pd = fork();
	if (pd == -1) {
		printf("fork error!\n");
		exit(1);
	} else if (pd == 0) {
		//bzero(buff2, sizeof(buff2));
		sprintf(buff2, "%d", fd[0]);
		execl("main3_2", "main3_2", buff2, 0);
		printf("execl error!\n");
		exit(1);
	} else {
		strcpy(buff1, "Hello!");
		write(fd[1], buff1, strlen(buff1)); 
		printf("process(%d) send information:%s\n", getpid(), buff1);
	}

	if (pd > 0) {
		wait();
	}
	
	return 0;	
}
//main3_2.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char* argv[]) 
{
	int fd;
	char buff[1024] = {0,};

	sscanf(argv[1], "%d", &fd);
	read(fd, buff, sizeof(buff));

	printf("Process(%d) received information:%s\n",  getpid(), buff);	
	return 0;	
}

实例4:关闭管道的读端/写端

管道关闭后的读操作:

问题:
对管道进行read时,如果管道中已经没有数据了,此时读操作将被“阻塞”。
如果此时管道的写端已经被close了,则写操作将可能被一直阻塞!
而此时的阻塞已经没有任何意义了。(因为管道的写端已经被关闭,即不会再写入数据了)

解决方案:
如果不准备再向管道写入数据,则把该管道的所有写端都关闭,
则,此时再对该管道read时,就会返回0,而不再阻塞该读操作。(管道的特性)
注意,这是管道的特性。
  如果有多个写端口,而只关闭了一个写端,那么无数据时读操作仍将被阻塞。

实际实现方式:
父子进程各有一个管道的读端和写端;
把父进程的读端(或写端)关闭;
把子进程的写端(或读端)关闭;
使这个“4端口”管道变成单向的“2端口”管道

在这里插入图片描述

//main4.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(void) 
{
	int fd[2];
	int ret;
	char buff1[1024];
	char buff2[1024];
	pid_t pd;

	ret = pipe(fd);
	if (ret !=0) {
		printf("create pipe failed!\n");
		exit(1);
	}

	pd = fork();
	if (pd == -1) {
		printf("fork error!\n");
		exit(1);
	} else if (pd == 0) {
		close(fd[1]);
		bzero(buff2, sizeof(buff2));
		read(fd[0], buff2, sizeof(buff2));
		printf("process(%d) received information:%s\n", getpid(), buff2);
	} else {
		strcpy(buff1, "Hello!");
		close (fd[0]);
		write(fd[1], buff1, strlen(buff1)); 
		printf("process(%d) send information:%s\n", getpid(), buff1);
		
		close (fd[1]);		
	}

	if (pd > 0) {
		wait();
	}
	
	return 0;	
}

实例5 把管道作为标准输入和标准输出

把管道作为标准输入和标准输出的优点:
1.子进程使用exec启动新程序时,就不需要再把管道的文件描述符传递给新程序了。
2.可以直接使用使用标准输入(或标准输出)的程序。
比如 od –c (统计字符个数,结果为八进制)

实现原理:
1.使用dup复制文件描述符
2.用exec启动新程序后,原进程中已打开的文件描述符仍保持打开,
即可以共享原进程中的文件描述符。

注意:dup的用法
dup复制文件描述符,
返回的新文件描述符被复制的文件描述符,指向同一个文件或管道

//main6.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(void) 
{
	int fd[2];
	int ret;
	char buff1[1024];
	char buff2[1024];
	pid_t pd;

	ret = pipe(fd);
	if (ret !=0) {
		printf("create pipe failed!\n");
		exit(1);
	}

	pd = fork();
	if (pd == -1) {
		printf("fork error!\n");
		exit(1);
	} else if (pd == 0) {
		//bzero(buff2, sizeof(buff2));
		//sprintf(buff2, "%d", fd[0]);
		close(fd[1]);

		close(0);//fd的0,1,2分别表示:标准输入、标准输出和标准错误
		dup(fd[0]);//我们刚刚关掉了fd=0,也就是标准输入被关了,dup会将fd[0]复制到当前进程的最小未用文件描述符,也就是fd=0
		//因此,标准输入现在指向复制后的文件描述符fd[0]。
		close(fd[0]);
		
		execlp("./od.exe", "./od.exe", "-c", 0);
		printf("execl error!\n");
		exit(1);
	} else {
		close(fd[0]);
	
		strcpy(buff1, "Hello!");
		write(fd[1], buff1, strlen(buff1)); //父线程写,子线程实际上此时用表示输入来收,也就是scanf

		close(fd[1]);
	}
	
	return 0;	
}
//od.c
#include <stdio.h>
#include <stdlib.h>

int main(void)
{       int ret = 0;
        char buff[80] = {0,};

        ret = scanf("%s", buff);
        printf("[ret: %d]buff=%s\n", ret, buff);

        ret = scanf("%s", buff);
        printf("[ret: %d]buff=%s\n", ret, buff);
        return 0;
}

4.使用popen/pclose

popen的作用:
用来在两个程序之间传递数据:
在程序A中使用popen调用程序B时,有两种用法:

  • 程序A读取程序B的输出(使用fread读取)
  • 程序A发送数据给程序B,以作为程序B的标准输入。(使用fwrite写入)

用法:man popen
返回值:成功,返回FILE*
失败, 返回空

实例1: 读取外部程序的输出

//main7.c
#include <stdio.h>
#include <stdlib.h>

#define BUFF_SIZE   1024

int main(void)
{
	FILE * file;
	char buff[BUFF_SIZE+1];
	int cnt;

	// system("ls -l > result.txt");
	file = popen("ls -l", "r");
	if (!file) {
		printf("fopen failed!\n");
		exit(1);
	}

	cnt = fread(buff, sizeof(char), BUFF_SIZE, file);
	if (cnt > 0) {
		buff[cnt] = '\0';
		printf("%s", buff);
	}	

	pclose(file);

	return 0;	
}

实例2:把输出写到外部程序

//main8.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUFF_SIZE   1024

int main(void)
{
	FILE * file;
	char buff[BUFF_SIZE+1];
	int cnt;

	file = popen("./p2", "w");//gcc p2.c -o p2
	if (!file) {
		printf("fopen failed!\n");
		exit(1);
	}

	strcpy(buff, "hello world!");
	cnt = fwrite(buff, sizeof(char), strlen(buff), file);
	
	pclose(file);

	return 0;	
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

踏过山河,踏过海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值