Linux小黑板(8)管道

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

"让我们,笑吧"

一、什么是通信?

管道是属于进程间通信的一个实现方式。再讲管道之前呢我们先来说说什么叫做进程间通信。我们日常生活中给自己的家人、朋友给一个call或者弹一条微信、QQ等等从而让人家能够知道我们想给对方传达的信息是什么这其实简单来说是一种信息的交互。

(1)为什么需要通信?

进程通信的目的有很多种:
数据传输:一个进程需要将数据发送给另外一个进程。
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或者另一组进程发送消息通知它们发生了某种事件。
进程控制有些进程希望完全控制另外一个进程的执行(如我们进入调试debug)。

比如我们想在file.txt里查找到hello的字段。命令行中cat是一个进程grep也是一个进程。

他们通过"|"管道进行了数据通信。cat负责打印file.txt中的数据到显示器grep 负责从这些数据按关键字过滤。从而达到我们想要的效果。那么在个地方我们使用到了进程间通信方式一种——管道。

(2)如何进行通信

在讨论了进程通信的重要性那么实现它就是很必要的。然而我们知道进程具有独立性那么通信的成本一定不会很低。

  • 管道

  • System V进程间通信

  • POSIX 进程间通信

常见的进程间通信有三种方式。而本节重要讲管道。

举个例子:
我们在使用QQ或者微信和自己的朋友、家人聊天的时候。发送的消息一定会满足两个需求一个是我自己要看到!我发了什么二个是对方一定要看到否则就根本无通信的可能。
进程间通信完全可以看作是两个进程 通过某种方式、凭借什么手段从而让自己发送的信息能够让对方看到。 我们称这样的一种方式这样的能被两个进程同时看到的 资源 叫做"临界资源"。

在这种基础上建立的对进程间通信的理解也就事半功倍。

OS的定位以及重要作用决定了任何的进程间通信一定绕不开OS的管理。
OS需要给通信的双方间接维护、建立通信需要的“内存空间”。
并且这种内存空间对于通信双方是 公共可见的

二、匿名管道

(1)什么是管道

管道是Unix中最古老的进程间通信的形式;
我们把一个进程连接到另外一个进程的数据流称之为"管道"
它们是一种基于文件系统(同一份资源)的通信方式。

管道分类;

管道分为两类:

  • 匿名管道: 是一种内存级文件

  • 命名管道:是一种磁盘文件同普通文件一样在磁盘上保存。

如何理解这两种文件呢那么一定是接下来的重头戏~

(2)创建匿名管道

正如其管道的名字一样该管道没有"名字"。毕竟是"内存级文件"。

       #include <unistd.h>
       int pipe(int pipefd[2]);
参数pipifd
    是一个输出型参数保存的是文件描述符数组。fd[0]表示读端fd[1]表示写端
return val:
On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.

从函数pipe的参数设置就说了管道支持单向通信的。如果是父进程打开的是写端那么它自己应该关闭"读端"(即便你不会去读取但是这保险)而子进程打开的读端,那么顺其自然它应该去关闭自己的"写端"。

(3)管道缓冲区

我们此时让 父进程一直读子进程每1s中向管道输出一次。

现象是我们看到每写入一次就读取一次。

我们对代码进行了修改。

可以看到当读端一直在管道里读数据的时候会在由于子进程没有发送数据那么读端就会在read处陷入阻塞状态。

我们再对代码进行修改;

此时父进程读取的数据不再是一行一行的了。因为管道的缓冲区在读取之前已经有很多数据了。而read的数据大小为 sizeof buffer。

当我们不再进行读取时;

当写入的次数达到一定时也就不会再进行写入了因为还有人前来读取数据释放掉管道的空间那么管道的空间也是有限的

(4)读写端关闭

如果父进程正在读取而此时写端关闭会发什么 如果子进程正在写入而父进程读端关闭又会发生什么

父进程正在读取而此时写端关闭;

和我们预想的一样read的返回值当检测到0时就证明着写端关闭了;

子进程正在写入而父进程读端关闭;

当读端关闭时写端也就没有任何意义了。那么此时OS会直接向该进程发送信号(SIGPIPE)终止该进程行为;

(5)读写与匿名管道特征

进行上述那么多的实验无非就是为了验证下面的总结:

读写特征:
1.读慢写快。(写端直到把缓冲区打满)
2.读快写慢。(读端在read处阻塞等待)
3.写关闭读0结束。
4.读关闭时写端会被OS发送信号终止。
匿名管道特征:
1.管道的生命周期随进程(内存级文件)
2.一般用来进行具有血缘关系的进程之间(继承文件描述符表)
3.管道是面向字节流
4.半双工 --- 单向通信
5.具有同步与互斥机制

三、命名管道

从名字上就知道匿名管道和命名管道的区别。是的管道是基于文件系统的进程间通信方式匿名管道是内存级文件因此它不需要有文件名。而命名管道是实打实的磁盘文件。两个进程建立通信即共同能够看到一份公共资源匿名管道是内存级父子进程通过继承关系子进程能够看到父进程打开的文件描述符fd。而命名管道基于磁盘的路径+文件名标识磁盘上唯一的文件两个进程同时打开这一个文件也就意味着两个进程能够看到同一份公共资源!

(1)命令行创建管道文件

mkfifo + filename

(2)函数实现创建

       #include <sys/types.h>
       #include <sys/stat.h>
//创建
       int mkfifo(const char *pathname, mode_t mode);
//删除
       #include <unistd.h>
       int unlink(const char *path);

(3)实现一个简单的client\server通信

comm.h:
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <cassert>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
using namespace std;
#define NAME_PIPE "./named_pipe"

bool CreatePipe(const std::string& path)
{
    umask(0);
    int n = mkfifo(path.c_str(),0666);
    if(n == 0)
    {
        return true;
    }    
    else{
        cout << "create pipe faild" << endl;
        return false;
    }
}


void removeFifo(const std::string& path)
{
    int n = unlink(path.c_str());
}

server:
int main()
{
    // std::cout << "hello server" << std::endl;
    bool r = CreatePipe(NAME_PIPE);
    assert(r);
    (void)r;

    int rfd = open(NAME_PIPE, O_RDONLY);
    if (rfd < 0)
        exit(1);

    char buffer[1024];
    while (true)
    {
        ssize_t s = read(rfd, buffer, sizeof(buffer));
        if (s > 0)
        {
            buffer[s] = 0;
            std::cout << "client->server# " << buffer << std::endl;
        }
        else if (s == 0)
        {
            std::cout << "client quit, me too!" << std::endl;
            break;
        }
        else
        {
            std::cout << "client quit, me too!" << std::endl;
            break;
        }
    }

    close(rfd);
    removeFifo(NAME_PIPE);
    return 0;
}

client:

int main()
{
    // 服务端写数据
    int wfd = open(NAME_PIPE, O_WRONLY);
    if (wfd < 0)
        exit(0);

    char buffer[1024];
    // write;
    while (true)
    {
        fgets(buffer, sizeof(buffer), stdin);
        // 去掉'\n'
        if (strlen(buffer) > 0)
            buffer[strlen(buffer) - 1] = 0;
        ssize_t n = write(wfd, buffer, sizeof(buffer));
        assert(n == strlen(buffer));
        (void)n;
    }

    close(wfd);
    return 0;
}

总结:

①进程间通信的本质在于让不同进程看到同一份公共资源

②管道是基于文件系统的通信方式。匿名管道属于内存级文件命名管道属于磁盘的管道文件。

③pipe可以创建匿名管道。多用于具有亲缘关系的进程间通信。

④命名管道可以实现任意两个进程进行通信。mkfifo命令行可以创建管道文件。

本篇到此结束感谢你的阅读

祝你好运也向阳而生~

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux