国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Linux進(jìn)程間通信【命名管道】

這篇具有很好參考價(jià)值的文章主要介紹了Linux進(jìn)程間通信【命名管道】。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

?個(gè)人主頁: 北 海
??所屬專欄: Linux學(xué)習(xí)之旅
??操作環(huán)境: CentOS 7.6 阿里云遠(yuǎn)程服務(wù)器

Linux進(jìn)程間通信【命名管道】



??前言

命名管道通信屬于 IPC 的其中一種方式,作為管道家族,命名管道的特點(diǎn)就是 自帶同步與互斥機(jī)制、數(shù)據(jù)單向流通,與匿名管道不同的是:命名管道有自己的名字,因此可以被沒有血緣關(guān)系的進(jìn)程看到,意味著命名管道可以實(shí)現(xiàn)毫不相干的兩個(gè)獨(dú)立進(jìn)程間通信

Linux進(jìn)程間通信【命名管道】


???正文

1、什么是命名管道

簡(jiǎn)單,給匿名管道起個(gè)名字就變成了命名管道

那么如何給 匿名管道 起名字呢?

  • 結(jié)合文件系統(tǒng),給匿名管道這個(gè)純純的內(nèi)存文件分配 inode,將文件名與之構(gòu)建聯(lián)系,關(guān)鍵點(diǎn)在于不給它分配 Data block,因?yàn)樗且粋€(gè)純純的內(nèi)存文件,是不需要將數(shù)據(jù)刷盤到磁盤中的

可以將命名管道理解為 “掛名” 后的匿名管道,把匿名管道加入文件系統(tǒng)中,但僅僅是掛個(gè)名而已,目的就是為了讓其他進(jìn)程也能看到這個(gè)文件(文件系統(tǒng)中的文件可以被所有進(jìn)程看到)

因?yàn)闆]有 Data block,所以命名管道這個(gè)特殊文件大小為 0

1.1、創(chuàng)建及簡(jiǎn)單使用

命令管道的創(chuàng)建依賴于函數(shù) mkfifo,函數(shù)原型如下

Linux進(jìn)程間通信【命名管道】

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

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

關(guān)于 mkfifo 函數(shù)

組成部分 含義
返回值 int 創(chuàng)建成功返回 0,失敗返回 -1
參數(shù)1 const char *pathname 創(chuàng)建命名管道文件時(shí)的路徑+名字
參數(shù)2 mode_t mode 創(chuàng)建命令管道文件時(shí)的權(quán)限

對(duì)于參數(shù)1,既可以傳遞絕對(duì)路徑 /home/xxx/namePipeCode/fifo,也可以傳遞相對(duì)路徑 ./fifo,當(dāng)然絕對(duì)路徑更靈活,但也更長(zhǎng)

對(duì)于參數(shù)2,mode_t 其實(shí)就是對(duì) unsigned int 的封裝,等價(jià)于 uint32_t,而 mode 就是創(chuàng)建命名管道時(shí)的初始權(quán)限,實(shí)際權(quán)限需要經(jīng)過 umask 掩碼計(jì)算

不難發(fā)現(xiàn),mkfifomkdir 非常像,其實(shí) mkfifo 可以直接在命令行中運(yùn)行

創(chuàng)建一個(gè)名為 fifo 的命名管道文件

mkfifo fifo

Linux進(jìn)程間通信【命名管道】

成功解鎖了一種新的特殊類型文件:p 管道文件

Linux進(jìn)程間通信【命名管道】
出自:Linux 權(quán)限理解和學(xué)習(xí)

這個(gè)管道文件也非常特殊:大小為 0,從側(cè)面說明 管道文件就是一個(gè)純純的內(nèi)存級(jí)文件,有自己的上限,出現(xiàn)在文件系統(tǒng)中,只是單純掛個(gè)名而已

可以直接在命令行中使用命名管道:

  • echo 可以進(jìn)行數(shù)據(jù)寫入,可以重定向至 fifo
  • cat 可以進(jìn)行數(shù)據(jù)讀取,同樣也可以重定向于 fifo
  • 打開兩個(gè)終端窗口(兩個(gè)進(jìn)程),即可進(jìn)行通信

Linux進(jìn)程間通信【命名管道】

當(dāng)然也可以通過程序?qū)崿F(xiàn)兩個(gè)獨(dú)立進(jìn)程 IPC

思路:創(chuàng)建 服務(wù)端 server 和 客戶端 client 兩個(gè)獨(dú)立的進(jìn)程,服務(wù)端 server 創(chuàng)建并以 的方式打開管道文件,客戶端 client 的方式打開管道文件,打開后倆進(jìn)程可以進(jìn)程通信,通信結(jié)束后,由客戶端關(guān)閉 寫端(服務(wù)端 讀端 讀取到 0 后也關(guān)閉并刪除命令管道文件)

注意:

  • 當(dāng)管道文件不存在時(shí),文件會(huì)打開失敗,因此為了確保正常通信,需要先運(yùn)行服務(wù)端 server 創(chuàng)建管道文件
  • 服務(wù)端啟動(dòng)后,因?yàn)槭亲x端,所以會(huì)阻塞等待 客戶端(寫端)寫入數(shù)據(jù)
  • 客戶端寫入數(shù)據(jù)時(shí),因?yàn)?'\n' 也被讀取了,所以要去除此字符
  • 通信結(jié)束后,需要服務(wù)端主動(dòng)刪除管道文件
unlink 命令管道文件名	//刪除管道文件

為了讓服務(wù)端和客戶端能享有同一個(gè)文件名,可以創(chuàng)建一個(gè)公共頭文件 common.h,其中存儲(chǔ) 命名管道文件名及默認(rèn)權(quán)限等公有信息

公共資源 common.h

#pragma once

#include <iostream>
#include <string>

std::string fifo_name = "./fifo";   //管道名
uint32_t mode = 0666;   //權(quán)限

服務(wù)端 server.cc

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

using namespace std;

int main()
{
    // 服務(wù)端
    // 1、創(chuàng)建命名管道文件
    int ret = mkfifo(fifo_name.c_str(), mode);
    if (ret < 0)
    {
        cerr << "mkfifo fail! errno: " << errno << " | " << strerror(errno) << endl;
        exit(0);
    }

    // 2、以讀的方式打開文件
    int rfd = open(fifo_name.c_str(), O_RDONLY);
    if (rfd < 0)
    {
        cerr << "open fail! errno: " << errno << " | " << strerror(errno) << endl;
        exit(0);
    }

    // 3、讀取數(shù)據(jù)
    while (true)
    {
        char buff[64];
        int n = read(rfd, buff, sizeof(buff) - 1);
        buff[n] = '\0';

        if (n > 0)
        {
            cout << "Server get message# " << buff << endl;
        }
        else if (n == 0)
        {
            cout << "寫端關(guān)閉,讀端讀取到0,終止讀端" << endl;
            break;
        }
        else
        {
            cout << "讀取異常" << endl;
            break;
        }
    }

    close(rfd);
    unlink(fifo_name.c_str());  //刪除命名管道文件

    return 0;
}

客戶端 client.cc

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

using namespace std;

int main()
{
    // 客戶端
    // 1、打開文件
    int wfd = open(fifo_name.c_str(), O_WRONLY);
    if (wfd < 0)
    {
        cerr << "open fail! errno: " << errno << " | " << strerror(errno) << endl;
        exit(0);
    }

    // 2、寫入數(shù)據(jù),進(jìn)行通信
    char buff[64] = {0};
    while (true)
    {
        cout << "Client send message# ";
        fgets(buff, sizeof(buff) - 1, stdin);
        buff[strlen(buff) - 1] = '\0'; // 去除 '\n'

        if (strcasecmp("exit", buff) == 0)
            break;

        write(wfd, buff, strlen(buff));
    }

    close(wfd);

    return 0;
}

注:strcasecmp 是一個(gè)字符串比較函數(shù),無論字符串大小寫,都能進(jìn)行比較

運(yùn)行效果:

Linux進(jìn)程間通信【命名管道】

所以 掛了名之后的命名管道是如何實(shí)現(xiàn)獨(dú)立進(jìn)程間 IPC 的呢?

1.2、命名管道的工作原理

把視角拉回文件系統(tǒng):當(dāng)重復(fù)多次打開同一個(gè)文件時(shí),并不會(huì)費(fèi)力的打開多次,而且在第一次打開的基礎(chǔ)上,對(duì) struct file 結(jié)構(gòu)體中的引用計(jì)數(shù) ++,所以對(duì)于同一個(gè)文件,不同進(jìn)程打開了,看到的就是同一個(gè)

  • 具體例子:顯示器文件(stdout)只有一個(gè)吧,是不是所有進(jìn)程都可以同時(shí)進(jìn)行寫入?
  • 同理,命名管道文件也是如此,先創(chuàng)建出文件,在文件系統(tǒng)中掛個(gè)名,然后讓獨(dú)立的進(jìn)程以不同的方式打開同一個(gè)命名管道文件,比如進(jìn)程 A 以只讀的方式打開,進(jìn)程 B 以只寫的方式打開,那么此時(shí)進(jìn)程 B 就可以向進(jìn)程 A 寫文件,即 IPC

Linux進(jìn)程間通信【命名管道】

因?yàn)槊艿肋m用于獨(dú)立的進(jìn)程間 IPC,所以無論是讀端和寫端,進(jìn)程 A、進(jìn)程 B 為其分配的 fd 是一致的,都是 3

  • 如果是匿名管道,因?yàn)槭且揽坷^承才看到同一文件的,所以讀端和寫端 fd 不一樣

所以 命名管道匿名管道 還是有區(qū)別的

1.3、命名管道與匿名管道的區(qū)別

不同點(diǎn):

  • 匿名管道只能用于具有血緣關(guān)系的進(jìn)程間通信;而命名管道不講究,誰都可以用
  • 匿名管道直接通過 pipe 函數(shù)創(chuàng)建使用;而命名管道需要先通過 mkfifo 函數(shù)創(chuàng)建,然后再通過 open 打開使用
  • 出現(xiàn)多條匿名管道時(shí),可能會(huì)出現(xiàn)寫端 fd 重復(fù)繼承的情況;而命名管道不會(huì)出現(xiàn)這種情況

在其他方面,匿名管道與命名管道幾乎一致

  • 兩個(gè)都屬于管道家族,都是最古老的進(jìn)程間通信方式,都自帶同步與互斥機(jī)制,提供的都是流式數(shù)據(jù)傳輸

2、命名管道的特點(diǎn)及特殊場(chǎng)景

命名管道的特點(diǎn)及特殊場(chǎng)景與匿名管道完全一致,這里簡(jiǎn)單回顧下,詳細(xì)內(nèi)容可跳轉(zhuǎn)至 《Linux進(jìn)程間通信【匿名管道】》

2.1、特點(diǎn)

可以簡(jiǎn)單總結(jié)為:

  • 管道是半雙工通信
  • 管道生命隨進(jìn)程而終止
  • 命名管道任意多個(gè)進(jìn)程間通信
  • 管道提供的是流式數(shù)據(jù)傳輸服務(wù)
  • 管道自帶 同步與互斥 機(jī)制

2.2、四種特殊場(chǎng)景

四種場(chǎng)景分別為

  1. 管道為空時(shí),讀端阻塞,等待寫端寫入數(shù)據(jù)
  2. 管道為滿時(shí),寫端阻塞,等待讀端讀取數(shù)據(jù)
  3. 進(jìn)程通信時(shí),關(guān)閉讀端,OS 發(fā)出 13 號(hào)信號(hào) SIGPIPE 終止寫端進(jìn)程
  4. 進(jìn)程通信時(shí),關(guān)閉寫端,讀端讀取到 0 字節(jié)數(shù)據(jù),可以借此判斷終止讀端

3、命名管道實(shí)操

以下是一些使用命名管道實(shí)現(xiàn)的簡(jiǎn)單小程序,主要目的是為了熟悉命名管道的使用

3.1、實(shí)現(xiàn)文件拷貝

下載應(yīng)用的本質(zhì)是在下載文件,將服務(wù)器看作寫端,自己的電腦看作讀端,那么 下載 這個(gè)動(dòng)作本質(zhì)上就是 IPC,不過是在網(wǎng)絡(luò)層面實(shí)現(xiàn)的

我們可以利用 命名管道實(shí)現(xiàn)不同進(jìn)程間 IPC,即進(jìn)程從文件中讀取并寫入一批數(shù)據(jù),另一個(gè)進(jìn)程一次讀取一批數(shù)據(jù)并保存至新文件中,這樣就實(shí)現(xiàn)了文件的拷貝

目標(biāo):利用命名管道,向空文件 target.txt 中寫入數(shù)據(jù),即拷貝源文件 file.txt

公共資源 common.h

#pragma once

#include <iostream>
#include <string>

std::string fifo_name = "./fifo";   //管道名
uint32_t mode = 0666;   //權(quán)限

服務(wù)端(寫端) server.cc 提供文件拷貝服務(wù)

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

using namespace std;

int main()
{
    // 服務(wù)端
    // 1、打開文件
    int wfd = open(fifo_name.c_str(), O_WRONLY);
    if (wfd < 0)
    {
        cerr << "open fail! errno: " << errno << " | " << strerror(errno) << endl;
        exit(0);
    }

    // 2、打開源文件
    FILE *fp = fopen("file.txt", "r");
    if (fp == NULL)
    {
        cerr << "fopen fail! errno: " << errno << " | " << strerror(errno) << endl;
        exit(0);
    }

    // 3、讀取源文件數(shù)據(jù)
    char buff[1024];
    int n = fread(buff, sizeof(char), sizeof(buff), fp);

    //IPC區(qū)域
    // 4、寫入源文件至命名管道
    write(wfd, buff, strlen(buff));
    cout << "服務(wù)端已向管道寫入: " << n << "字節(jié)的數(shù)據(jù)" << endl;
    //IPC區(qū)域

    fclose(fp);
    fp = nullptr;
    close(wfd);
    return 0;
}

客戶端(讀端) client.cc 從服務(wù)端中拷貝文件(下載)

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

using namespace std;

int main()
{
    // 客戶端
    // 1、創(chuàng)建命名管道文件
    int ret = mkfifo(fifo_name.c_str(), mode);
    if (ret < 0)
    {
        cerr << "mkfifo fail! errno: " << errno << " | " << strerror(errno) << endl;
        exit(0);
    }

    // 2、以讀的方式打開管道文件
    int rfd = open(fifo_name.c_str(), O_RDONLY);
    if (rfd < 0)
    {
        cerr << "open fail! errno: " << errno << " | " << strerror(errno) << endl;
        exit(0);
    }

    // 3、打開目標(biāo)文件
    FILE *fp = fopen("target.txt", "w");
    if (fp == NULL)
    {
        cerr << "fopen fail! errno: " << errno << " | " << strerror(errno) << endl;
        exit(0);
    }

    //IPC區(qū)域
    // 4、讀取數(shù)據(jù)
    char buff[1024];
    int n = read(rfd, buff, sizeof(buff) - 1);
    buff[n] = '\0';

    if (n > 0)
        cout << "客戶端已從管道讀取: " << n << "字節(jié)的數(shù)據(jù)" << endl;
    else if (n == 0)
        cout << "寫端關(guān)閉,讀端讀取到0,終止讀端" << endl;
    else
        cout << "讀取異常" << endl;
    //IPC區(qū)域

    //5、寫入目標(biāo)文件,完成拷貝
    fwrite(buff, sizeof(char), strlen(buff), fp);
    cout << "客戶端已成功從服務(wù)端下載(拷貝)了文件數(shù)據(jù)" << endl;

    fclose(fp);
    close(rfd);
    unlink(fifo_name.c_str()); // 刪除命名管道文件
    return 0;
}

拷貝結(jié)果:成功拷貝

Linux進(jìn)程間通信【命名管道】

此時(shí) 服務(wù)端是寫端,客戶端是讀端,實(shí)現(xiàn)的是 下載服務(wù);當(dāng) 服務(wù)端是讀端,客戶端是寫端時(shí),實(shí)現(xiàn)的就是 上傳服務(wù),搞兩條管道就能模擬實(shí)現(xiàn)簡(jiǎn)單的 數(shù)據(jù)雙向傳輸服務(wù)

注意:創(chuàng)建管道文件后,無論先啟動(dòng)讀端,還是先啟動(dòng)寫端,都要阻塞式的等待另一方進(jìn)行交互

3.2、實(shí)現(xiàn)進(jìn)程控制

Linux 匿名管道 IPC 中,我們實(shí)現(xiàn)了一個(gè)簡(jiǎn)易版的進(jìn)程控制程序,原理是通過多條匿名管道實(shí)現(xiàn)父進(jìn)程對(duì)多個(gè)子進(jìn)程執(zhí)行任務(wù)分配

匿名管道用于有血緣關(guān)系間 IPC,命名管道也可以

所以我們可以把上一篇文章中的 匿名管道換為命名管道,一樣可以實(shí)現(xiàn)通信

任務(wù)池 Task.hpp

#include <iostream>
#include <string>
#include <functional>
#include <unordered_map>
#include <unistd.h>

using namespace std;

void PrintLOG()
{
    cout << "PID: " << getpid() << " 正在執(zhí)行打印日志的任務(wù)…" << endl;
}

void InsertSQL()
{
    cout << "PID: " << getpid() << " 正在執(zhí)行數(shù)據(jù)庫插入的任務(wù)…" << endl;
}

void NetRequst()
{
    cout << "PID: " << getpid() << " 正在執(zhí)行網(wǎng)絡(luò)請(qǐng)求的任務(wù)…" << endl;
}

class Task
{
public:
    Task()
    {
        // 裝載任務(wù)
        _tt = {{"打印日志", PrintLOG}, {"數(shù)據(jù)庫插入", InsertSQL}, {"網(wǎng)絡(luò)請(qǐng)求", NetRequst}};
    }

    // 展示任務(wù)
    void showTask()
    {
        cout << "目前可用任務(wù)有:[";
        for (auto e : _tt)
            cout << e.first << " ";
        cout << "]" << endl;
        cout << "輸入 退出 以終止程序" << endl;
    }

    // 執(zhí)行任務(wù)
    void Execute(const string &task)
    {
        if (_tt.count(task) == 0)
        {
            cerr << "沒有這個(gè)任務(wù):" << task << endl;
        }
        else
        {
            _tt[task](); // 函數(shù)對(duì)象調(diào)用
        }
    }

private:
    unordered_map<string, function<void(void)>> _tt;
};

控制程序 namePipeCtrl.cc 包括進(jìn)程、管道創(chuàng)建,任務(wù)執(zhí)行與進(jìn)程等待

#include <iostream>
#include <string>
#include <vector>
#include <cassert>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "Task.hpp"

using namespace std;

enum
{
    NAME_SIZE = 64
};

// 子進(jìn)程基本信息類
class ProcINfo
{

public:
    ProcINfo(pid_t pid = pid_t(), int wfd = int())
        : _pid(pid), _wfd(wfd), _num(_cnt++)
    {
        char buff[NAME_SIZE] = {0};
        snprintf(buff, NAME_SIZE, "Process %d | pid:wfd [%d:%d]", _num, _pid, _wfd);
        _name = string(buff);
    }

    pid_t _pid;
    int _wfd;
    int _num;
    string _name;
    static int _cnt;
};

int ProcINfo::_cnt = 0;

// 進(jìn)程控制類
class ProcCtrl
{
public:
    ProcCtrl(int num = 3, mode_t mode = 0666)
        : _num(num), _mode(mode)
    {
        // 根據(jù) _num 創(chuàng)建命名管道及子進(jìn)程
        CreatPipeAndProc();
    }

    ~ProcCtrl()
    {
        waitProc();
    }

    // 創(chuàng)建管道及進(jìn)程
    void CreatPipeAndProc()
    {
        // 因?yàn)槭抢^承的,所以也要注意寫端重復(fù)繼承問題
        vector<int> fds;
        for (int i = 0; i < _num; i++)
        {
            // 步驟:創(chuàng)建管道,存入 _vst
            char pipeNameBUff[NAME_SIZE]; // 管道名緩沖區(qū)
            snprintf(pipeNameBUff, NAME_SIZE, "./fifo-%d", i);

            int ret = mkfifo(pipeNameBUff, _mode);
            assert(ret != -1);
            (void)ret;

            _vst.push_back(string(pipeNameBUff));

            // 創(chuàng)建子進(jìn)程,讓子進(jìn)程以只讀的方式打開管道文件
            pid_t id = fork();
            if (id == 0)
            {
                // 子進(jìn)程內(nèi)
                // 先關(guān)閉不必要的寫端
                for (auto e : fds)
                    close(e);

                // 打開管道文件,并進(jìn)入任務(wù)等待默認(rèn)(讀端阻塞)
                int rfd = open(_vst[i].c_str(), O_RDONLY);
                assert(rfd != -1);
                (void)rfd;

                waitCommand(rfd);

                close(rfd); // 關(guān)閉讀端
                exit(0);
            }

            // 父進(jìn)程以寫打開管道,保存 fd 信息
            int wfd = open(_vst[i].c_str(), O_WRONLY);
            assert(wfd != -1);
            (void)wfd;

            // 注冊(cè)子進(jìn)程信息
            _vpt.push_back(ProcINfo(id, wfd));
            fds.push_back(wfd);
        }
    }

    // 子進(jìn)程等待任務(wù)派發(fā)
    void waitCommand(int rfd)
    {
        while (true)
        {
            char buff[NAME_SIZE] = {0};
            int n = read(rfd, buff, sizeof(buff) - 1);

            buff[n] = '\0';

            if (n > 0)
            {
                Task().Execute(string(buff));
            }
            else if (n == 0)
            {
                cerr << "讀端讀取到 0,寫端已關(guān)閉,讀端也即將關(guān)閉" << endl;
                break;
            }
            else
            {
                cerr << "子進(jìn)程讀取異常!" << endl;
                break;
            }
        }
    }

    // 展示可選進(jìn)程
    void showProc()
    {
        cout << "目前可用進(jìn)程有:[";
        int i = 0;
        for (i = 0; i < _num - 1; i++)
            cout << i << "|";
        cout << i << "]" << endl;
    }

    // 下達(dá)任務(wù)給子進(jìn)程
    void ctrlProc()
    {
        while (true)
        {
            cout << "==========================" << endl;
            int n = 0;
            do
            {
                showProc();
                cout << "請(qǐng)選擇子進(jìn)程:> ";
                cin >> n;
            } while (n < 0 || n >= _num);

            Task().showTask();
            string taskName;
            cout << "請(qǐng)選擇任務(wù):> ";
            cin >> taskName;

            if (taskName == "退出")
                break;

            // 將信息通過命名管道寫給子進(jìn)程
            cout << "選擇進(jìn)程 ->" << _vpt[n]._name << " 執(zhí)行 " << taskName << " 任務(wù)" << endl;
            write(_vpt[n]._wfd, taskName.c_str(), taskName.size());
            sleep(1);
        }
    }

    // 關(guān)閉寫端、刪除文件、等待子進(jìn)程退出
    void waitProc()
    {
        for (int i = 0; i < _num; i++)
        {
            close(_vpt[i]._wfd);               // 關(guān)閉寫端
            unlink(_vst[i].c_str());           // 關(guān)閉管道文件
            waitpid(_vpt[i]._pid, nullptr, 0); // 等待子進(jìn)程
        }

        cout << "所有子進(jìn)程已回收" << endl;
    }

private:
    vector<ProcINfo> _vpt; // 子進(jìn)程信息表
    vector<string> _vst;   // 命名管道信息表
    int _num;              // 子進(jìn)程數(shù)/命名管道數(shù)
    mode_t _mode;          // 命名管道文件的權(quán)限
};

int main()
{
    ProcCtrl p1;
    p1.ctrlProc();
    return 0;
}

執(zhí)行結(jié)果如下:

Linux進(jìn)程間通信【命名管道】

關(guān)于 父子進(jìn)程間使用命名管道通信 值得注意的問題:

  1. 在命名管道創(chuàng)建后,需要先創(chuàng)建子進(jìn)程,讓子進(jìn)程打開【讀端或?qū)懚恕?,然后才讓父進(jìn)程打開【寫端或讀端】,這是因?yàn)榧偃缦茸尭高M(jìn)程打開【寫端或讀端】,那么此時(shí)父進(jìn)程就會(huì)進(jìn)入【阻塞】狀態(tài),導(dǎo)致無法創(chuàng)建子進(jìn)程,自然也就無法再打開【讀端或?qū)懚恕浚凰哉_做法是先讓子進(jìn)程打開,即使子進(jìn)程【阻塞】了,父進(jìn)程也還能運(yùn)行。不要讓【阻塞】阻礙子進(jìn)程的創(chuàng)建
  2. 子進(jìn)程繼承都存在的問題:寫端重復(fù)繼承,因此需要關(guān)閉不必要的寫端 fd

關(guān)于問題一的理解可以看看下面這兩張圖:

錯(cuò)誤用法: 父進(jìn)程先打開【寫端或讀端】,再創(chuàng)建子進(jìn)程,最后才讓子進(jìn)程打開【讀端或?qū)懚恕?/p>

Linux進(jìn)程間通信【命名管道】

正確用法: 先創(chuàng)建子進(jìn)程,讓子進(jìn)程打開【讀端或?qū)懚恕?,再讓父進(jìn)程打開【寫端或讀端】

Linux進(jìn)程間通信【命名管道】

3.3、實(shí)現(xiàn)進(jìn)程遙控(配合簡(jiǎn)易版 bash)

利用命名管道就可以遠(yuǎn)程遙控,原理很簡(jiǎn)單:簡(jiǎn)易版 bash 會(huì)等待命令輸入,將輸入源換成命名管道讀端,再創(chuàng)建一個(gè)獨(dú)立進(jìn)程,作為命名管道的寫端,此時(shí)就可以實(shí)現(xiàn)遠(yuǎn)程遙控進(jìn)程,執(zhí)行不同的指令

這里直接用之前寫好的 簡(jiǎn)易版 bash,關(guān)于 簡(jiǎn)易版 bash 的具體實(shí)現(xiàn)可以看看這篇文章 《Linux模擬實(shí)現(xiàn)【簡(jiǎn)易版bash】

步驟:

  • 創(chuàng)建命名管道
  • bash 改裝,打開命名管道文件,作為 讀端
  • 創(chuàng)建獨(dú)立進(jìn)程,打開命名管道文件,作為 寫端
  • 進(jìn)行 IPC,發(fā)送命令給 bash 執(zhí)行

公共資源 common.h

#pragma once

#include <iostream>
#include <string>

std::string fifo_name = "./fifo";   //管道名
uint32_t mode = 0666;   //權(quán)限

簡(jiǎn)易版bash mybash.cc

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <string.h>
#include <assert.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "common.h"

using namespace std;

#define COM_SIZE 1024
#define ARGV_SIZE 64
#define DEF_CHAR " "

void split(char *argv[ARGV_SIZE], char *ps)
{
    assert(argv && ps);

    // 調(diào)用 C語言 中的 strtok 函數(shù)分割字符串
    int pos = 0;
    argv[pos++] = strtok(ps, DEF_CHAR); // 有空格就分割
    while (argv[pos++] = strtok(NULL, DEF_CHAR))
        ; // 不斷分割

    argv[pos] = NULL; // 確保安全
}

void showEnv()
{
    extern char **environ; // 使用當(dāng)前進(jìn)行的環(huán)境變量表
    int pos = 0;
    for (; environ[pos]; printf("%s\n", environ[pos++]))
        ;
}

// 枚舉類型,用于判斷不同的文件打開方式
enum redir
{
    REDIR_INPUT = 0,
    REDIR_OUTPUT,
    REDIR_APPEND,
    REDIR_NONE
} redir_type = REDIR_NONE; // 創(chuàng)建對(duì)象 redir_type,默認(rèn)為 NONE

// 檢查是否出現(xiàn)重定向符
char *checkDir(char *command)
{
    // 從右往左遍歷,遇到 > >> < 就置為 '\0'
    size_t end = strlen(command); // 與返回值相匹配
    char *ps = command + end;     // 為了避免出現(xiàn)無符號(hào)-1,這里采取錯(cuò)位的方法
    while (end != 0)
    {
        if (command[end - 1] == '>')
        {
            if (command[end - 2] == '>')
            {
                command[end - 2] = '\0';
                redir_type = REDIR_APPEND;
                return ps;
            }

            command[end - 1] = '\0';
            redir_type = REDIR_OUTPUT;
            return ps;
        }
        else if (command[end - 1] == '<')
        {
            command[end - 1] = '\0';
            redir_type = REDIR_INPUT;
            return ps;
        }

        // 如果不是空格,就可以更新 ps指向
        if (*(command + end - 1) != ' ')
            ps = command + end - 1;

        end--;
    }

    return NULL; // 如果沒有重定向符,就返回空
}

int main()
{
    char myEnv[COM_SIZE][ARGV_SIZE]; // 大小與前面有關(guān)
    int env_pos = 0;                 // 專門維護(hù)緩沖區(qū)
    int exit_code = 0;               // 保存退出碼的全局變量

    // 2023.6.7 更新
    // 創(chuàng)建管道文件
    int ret = mkfifo(fifo_name.c_str(), mode);
    assert(ret != -1);
    (void)ret;

    // 打開管道文件
    int rfd = open(fifo_name.c_str(), O_RDONLY);
    assert(rfd != -1);
    (void)rfd;

    // 這是一個(gè)始終運(yùn)行的程序:bash
    while (1)
    {
        char command[COM_SIZE]; // 存放指令的數(shù)組(緩沖區(qū))

        // 打印提示符
        printf("[User@myBash default]$ ");
        fflush(stdout);

        // 讀取指令
        //從管道中讀取
        int n = read(rfd, command, COM_SIZE - 1);

        if(n == 0)
        {
            cout << "寫端已關(guān)閉,讀端也即將關(guān)閉" << endl;
            break;
        }

        command[n] = '\0';
        cout << command << endl;

        // 重定向
        // 在獲取指令后進(jìn)行判斷
        // 如果成立,則獲取目標(biāo)文件名 filename
        char *filename = checkDir(command);

        // 指令分割
        // 將連續(xù)的指令分割為 argv 表
        char *argv[ARGV_SIZE];
        split(argv, command);

        // 特殊處理
        // 顏色高亮處理,識(shí)別是否為 ls 指令
        if (strcmp(argv[0], "ls") == 0)
        {
            int pos = 0;
            while (argv[pos++])
                ;                                   // 找到尾
            argv[pos - 1] = (char *)"--color=auto"; // 添加此字段
            argv[pos] = NULL;                       // 結(jié)尾
        }

        // 目錄間移動(dòng)處理
        if (strcmp(argv[0], "cd") == 0)
        {
            // 直接調(diào)用接口,然后 continue 不再執(zhí)行后續(xù)代碼
            if (strcmp(argv[1], "~") == 0)
                chdir("/home"); // 回到家目錄
            else if (strcmp(argv[1], "-") == 0)
                chdir(getenv("OLDPWD"));
            else if (argv[1])
                chdir(argv[1]); // argv[1] 中就是路徑
            continue;           // 終止此次循環(huán)
        }

        // 環(huán)境變量相關(guān)
        if (strcmp(argv[0], "export") == 0)
        {
            if (argv[1])
            {
                strcpy(myEnv[env_pos], argv[1]);
                putenv(myEnv[env_pos++]);
            }
            continue; // 一樣需要提前結(jié)束循環(huán)
        }

        // 環(huán)境變量表
        if (strcmp(argv[0], "env") == 0)
        {
            showEnv(); // 調(diào)用函數(shù),打印父進(jìn)程的環(huán)境變量表
            continue;  // 提前結(jié)束本次循環(huán)
        }

        // echo 相關(guān)
        // 只有 echo $ 才做特殊處理(環(huán)境變量+退出碼)
        if (strcmp(argv[0], "echo") == 0 && argv[1][0] == '$')
        {
            if (argv[1] && argv[1][0] == '$')
            {
                if (argv[1][1] == '?')
                    printf("%d\n", exit_code);
                else
                    printf("%s\n", getenv(argv[1] + 1));
            }

            continue;
        }

        // 子進(jìn)程進(jìn)行程序替換
        pid_t id = fork();
        if (id == 0)
        {
            // 判斷是否需要進(jìn)行重定向
            if (redir_type == REDIR_INPUT)
            {
                int fd = open(filename, O_RDONLY);
                dup2(fd, 0); // 更改輸入,讀取文件 filename
            }
            else if (redir_type == REDIR_OUTPUT)
            {
                int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
                dup2(fd, 1); // 寫入
            }
            else if (redir_type == REDIR_APPEND)
            {
                int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
                dup2(fd, 1); // 追加
            }

            // 直接執(zhí)行程序替換,這里使用 execvp
            execvp(argv[0], argv);

            exit(168); // 替換失敗后返回
        }

        // 父進(jìn)程等待子進(jìn)程終止
        int status = 0;
        waitpid(id, &status, 0); // 在等待隊(duì)列中阻塞
        exit_code = WEXITSTATUS(status);
        if (WIFEXITED(status))
        {
            // 假如程序替換失敗
            if (exit_code == 168)
                printf("%s: Error - %s\n", argv[0], "The directive is not yet defined");
        }
        else
            printf("process run fail! [code_dump]:%d [exit_signal]:%d\n", (status >> 7) & 1, status & 0x7F); // 子進(jìn)程異常終止的情況
    }

    //關(guān)閉管道文件
    close(rfd);
    unlink(fifo_name.c_str());
    return 0;
}

進(jìn)程控制端 namePipeCtrl.cc

#include <iostream>
#include <cassert>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "common.h"

using namespace std;

int main()
{
    // 打開管道文件 --- 只寫
    int wfd = open(fifo_name.c_str(), O_WRONLY);
    assert(wfd != -1);
    (void)wfd;

    char buff[64];
    while (true)
    {
        cout << "遠(yuǎn)程發(fā)送指令:> ";
        fgets(buff, sizeof(buff) - 1, stdin);
        buff[strlen(buff) - 1] = '\0';  // 去除 '\n'

        if (strcasecmp("exit", buff) == 0)
            break;

        // 向管道寫入數(shù)據(jù)
        write(wfd, buff, strlen(buff));
    }

    close(wfd);
    return 0;
}

實(shí)際效果如下:

Linux進(jìn)程間通信【命名管道】

注意:在進(jìn)行指令處理時(shí),需要注意 '\n',不能把 '\n' 帶入進(jìn)程替換中

3.4、實(shí)現(xiàn)字符實(shí)時(shí)讀取

回車 '\n' 這個(gè)東西很難處理,那么有沒有一種方式,能實(shí)現(xiàn)不輸入回車也能寫入數(shù)據(jù)至管道中呢?答案是有的

比如以下代碼,可以實(shí)現(xiàn)特殊化讀取,即 不需要特定條件觸發(fā)緩沖區(qū)沖刷,實(shí)時(shí)寫入字符

公共資源 common.h

#pragma once

#include <iostream>
#include <string>

std::string fifo_name = "./fifo";   //管道名
uint32_t mode = 0666;   //權(quán)限

服務(wù)端 server.cc 實(shí)時(shí)讀取字符

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

using namespace std;

int main()
{
    // 服務(wù)端
    // 1、創(chuàng)建命名管道文件
    int ret = mkfifo(fifo_name.c_str(), mode);
    if (ret < 0)
    {
        cerr << "mkfifo fail! errno: " << errno << " | " << strerror(errno) << endl;
        exit(0);
    }

    // 2、以讀的方式打開文件
    int rfd = open(fifo_name.c_str(), O_RDONLY);
    if (rfd < 0)
    {
        cerr << "open fail! errno: " << errno << " | " << strerror(errno) << endl;
        exit(0);
    }

    // 3、讀取數(shù)據(jù)
    while (true)
    {
        char buff[64];
        int n = read(rfd, buff, sizeof(buff) - 1);
        buff[n] = '\0';

        if (n > 0)
        {
            buff[n] = 0;
            printf("%c", buff[0]);
            fflush(stdout);
        }
        else if (n == 0)
        {
            cout << "寫端關(guān)閉,讀端讀取到0,終止讀端" << endl;
            break;
        }
        else
        {
            cout << "讀取異常" << endl;
            break;
        }
    }

    close(rfd);
    unlink(fifo_name.c_str()); // 刪除命名管道文件

    return 0;
}

客戶端 client.cc 實(shí)時(shí)發(fā)送字符

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

using namespace std;

int main()
{
    // 客戶端
    // 1、打開文件
    int wfd = open(fifo_name.c_str(), O_WRONLY);
    if (wfd < 0)
    {
        cerr << "open fail! errno: " << errno << " | " << strerror(errno) << endl;
        exit(0);
    }

    // 2、寫入數(shù)據(jù),進(jìn)行通信
    char buff[64] = {0};
    while (true)
    {
        system("stty raw");
        int c = getchar();
        system("stty -raw");

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

    close(wfd);

    return 0;
}

實(shí)時(shí)讀取字符的效果如下:

Linux進(jìn)程間通信【命名管道】

本文中涉及的所有代碼均在此倉庫中:《命名管道博客倉庫》


??總結(jié)

以上就是本次關(guān)于 Linux 進(jìn)程間通信之命名管道的全部?jī)?nèi)容了,作為匿名管道的兄弟,命名管道具備匿名管道的大部分特性,使用方法也基本一致,不過二者在創(chuàng)建和打開方式上各有不同:匿名管道簡(jiǎn)單,但只能用于具有血緣關(guān)系進(jìn)程間通信,命名管道雖麻煩些,但適用于所有進(jìn)程間通信場(chǎng)景;在本文的最后,使用命名管道實(shí)現(xiàn)了幾個(gè)簡(jiǎn)單的小程序,這些小程序的本質(zhì)都是一樣的:創(chuàng)建命名管道 -> 打開命名管道 -> 通信 -> 關(guān)閉命名管道,掌握其中一個(gè)即可融會(huì)貫通


Linux進(jìn)程間通信【命名管道】

文章來源地址http://www.zghlxwxcb.cn/news/detail-475147.html

相關(guān)文章推薦

Linux進(jìn)程間通信【匿名管道】

Linux基礎(chǔ)IO【軟硬鏈接與動(dòng)靜態(tài)庫】

Linux基礎(chǔ)IO【深入理解文件系統(tǒng)】

Linux【模擬實(shí)現(xiàn)C語言文件流】

Linux基礎(chǔ)IO【重定向及緩沖區(qū)理解】

Linux基礎(chǔ)IO【文件理解與操作】

到了這里,關(guān)于Linux進(jìn)程間通信【命名管道】的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 【Linux】進(jìn)程間通信——命名管道

    【Linux】進(jìn)程間通信——命名管道

    匿名管道只能用來進(jìn)行進(jìn)程間通信,讓具有血緣關(guān)系的進(jìn)程進(jìn)行通信 讓毫不相關(guān)的進(jìn)程之間進(jìn)行通信,就需要采用命名管道通信 因?yàn)樵撐募形募Q的,而且必須要有,所以叫做命名管道 mkfifo函數(shù) 輸入 man mkfifo 指令 制作一個(gè) FIFOS ,表示命名管道 mkfifo fifo 制作一個(gè)管道

    2023年04月15日
    瀏覽(19)
  • 【Linux】進(jìn)程間通信 -- 命名管道 | mkfifo調(diào)用

    【Linux】進(jìn)程間通信 -- 命名管道 | mkfifo調(diào)用

    client.cpp : server.cpp : 然后創(chuàng)建 Makefile 使得我們更方便的去編譯: 這樣我們使用一條指令編譯兩個(gè)文件: 我們可以發(fā)現(xiàn)我們創(chuàng)建的 named_pipe 是以 p 開頭而且有自己獨(dú)立的 inode ,說明它是一個(gè) 獨(dú)立的管道文件 我們執(zhí)行下面腳本,主要的功能就是使用 echo 循環(huán)輸出 hello world! 到管

    2024年02月13日
    瀏覽(19)
  • Linux通信--構(gòu)建進(jìn)程通信的 方案之管道(下)|使用匿名管道實(shí)現(xiàn)功能解耦|命名管道實(shí)現(xiàn)serve&client通信

    Linux通信--構(gòu)建進(jìn)程通信的 方案之管道(下)|使用匿名管道實(shí)現(xiàn)功能解耦|命名管道實(shí)現(xiàn)serve&client通信

    文章目錄 一、管道的應(yīng)用實(shí)例-父進(jìn)程喚醒子進(jìn)程,子進(jìn)程執(zhí)行某種任務(wù) 二、命名管道 1.創(chuàng)建一個(gè)命名管道 2.匿名管道與命名管道的區(qū)別 3.命名管道的打開規(guī)則 4.用命名管道實(shí)現(xiàn)serverclient通信 后續(xù)將源碼上傳到gitee,上傳后修改鏈接。 管道應(yīng)用的一個(gè)限制就是只能具有共同祖

    2024年02月10日
    瀏覽(26)
  • 進(jìn)程間通信-命名管道

    進(jìn)程間通信-命名管道

    ????????先前已經(jīng)了解了匿名管道,但是這是適用于有血緣關(guān)系的進(jìn)程間,如果無血緣關(guān)系的進(jìn)程要實(shí)現(xiàn)通信, 此時(shí)需要有另一種通信方案-命名管道。為什么命名管道可以用于無血緣關(guān)系的進(jìn)程間通信,什么是命名管道,為什么說它是有名字的,后面我們會(huì)一一了解。

    2024年01月20日
    瀏覽(20)
  • 進(jìn)程間通信(命名管道)

    進(jìn)程間通信(命名管道)

    目錄: ?????????? 1.命名管道 ?????????? 2.創(chuàng)建命名管道 --------------------------------------------------------------------------------------------------------------------------------- 1.命名管道 1.管道的一個(gè)應(yīng)用限制就是只能在具有共同祖先(具有親緣關(guān)系)的進(jìn)程間通信 2.如果我們想在不相

    2024年02月06日
    瀏覽(20)
  • 進(jìn)程間通信之利用命名管道進(jìn)行通信

    進(jìn)程間通信之利用命名管道進(jìn)行通信

    命名管道(Named Pipe),也被稱為FIFO(First In, First Out),是一種在Unix和Unix-like操作系統(tǒng)中用于進(jìn)程間通信的特殊文件類型。它允許不相關(guān)的進(jìn)程通過文件系統(tǒng)中的路徑名進(jìn)行通信。 命名管道(Named Pipe)是一種在Unix和Unix-like系統(tǒng)中用于進(jìn)程間通信的特殊文件類型。它的作用主

    2024年01月19日
    瀏覽(23)
  • 學(xué)習(xí)系統(tǒng)編程N(yùn)o.20【進(jìn)程間通信之命名管道】

    學(xué)習(xí)系統(tǒng)編程N(yùn)o.20【進(jìn)程間通信之命名管道】

    北京時(shí)間:2023/4/15/10:34,今天起床時(shí)間9:25,睡了快8小時(shí),昨天刷視屏刷了一個(gè)小時(shí),本來12點(diǎn)的時(shí)候發(fā)完博客洗把臉就要睡了,可惜,看到了一個(gè)標(biāo)題,說實(shí)話,現(xiàn)在的標(biāo)題黨是懂人性的,接下來就是無法自拔的一個(gè)小時(shí)快樂時(shí)光,但導(dǎo)致莫名間接熬夜,你說煩人不煩人!但

    2023年04月17日
    瀏覽(23)
  • 【Linux從入門到精通】通信 | 管道通信(匿名管道 & 命名管道)

    【Linux從入門到精通】通信 | 管道通信(匿名管道 & 命名管道)

    ? ? 本派你文章主要是對(duì)進(jìn)程通信進(jìn)行詳解。主要內(nèi)容是介紹 為什么通信、怎么進(jìn)行通信。其中本篇文章主要講解的是管道通信。希望本篇文章會(huì)對(duì)你有所幫助。 文章目錄 一、進(jìn)程通信簡(jiǎn)單介紹 1、1 什么是進(jìn)程通信 1、2?為什么要進(jìn)行通信 ?1、3 進(jìn)程通信的方式 二、匿名管

    2024年02月09日
    瀏覽(19)
  • 【Linux】匿名管道與命名管道,進(jìn)程池的簡(jiǎn)易實(shí)現(xiàn)

    【Linux】匿名管道與命名管道,進(jìn)程池的簡(jiǎn)易實(shí)現(xiàn)

    本質(zhì)是先讓不同的進(jìn)程看到同一份資源,也就是兩個(gè)進(jìn)程都能對(duì)管道文件的緩沖區(qū)進(jìn)行操作 這里我們pipe的時(shí)候,會(huì)使用兩個(gè)文件描述符,這兩個(gè)文件描述里面存的file結(jié)構(gòu)體是同一個(gè),也就是管道文件的file結(jié)構(gòu)體,file結(jié)構(gòu)體中存儲(chǔ)有inode以及系統(tǒng)緩沖區(qū),此時(shí)fork一個(gè)子進(jìn)程

    2024年02月05日
    瀏覽(23)
  • 【Linux】進(jìn)程通信之管道通信詳解

    【Linux】進(jìn)程通信之管道通信詳解

    ?? 作者: 阿潤(rùn)菜菜 ?? 專欄: Linux系統(tǒng)編程 其實(shí)管道通信是Unix中最古老的進(jìn)程間通信的形式了: 管道通信是一種進(jìn)程間通信的方式,它可以讓一個(gè)進(jìn)程的輸出作為另一個(gè)進(jìn)程的輸入,實(shí)現(xiàn)數(shù)據(jù)的傳輸、資源的共享、事件的通知和進(jìn)程的控制。 管道通信分為兩種類型:匿名

    2023年04月19日
    瀏覽(46)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包