进程间通信-管道通信

两个进程的通信,每个进程各自有不同的地址空间,每个地址空间的数据信息是独立的,任何一个进程的全局变量在另一个进程中都看不到。例如,父进程中有一个全局变量a = 0;在子进程中改变a的值不会影响父进程中a值的结果,因为子进程所有的数据信息都拷贝(写时拷贝)自父进程,两个进程有各自不同的地址空间。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int a = 0;
int main()
{
	pid_t id = fork();
	if(id == 0){
			//child
			a = 10;
			printf("Is child time,a:%d\n",a);
		}else{
			//father
			printf("Is father time,a:%d\n",a);
			sleep(1);
		}

	return 0;
}


由上面代码和运行截图可清晰的看出,父子进程之间没有共享数据,所以两个进程是不能直接通信的。

要通信就必须要进过内核,在内核中开辟一块缓冲区(第三方资源),让不同的进程看到一份公共的资源。具体实现是进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,这就实现了进程之间的通信。

进程通信--管道

先看管道的创建,由pipe函数创建:

 #include <unistd.h>

 int pipe(int pipefd[2]);


返回值:

调用成功返回0,调用失败返回-1;

作用:

调用pipe函数时在内核中开辟一块缓冲区(又称管道)用于通信,有一个读端和一个写端,然后通过输出型参数pipefd[2]传出给进程两个文件描述符,pipefd[0]指向的是管道的读端,pipefd[1]指向的是管道的写端。向管道中写的话就直接调用write(pipefd[1],msg,strlen(msg));从管道中读直接调read(pipefd[0],buf,sizeof(buf) - 1);所以管道看来就像一个打开的文件。两个进程分别向管道(资源共享区)读写,就完成了进程的通信。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int a = 0;
int main()
{
	int fd[2] = {0,0};
	if(pipe(fd) < 0){
		perror("pipe error\n");
		return 1;
	}
	printf("fd[0]:%d,fd[1]:%d\n",fd[0],fd[1]);

	return 0;
}

这个程序可以看出,读端的文件描述符fd[0] = 3;写端的文件描述符fd[1] = 4;

一个进程创建的管道:

因为子进程的数据资源都是来自与父进程,所以子进程中也有和父进程一样的文件描述符表,两个进程指向的是同一个管道,如下图:

实现父子进程通信的代码:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main()
{
	int fd[2] = {0,0};
	if(pipe(fd) < 0){
		perror("pipe error\n");
		return 1;
	}

	pid_t id = fork();
	if(id == 0){
			//child --->write
			close(fd[0]);
			const char *msg = "I am child,hello father\n";
			while(1){
				write(fd[1],msg,strlen(msg));
			//	printf("child\n");
				sleep(1);
			}
		}else{
			//father--->read
			char buf[1024];
			close(fd[1]);
			while(1){
				ssize_t s = read(fd[0],buf,sizeof(buf) - 1);
			//	printf("father\n");
				if(s > 0){
					buf[s] = 0;
					printf("%s",buf);
				}
			}
		}

	return 0;
}


在代码中,"I am child,hello father"这句话是子进程写在管道中的,父进程从管道中读出来了管道中的内容。这就完成了父子进程之间的通信。

从上面代码中,看到子进程在写之前关闭了读的文件描述符fd[0],父进程在读之前关闭了写文件描述符fd[1];这是为了预防发生错误,这就要涉及到管道的使用限制了,两个进程通过一个管道只能实现单向通信,父进程读,子进程写。要完成父进程写子进程读的话就需要重新开辟一个管道了。若在父进程读的情况下,没有关闭父进程的写fd[1],在父进程中去写,就会导致某些未知的错误(在父进程中不去写就不会出错,关闭是为了防止错误的出现);关闭fd[1],在父进程想写进管道时就不会成功。管道是一种半双工方式,即对于进程来说,要么只能读管道,要么只能写管道。不允许对管道又读又写。

这种管道是匿名管道,它的特点是:

1.单向通信;

2.管道依赖于文件系统,其生存周期结束是进程退出时结束,称为随进程;

3.数据读写时是按照数据流进行的;

4.只适用于有血缘关系的进程(父子进程,兄弟进程)通信;

5.自带同步机制,若写时满,读的时候要匹配写的速度;

使用管道需要注意以下4中情况:(读情况都是父进程,写情况都是子进程)

1.子进程写的慢,父进程读的快;父进程从管道中读数据,管道中数据全部被读完后,父进程再次去read会阻塞,等待子进程写,直到管道中有数据可读了才读取数据并返回,例子就是上面实现的代码。

2.子进程写的快,父进程都得慢;导致管道中数据全被写满,再次write会被阻塞,直到管道中有空位置了才写入数据并返回。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main()
{
	int fd[2] = {0,0};
	if(pipe(fd) < 0){
		perror("pipe error\n");
		return 1;
	}

	pid_t id = fork();
	if(id == 0){
			//child --->write
			close(fd[0]);
			int count = 0;
			const char *msg = "I am child,hello father\n";
			while(1){
				write(fd[1],msg,strlen(msg));
			//	printf("child\n");
			//	sleep(1);
				printf("count:%d\n",++count);
			}
		}else{
			//father--->read
			char buf[1024];
			close(fd[1]);
			while(1){
				ssize_t s = read(fd[0],buf,sizeof(buf) - 1);
			//	printf("father\n");
				if(s > 0){
					buf[s] = 0;
					printf("%s",buf);
				}
				sleep(5);
			}
		}

	return 0;
}


写到管道的最大容量,等待读取之后在进行写入;

再次write,管道中有空余,则继续写入,直到满,下次写入阻塞,直到管道中有空位置了才写入数据并返回。

3.父进程中读fd[0]关闭,子进程写;子进程向管道中一写,操作系统会终止该进程,进程异常终止。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main()
{
	int fd[2] = {0,0};
	if(pipe(fd) < 0){
		perror("pipe error\n");
		return 1;
	}

	pid_t id = fork();
	if(id == 0){
			//child --->write
			close(fd[0]);
			int count = 0;
			const char *msg = "I am child,hello father\n";
			while(1){
				write(fd[1],msg,strlen(msg));
			//	printf("child\n");
				sleep(1);
				//printf("count:%d\n",++count);
			}
		}else{
			//father--->read
			char buf[1024];
			close(fd[1]);
			int count = 0;
			while(1){
				ssize_t s = read(fd[0],buf,sizeof(buf) - 1);
			//	printf("father\n");
				printf("count:%d\n",count);
				if(s > 0){
					buf[s] = 0;
					printf("%s",buf);
				}
				
				if(count++ > 5)
				{
					close(fd[0]);
					break;
				}
					
			}
			int status = 0;
			pid_t ret = waitpid(id,&status,0);
			printf("ret:%d,sig:%d,exitCode:%d\n",ret,status&0xff,(status>>8)&0xff);

		}

	return 0;
}

4.子进程关闭写,父进程读;子进程没有向管道中写数据,父进程从管道中读数据,管道中所以数据都被读取后,在次read会阻塞,直到管道中有数据可读了才读取数据并返回。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main()
{
	int fd[2] = {0,0};
	if(pipe(fd) < 0){
		perror("pipe error\n");
		return 1;
	}

	pid_t id = fork();
	if(id == 0){
			//child --->write
			close(fd[0]);
			int count = 0;
			const char *msg = "I am child,hello father\n";
			while(1){
				write(fd[1],msg,strlen(msg));
				if(count++ > 5)
				{
					close(fd[1]);
					break;
				}
				sleep(1);
				printf("count:%d\n",count);
			}
		}else{
			//father--->read
			char buf[1024];
			close(fd[1]);
			int count = 0;
			while(1){
				ssize_t s = read(fd[0],buf,sizeof(buf) - 1);
				if(s > 0){
					buf[s] = 0;
					printf("%s",buf);
				}
					
			}
			int status = 0;
			pid_t ret = waitpid(id,&status,0);
			printf("ret:%d,sig:%d,exitCode:%d\n",ret,status&0xff,(status>>8)&0xff);

		}

	return 0;
}

由上面代码可以证实,子进程关闭写,父进程读完管道的数据,会一直阻塞等待直到有数据写入,才读取数据并返回;

进程间通信并没有结束,这只是个开始;






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值