进程之间具有独立性. 其他的进程无法访问本进程的资源和内容.
那如果想要让进程访问一些某个进程内的内容, 我们该怎么做?
这时就需要通过进程间通信, 来完成两个进程之间的内容交互.
管道就是用来完成进程间通信的一种方式.
管道又分为匿名管道, 和命名管道.
管道
管道它允许两个进程之间的信息传递, 其中一个进程的输出直接作为另一个进程的输入.
管道是一种单向通信机制, 数据只能沿一个方向流动.
管道本质就是一个文件, 进程间通信就是让两个进程看见相同的空间 (文件)
并且管道都是位于内核区的, 而不是用户区
上面我们也提过, 管道只能单向通信, 即一个进程要么只能读, 要么只能写
如果想要实现两个进程都能进行"交流', 可以在创建一个管道, 这次读写的身份反过来.
匿名管道
匿名管道: 顾名思义这个管道文件没有名字
匿名管道通常用于父子进程之间进行通信
int pipefd[2]: 当创建好管道后, 会将管道读写端文件描述符返回
pipefd[0]: 管道读端的描述符. pipefd[1]: 管道写端的描述符
返回值: 创建成功返回 0, 创建失败返回 -1
int main()
{//创建管道int pipefd[2]={0}; //0下标表示读取端,1下标表示写入端int n = pipe(pipefd);assert(n!=-1);pid_t id = fork();assert(id!=-1);if(id==0)//子进程, 构建单向通信{close(pipefd[1]);}//父进程写入,子进程读取close(pipefd[0]);return 0;
}
在上面的代码中, 我们先创建了匿名管道, 然后再创建子进程, 这样管道的两个文件描述符 pipefd[2] 就会被自动拷贝给子进程. 然后让父子进程分别关闭读端和写端.
当然, 上面的代码只实现了单项通信, 想实现双向通信就需要多创建一个管道.
既然管道就是一个文件, 那么读写数据也就是使用 read / write 函数.
int main()
{//创建管道int pipefd[2]={0}; //0下标表示读取端,1下标表示写入端int n = pipe(pipefd);assert(n!=-1);pid_t id = fork();assert(id!=-1);if(id==0)//子进程, 构建单向通信{close(pipefd[1]);char buffer[1024];while(1){ssize_t s = read(pipefd[0],buffer,sizeof(buffer)-1);if(s>0){buffer[s]=0;cout<<"father# "<<buffer<<endl;}else//read的返回值等于0代表父进程的管道文件已经close了{cout<<"写入结束,子进程退出";break;}}exit(0);}//父进程写入,子进程读取close(pipefd[0]);string str = "给子进程发信息";int count=0;char send_buffer[1024];snprintf(send_buffer,sizeof(send_buffer),"%s[%d]",str.c_str(),getpid());//往缓冲区里写入数据//写入到管道中write(pipefd[1],send_buffer,sizeof(send_buffer));// 关闭文件描述符, 释放资源close(pipefd[1]);pid_t ret = waitpid(id,NULL,0); // 等待子进程return 0;
}
匿名管道的特点:
- 管道常用于父子进程间的通信
- 管道是面向字节流的服务
- 管道是基于文件的,管道的生命周期随进程
- 管道是单向通信的
- 写快读慢,写满管道后不能再写了
- 写慢读快,管道没有数据时,读端要等待
- 写端关闭,读端会读到0,标识结束
- 读端关闭,写端继续写会终止进程
命名管道
匿名管道必须在有血缘关系的进程间才能使用.
如果两个进程之间没有血缘关系, 可以通过创建命名管道来进行通信.
命名管道可以通过使用命令创建, 也可以在程序中使用系统调用函数来创建.
1. 使用命令创建
mkfifo filename
mkfifo 用于创建命名管道
filename 被创建出来的管道的名称
2. 通过函数调用创建
#include <sys/types.h>
#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);
int main()
{mkfifo("fifo", 0644); // 创建一个管道, 名称为 fiforeturn 0;
}
当命名管道存在以后, 使用就和匿名管道是类似的.
读端代码:写端代码:
string ipcpath = "./fifo.ipc";
int main()
{//client不用自己创建管道文件,只需获取文件即可int fd = open(ipcpath.c_str(),O_WRONLY);if(fd<0){perror("open");exit(1);}//通信过程string buffer;cout<<"please Enter message: ";getline(cin,buffer);write(fd,buffer.c_str(),buffer.size());close(fd);return 0;
}
命名管道打开的的规则:
- 如果没有进程打开命名管道进行写操作, 那么尝试从命名管道读数据的进程将被阻塞
- 如果没有进程打开命名管道进行读操作, 那么尝试向命名管道写数据的进程将被阻塞
进程可以在打开命名管道时设置非阻塞标志 (O_NONBLOCK) , 这样在没有对应读/写操作时,
read()
或write()
调用将立即返回, 而不是阻塞等待.
命名管道的使用, 和文件的使用是非常相似的, 会文件操作就会命名管道的使用.