进程间通信 -- 管道(匿名管道,命名管道)

在进程地址空间那篇博客中提到,进程具有独立性,一个进程的崩溃不会影响其他进程,那怎么该让两个进程之间相互通信呢?

首先我们要有一个共识,进程间想要相互通信的前提是,两个进程要先看到同一份资源,这份资源可以是一个文件或者一块内存(共享内存,下篇博客写),我们试想一下,A进程先打开一份文件,B进程再打开这份文件,A进程往文件中写入后,B文件再向其读取,这样是不是就可以让两个进程看到同一份资源并进行通信了呢?是的,但如果向磁盘中写入后在读取,这样效率就太低了,在OS内核中,每打开一个文件,都会有一个struct file结构体将其组织起来,里面包含了缓冲区,当我们使用write系统调用写入时,实际上是往内核缓冲区里copy,至于什么时候往磁盘中写入,是由操作系统决定的,所以我们如果想要让两个进程看到同一份资源,直接让他们看到文件缓冲区的内容即可,而不必与磁盘进行交互,而这种没有向磁盘中写入,只使用缓冲区的文件,就被称之为管道文件(pipe fiile)

管道又分为匿名管道,和命名管道,我们分别来介绍这两种管道的使用及其特点,并且会在博客结尾附测试代码

一.匿名管道

 从名字中我们可知,这是没有名字的,通常用于父子进程通信时使用,创建匿名管道的函数为pipe

int pipe(int pipefd[2]);

pipefd是一个传出型参数,pipe会传出两个文件描述符,pipefd[0]为读端,pipefd[1]为写端,由调用者来决定该进程是读还是写,是的,没错,读写只能选择其一,管道是半双工通信,无法一份管道既读又写

再创建好管道后,再fork子进程,子进程会继承父进程所拥有的文件描述符,我们以父进程向子进程发送信息为例,也就是父进程写,子进程读,再创建好子进程后,父进程应当关闭读端,也就是close(fds[0]),子进程也应当关闭写端,即close(fds[1]),之后父进程调用write向管道文件写入,子进程再read管道文件,即可进行通信,下面是测试代码

#include <iostream>
#include <unistd.h>
#include <cassert>
#include <cstring>
using namespace std;

int main()
{
    int fds[2];
    int ret = pipe(fds);
    assert(ret == 0); // pipe 成功返回0,失败返回-1并设置errno
    char buffer[1024];
    int id = fork();
    if (id == 0)
    {
        while (1)
        {
            ssize_t n = read(fds[0], buffer, sizeof(buffer));
            buffer[n] = 0;
            cout << getpid() << "receive: "<< buffer << endl;
        }
    }

    while (1)
    {
        snprintf(buffer, sizeof(buffer), "hello world");
        write(fds[1], buffer, strlen(buffer));
        sleep(1);
    }

    return 0;
}

执行结果:

这里还有一些细节需要补充,即在读快,写慢会是什么情况,同样,写快读慢,读时写端关闭,写时读端关闭,我就不一个个放测试代码测试了,只需要改变一下上述测试案例中的sleep时间或者读写关闭时间即可,我就直接写答案了

读快写慢:读端会阻塞等待写端写入

写快读慢:写端会将缓冲区写满后等待读端读取部分数据后再进行写入

读时写端关闭: 读端会读到0时返回

写时读端关闭: 写端会被操作系统发送异常信号,杀死该进程 注意:如果父进程被杀死,子进程不会中断,所以测试该案例时可以换成父进程读,子进程写


二.命名管道:

理解匿名管道后再看命名管道就非常简单了,匿名管道主要用于父子进程之间进行通信,如果想让两个毫不相干的进程使用管道文件进行通信的话,那我们就需要一个拥有名字的管道文件,再让两个进程分别使用open系统调用打开该管道文件再进行读写,这里有个细节,在文件系统博客中提到过,目录也是一种文件,里面存放的是文件名和inode的映射关系,而管道文件只存在文件名和inode,不存在任何文件内容,所以管道文件属于内存级文件,下图是管道文件的属性,可以看到管道文件确实不占用任何磁盘空间

命名管道,其实就是多了个名字,让毫不相干的两个进程打开并进行读写,使用的函数为mkfifo

int mkfifo(const char *pathname, mode_t mode);

第一参数为文件路径,默认为当前路径,第二参数为文件权限,成功返回0,失败返回-1并设置errno

下面两个进程模拟client向server发送信息

#include"command.hpp"

//server
int main()
{
    //创建管道并打开
    createpipe(PIPE_PATH);
    int pipefd = open(PIPE_PATH,O_RDONLY);
    char buffer[1024];
    snprintf(buffer,sizeof(buffer),NULL);
    while(true)
    {
        ssize_t n = read(pipefd,buffer,sizeof(buffer));
        assert(n >= 0);
        if(n>0)
        {
            buffer[n-1] = 0;
            cout << "client: "<< buffer << endl;
        }
        else
        {
            break;
        }
    }
    unlink(PIPE_PATH);
    return 0;
}
#include"command.hpp"

//client
int main()
{
    int pipefd = open(PIPE_PATH,O_WRONLY);
    char buffer[1024];
    while(true)
    {
        cout << "client send# ";
        fgets(buffer,sizeof(buffer),stdin);
        ssize_t n = write(pipefd,buffer,strlen(buffer));
        assert(n >= 0);
    }

    return 0;
}
#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<cassert>
#include<cstring>
#define PIPE_PATH "/tmp/named_pipe"
using namespace std;

//command.hpp
bool createpipe(const string& path)
{
    umask(0);
    int n = mkfifo(path.c_str(),0600);
    if(!n) return true;
    else 
    {
        cout << "创建管道失败,程序已退出"<<endl;
        exit(-1);
    }
}

执行结果:

  • 19
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值