【Linux】进程通信篇Ⅰ:管道进程——pipe()、mkfifo()、open()、read()、write()、close()、unlink()

进程间通信:Inter-Process Communication,简写做 IPC


一、匿名管道

1. 创建管道 pipe

头文件:

#include <unistd.h>
#include <fcntl.h>

int pipe(int pipefd[2])

输出型参数:

  • 带回两个 文件描述符
  • 0 下标为 读端,助记:0 - -> 嘴巴 - -> 读书
  • 1 下标为 写端,助记:1 - -> 笔 - -> 写东西

返回值

  • 成功返回 0,失败返回 -1

2. 管道的特点

  1. 单向通行(管道是半双工的一种特殊情况)

  2. 管道的本质是文件,因为 fd 的生命周期随进程,所以管道的生命周期也是随进程的

  3. 管道通信,通常用来进行具有 “血缘关系”的进程,进行进程件通信。常用于 父子间通信 - - 使用 pipe 打开管道(匿名管道,并不清楚管道的名字)

  4. 在管道通信中,写入的次数 和 读取的次数,不是严格匹配的。读写没有强相关 - - 字节流
    管道 是面向 字节流 的

  5. 管道具有一定的协同能力,让 reader 和 writer 能够按照一定的步骤进行通信(管道自带 同步机制

3. 四种场景

  1. 如果我们 read 读取完毕所有管道的数据,如果对方不发,我们就只能等待

  2. 如果我们 write 端将管道写满了,就不能写啦

  3. 如果关闭了写端,读取完毕管道数据,再读,就会 read 返回 0,表明读到了文件结尾

  4. 写端一直再写,读端关闭,没有意义。OS 不会维护无意义、低效率、或者浪费资源的事。故,OS 会通过信号,来终止进程 13)SIGPIPE


二、命名管道

让不同的进程通过 文件路径 + 文件名 看到同一个文件并打开,就是看到了同一个资源,也即具备了进程通信的前提。

命名管道也被称为 FIFO 文件,它是一种特殊类型的文件,它在文件系统中以文件名的形式存在,但是它的行为却和之前所讲的没有名字的管道(匿名管道)类似。

由于Linux中所有的事物都可被视为文件,所以对命名管道的使用也就变得与文件操作非常的统一,也使它的使用非常方便,同时我们也可以像平常的文件名一样在命令中使用。

1. Linux命令:mkfifo (创建 命名管道)

mkfifo [管道名]:创建命名管道
创建出来的文件类型以 p 开头

-:普通文件
d:目录文件(document)
l:链接文件(link)
p:管道文件(pipeline)

2. 函数 mkfifo

头文件:

#include <sys/types.h>
#include <sys/stat.h>

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

参数 pathname:

  • 文件路径


参数 mode:

  • 创建文件的权限,这里的权限会被系统的umask影响,所以我们若不想被系统的默认掩码影响,可以使用 函数 umask(0)

返回值:

  • 成功返回 0,失败返回 -1

3. 函数 unlink(删除 命名管道)

头文件:

#include <unistd.h>

int unlink(const char *path);

参数 pathname:

  • 需要删除的文件路径

返回值:

  • 成功返回 0,失败返回 -1

4. 函数 open(打开 FIFO 文件)

与打开其他文件一样,FIFO 文件也可以使用 open 调用来打开。注意,mkfifo 函数只是创建一个 FIFO 文件,要使用命名管道还需要将其打开。

需要注意的是一下两点:

  1. 就是程序不能以 O_RDWR 模式打开 FIFO 文件进行读写操作,而其行为也未明确定义。因为如一个管道以读/写方式打开,进程就会读回自己的输出,而我们通常使用 FIFO 只是为了单向的数据传递。

  2. 就是传递给 open 调用的是要是 FIFO 文件的路径名,而不是别的属性的文件。

打开FIFO文件通常有四种方式:

open(const char *path, O_RDONLY); // 1
open(const char *path, O_RDONLY | O_NONBLOCK); // 2
open(const char *path, O_WRONLY); // 3
open(const char *path, O_WRONLY | O_NONBLOCK); // 4

在 open 函数的调用的第二个参数中,选项 O_NONBLOCK 表示非阻塞,加上这个选项后,表示 open 调用是非阻塞的,如果没有这个选项,则表示 open 调用是阻塞的。

open调用的阻塞是什么一回事呢?

  • 对于以只读方式(O_RDONLY)打开的 FIFO 文件,如果 open 调用是阻塞的(即第二个参数为 O_RDONLY),除非有一个进程以写方式打开同一个 FIFO,否则它不会返回;如果 open 调用是非阻塞的的(即第二个参数为O_RDONLY | O_NONBLOCK),则即使没有其他进程以写方式打开同一个 FIFO 文件,open 调用将成功并立即返回。

  • 对于以只写方式(O_WRONLY)打开的 FIFO 文件,如果 open 调用是阻塞的(即第二个参数为 O_WRONLY),open 调用将被阻塞,直到有一个进程以只读方式打开同一个 FIFO 文件为止;如果 open 调用是非阻塞的(即第二个参数为 O_WRONLY | O_NONBLOCK),open 总会立即返回,但如果没有其他进程以只读方式打开同一个 FIFO 文件,open 调用将返回 -1,并且 FIFO 也不会被打开。

5. 命名管道代码案例

举例实现:利用命名管道,实现两个进程的连接。client 端输入,server 端同步实现接收。

makefile

.PHONY:all
all:server client

server:server.cc
	g++ -o $@ $^ -std=c++11
client:client.cc
	g++ -o $@ $^ -lncurses -std=c++11

.PHONY:clean
clean:
	rm -f server client

comm.hpp

#pragma once

#include <iostream>
#include <string>

#define NUM 1024

const std::string fifoname = "./fifo";
uint32_t mode = 0666; 

server.cc

#include <iostream>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "comm.hpp"


int main()
{
    // 1. 创建管道文件,一次创建即可
    umask(0); // 这个设置并不影响系统的默认配置,只会影响当前进程
    int n = mkfifo(fifoname.c_str(), mode);
    if(n != 0)
    {
        std::cout << errno << " : " << strerror(errno) << std::endl;
        return 1;
    }
    std::cout << "管道文件已创建" << std::endl;
    
    // 2. 让服务端直接开启管道文件
    int rfd = open(fifoname.c_str(), O_RDONLY);
    if(rfd < 0 )
    {
        std::cout << errno << " : " << strerror(errno) << std::endl;
        return 2;
    }
    std::cout << "成功打开管道文件,开始 ipc(进程间通信)" << std::endl;

    // 3. 正常通信
    char buffer[NUM];
    while(true)
    {
        buffer[0] = 0;
        ssize_t n = read(rfd, buffer, sizeof(buffer) - 1);
        if(n > 0)
        {
            buffer[n] = 0;
            //std::cout << "client# " << buffer << std::endl;
            printf("%c", buffer[0]);
            fflush(stdout);
        }
        else if(n == 0)
        {
            std::cout << "client 已经退出,我是 server,也退出了" << std::endl;
            break;
        }
        else 
        {
            std::cout << errno << " : " << strerror(errno) << std::endl;
            break;
        }
    }

    // 关闭不要的fd
    close(rfd);
	// 删除管道文件
    unlink(fifoname.c_str());

    return 0;
}

client.cc

#include <iostream>
#include <cstdio>
#include <cerrno>
#include <cstring>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "comm.hpp"

using namespace std;

int main()
{
    //1. 不需创建管道文件,只需要打开对应的文件即可(文件就是两个进程看到的相同的资源)
    int wfd = open(fifoname.c_str(), O_WRONLY);
    if(wfd < 0)
    {
        cerr << errno << ":" << strerror(errno) << endl;
        return 1;
    }

    // 2. 开始通信
    char buffer[NUM];
    while(true)
    {
    	// version(1)回车发送
        // cout << "请输入你的消息# ";
        // char *msg = fgets(buffer, sizeof(buffer), stdin);
        // assert(msg);
        // (void)msg;

        // version(2)不用进行回车就可以确认输入
        system("stty raw");     // 使终端驱动处于一次一字符模式
        int c = getchar();
        system("stty -raw");    // 使终端驱动回到一次一行模式

        //buffer[strlen(buffer) - 1] = 0;   // 解决输入的\n
        //if(strcasecmp(buffer, "quit") == 0) break;    // 写端退出,读端就也一起退出了
                                                        // strcasecmp 忽略大小写的字符串比较(带 n 是strncasecmp)

        ssize_t n = write(wfd, (char*)&c, sizeof(char));
        assert(n >= 0);
        (void)n;
    }

    close(wfd);
    return 0;
} 

🥰如果本文对你有些帮助,请给个赞或收藏,你的支持是对作者大大莫大的鼓励!!(✿◡‿◡) 欢迎评论留言~~


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值