进程间通信----管道

文章目录

  • 管道
    • 实例1:单进程使用管道进行通信
    • 实例2:多进程使用管道进行通信
    • 实例3:父子进程管道半双工
    • 实例4:子进程使用execl启动新程序时管道的使用
    • 使用popen/pclose
      • 实例1: 读取外部程序的输出
      • 实例2:把输出写到外部程序
    • popen的原理:

管道

  • 管道是“半双工”的,即是单向的。

单进程中的管道:

int fd[2]
  • 使用文件描述符fd[1], 向管道写数据
  • 使用文件描述符fd[0], 从管道读数据

注:单进程中的管道无实际用处,管道用于多进程间通信

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

main1.c
一个进程实现管道通信(管道主要用于多进程通信)

#include 
#include 
#include 

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

	// fd为长度为2类型为int的数组,用于保存读管道和写管道两个文件描述符
	ret = pipe(fd); 
	if (ret !=0) {
		printf("create pipe failed!\n");
		exit(1);
	}
	
	// 往管道中写入数据
	strcpy(buff1, "Hello!");
	write(fd[1], buff1, strlen(buff1)); 
	printf("send information:%s\n", buff1);

	// 往管道中读出数据
	bzero(buff2, sizeof(buff2));
	read(fd[0], buff2, sizeof(buff2));
	printf("received information:%s\n", buff2);

	return 0;	
}

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

父子进程通过管道通信步骤

  • 父进程调用 pipe 函数创建管道,得到两个文件描述符 fd[0]、fd[1]指向管道的读端和写端。
  • 父进程调用 fork 创建子进程,那么子进程也有两个文件描述符指向同一管道

如图
进程间通信----管道_第1张图片
main2.c
这里的父子进程可以实现全双工

#include 
#include 
#include 

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:父子进程管道半双工

问题:

  • 对管道进行read时,如果管道中已经没有数据了,此时读操作将被“阻塞”。
  • 父子进程读写都指向同一个管道,则管道有两个写操作.
  • 如果有多个写端口,而只关闭了一个写端,那么无数据时读操作仍将被阻塞。

解决方案:

  • 如果不准备再向管道写入数据,则把该管道的所有写端都关闭,
  • 则,此时再对该管道read时,就会返回0,而不再阻塞该读操作。(管道的特性)

实际实现方式:
父子进程各有一个管道的读端和写端;

  • 把父进程的读端(或写端)关闭;
  • 把子进程的写端(或读端)关闭;
  • 使这个“4端口”管道变成单向的“2端口”管道,全双工变成半双工
    如图:
    进程间通信----管道_第2张图片
    代码:main4.c (改写main2.c)
#include 
#include 
#include 

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

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

解决方案:

  • 把子进程中的管道文件描述符,用exec的参数传递给新进程。
#include 
#include 
#include 
#include 

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()其中后缀"l"代表list也就是参数列表的意思,第一参数path字符指针
		所指向要执行的文件路径, 接下来的参数代表执行该文件时传递的参数列表:
		argv[0],argv[1]... 最后一个参数须用空指针NULL作结束*/

		execl("main3_2", "main3_2", buff2, 0);// 子进程执行main3_2
		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 
#include 
#include 
#include 

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

在这里插入图片描述

使用popen/pclose

popen的作用:

用来在两个程序之间传递数据:

在程序A中使用popen调用程序B时,有两种用法:

  • 程序A读取程序B的输出(使用fread读取)
  • 程序A发送数据给程序B,以作为程序B的标准输入。(使用fwrite写入)
// 返回值:成功,返回FILE*   失败, 返回空
FILE * popen( const char * command,const char * type);

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

读取 ls -l 的结果
main7.c

#include 
#include 

#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"); // 读出ls -l 输出的结果
	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;	
}

进程间通信----管道_第3张图片

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

输出一个字符串,到p2程序中

#include 
#include 
#include 

#define BUFF_SIZE   1024

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

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

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

	return 0;	
}

p2.c
进程间通信----管道_第4张图片
结果:
在这里插入图片描述

popen的原理:

  • 先使用fork创建一个子进程,
  • 然后在子进程中使用exec执行指定外部程序,并返回一个文件指针FILE*给父进程。
  • 当使用”r”时,该FILE指向外部程序的标准输出
  • 当使用”w”时,该FILE指向外部程序的标准输入。

popen的优缺点:

  • 优点:可以使用shell扩展(比如命令中可以使用通配符)使用方便。
  • 缺点:每调用一次popen, 将要启动两个进程(shell和被指定的程序), 资源消耗大。

如果所有管道写端对应的文件描述符被关闭,则read返回0
如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE

你可能感兴趣的:(linux,c语言,c++,开发语言)