进程间通信——管道
要学习进程间通信方式之一的管道,就要了解进程间通信的相关概念。
进程间通信的本质:进程间通信的本质就是让不同的进程看到公共的资源
什么叫做进程间通信呢?
进程之间要交换数据,必须要通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核的缓冲区,进程2再从
内核的缓冲区把数据拷走,内核提供的这种机制叫做:进程间通信
进程间通信的目的:
①数据的传输:一个进程要把数据传送给另外一个进程
②资源共享:多个进程之间要共享一份资源
③通知事件:一个进程要通知另外一个进程,告诉它发生了什么事情
④控制进程:有些进程需要完全控制另外一个进程
介绍完进程间通信,现在就要开始学习进程间通信的方式之一:管道
什么是管道?我们把从一个进程连接到另外一个进程的数据流叫做管道。
管道有两种分类:匿名管道和命名管道,我们先来学习匿名管道。
匿名管道:
那我们要如何创建一个匿名管道,在Linux下,我们创建管道的函数是:pipe
int pipe(int pipefd[2]);
参数:大小为2的文件描述符数组 返回值:创建成功返回0,失败返回错误代码
现在我们在父子进程中创建一个管道,其原理如下图所示:
上面就是创建匿名管道的原理图,现在给出代码:
#include<string.h> #include<stdio.h> #include<unistd.h> int main(){ int fds[2]; if(pipe(fds)<0){ perror("perror"); return 1; } pid_t id = fork(); if(id == 0)//child->read { close(fds[1]);//子进程关闭写端 char buff[1024]; while(1){ size_t size = read(fds[0],buff,sizeof(buff)-1); if(size >0) { buff[size] = '\0'; printf("hello father,i am child:%s\n",buff); } } } else{//father->write close(fds[0]);//父进程关闭读端 const char* msg = "hello child,i am father,hello pipe\n"; while(1){ write(fds[1],msg,strlen(msg)); sleep(1); } } return 0; }
由以上,我们可以得到匿名管道的特点:
①单向通信
②有血缘关系的进程才能通过管道进行相互通信,通常用于父子进程
③管道自带同步机制
④管道是面向字节流的通信服务
⑤与之相关的进程关闭的时候,管道也会随之关闭,我们把管道的生命周期叫做:“随进程”
命名管道:
在学习命名管道之前,我们再补充几个相关概念。
①我们知道管道的本质就是让两个进程看到一份公共的资源,那么我们就把两个进程看到的同一份资源叫做临界资源。
②把访问临街资源的代码叫做临界区,比如:读写部分操作的代码。
③在任意时刻,只允许一方访问临界区获取公共资源,这就是互斥。互斥也保证了访问的时候不会出错。
④Linux同时也会保证访问临界资源的原子性,所谓原子性就是完全相反的两种状态,比如:做了或者没做,没有正在做的状态。
⑤按照某种特定的顺序去访问临界资源的情况叫做同步。
Tip:绝大部分情况下,同步是互斥的前提条件
同时,互斥也会引进另外的问题,就是饥饿问题:当某一段一直在访问临界资源,且当前端的访问优先级比较高,那么另
外一端
就无法被响应,此时就造成饥饿问题。
相关的概念补充完成之后,我们来学习命名管道,刚才在匿名管道中,我们知道匿名管道的缺点就是只能让具有血缘关系
的进程进行通信,但是如果两个进程之间没有血缘关系,但是需要进行通信,此时就需要命名管道。
如何创建我们的命名管道:
mkfifo();
int mkfifo(const char* pathname,mode_t mode)
返回值:成功返回0,失败返回-1
参数:pathname 在指定文件路径下生成文件,这个文件叫做管道文件 mode:文件的权限
头文件:<sys/types.h> <sys/stat.h>
我们在linux中实现的时候,创建两个文件: client.c server.c 模拟服务器端和客户端,在server.c中创建管道文件,
client作为
写端,server作为读端。
server.c代码:
#include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/stat.h> #include<string.h> #include <fcntl.h> int main() { umask(0); if(mkfifo("./mypipe",0666|S_IFIFO)<0)//当前文件路径下创建管道文件,权限为只读只写 { perror("mkfifo"); return 1; } int fd = open("./mypipe",O_RDONLY);//服务器以只读方式打开 if(fd <0 ) { perror("open"); return 2; } char buff[1024]; while(1) { size_t size = read(fd,buff,sizeof(buff)-1); if(size>0){ printf("client say#%s\n",buff); }else{ if(size==0){ perror("read"); break; } } } close(fd); return 0; }
client.c代码:
#include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/stat.h> #include<string.h> #include <fcntl.h> int main() { int fd = open("./mypipe",O_WRONLY);//客户端以写方式打开 if(fd <0 ) { perror("open"); return 2; } char buff[1024]; while(1) { printf("please enter#"); fflush(stdout); size_t size = read(0,buff,sizeof(buff)-1); if(size>0){ buff[size-1] = 0; write(fd,buff,sizeof(buff)-1); }else{ if(size == 0){ perror("read"); break; } } } close(fd); return 0; }
运行时,虽然client进行的写操作,但是命名管道是在server中创建的,所以在运行的时候,要先去运行
server,在linux中运行时,我们可以打开两个命令行窗口,一个运行client,一个运行server,在client中
输入的字符就会在server中显示出来。
以上就是我们的命名管道。
Tip:限于编者水平,文章难免有不足之处,欢迎指正。
如需转载,请注明出处!