路橋網(wǎng)站制作制作網(wǎng)頁教程
文章目錄
- 命名管道
- 指令級命名管道
- 代碼級命名管道
本篇要引入的內(nèi)容是命名管道
命名管道
前面的總結(jié)中已經(jīng)搞定了匿名管道,但是匿名管道有一個很嚴(yán)重的問題,它只允許具有血緣關(guān)系的進程進行通信,那如果是兩個不相關(guān)的進程進行通信,此時應(yīng)該如何處理?此時就可以采用的是命名管道
從名字上能看出來,它既然叫命名管道,就說明它是有名字的
指令級命名管道
系統(tǒng)默認(rèn)是支持指令級別的命名管道的,例如在bash中的豎劃線實際上就是指令級別的匿名管道,而命名管道當(dāng)然也是有指令級別的
fifo指令
從指令的角度來講,使用fifo命令就可以創(chuàng)建出一條命名管道,例如使用mkfifo filename,就可以在當(dāng)前目錄下創(chuàng)建出一個命名管道:
從上述的指令中可以看到,確確實實一個命名管道被創(chuàng)建出來了,并且在權(quán)限前面的p也證明這是一個管道文件
為什么說它是一個命名管道?第一個是說它有文件名,其實還有一層原因是因為它存在路徑,只要有路徑和文件名,未來就可以通過路徑和文件名找到這個文件,既然可以找到文件,就可以借助這個文件進行進程間的通信,那么下面來看如何進行進程間的通信
上述是最簡單的管道通信示意圖,基本原理就是一個進程把內(nèi)容放入到管道中,另外一個進程從管道中讀取信息,這就是最基本的原理
這個管道和匿名管道的一個區(qū)別是,匿名管道是內(nèi)存級的文件,簡單來說就是不會在磁盤上創(chuàng)建信息,操作系統(tǒng)關(guān)閉這個文件也就隨之消失了,而命名管道是一個磁盤級的文件,不會隨著操作系統(tǒng)的關(guān)閉而消失,所以可以把這個文件當(dāng)做是一個正常的文件來看待
那么和正常文件的其中一個區(qū)別是,向管道中輸入文件后會發(fā)現(xiàn),此時文件的大小依舊是0:
出現(xiàn)這樣的原因是,雖然它是一個磁盤級的文件,但是在實際的數(shù)據(jù)通信的過程,這個文件必然是要讀端和寫端分別對兩個程序進行開放,那么既然這個文件已經(jīng)被打開了,那么它的數(shù)據(jù)就沒有必要向磁盤中刷新,所以磁盤中這個文件此時還是沒有數(shù)據(jù)的,而對于其他的文件,都會及時的向磁盤中做刷新,以保存到磁盤中
管道的原理
下面討論的內(nèi)容是關(guān)于管道文件的原理,現(xiàn)在有兩個進程a和進程b,通信的本質(zhì)是讓兩個進程看到同一份資源,這樣就可以借助這個同一份資源實現(xiàn)進程之間的通信了,這是在最初就創(chuàng)建出的觀點,那么對于命名管道來說,如何能讓兩個資源都看到我呢?怎么能保證呢?其實借助的就是路徑名和文件名的唯一性,這樣從宏觀上來講就能保證看到的是同一份資源,換個角度,從微觀上講,看到的真的是同一份資源嗎?答案也是肯定的,下面給出具體的解釋
首先,文件是存在于磁盤中的,現(xiàn)在a進程有它對應(yīng)的PCB,有自己的文件描述符表,b進程也有自己的PCB和文件描述符表,而現(xiàn)在如果a進程打開了這個路徑中名字為某個名字的命名管道文件,操作系統(tǒng)為了方便管理信息,就要為這個文件創(chuàng)建一個文件結(jié)構(gòu)體來管理這個文件對象,然后再將文件描述符分配給a進程的文件描述符表當(dāng)中,同時也有文件對應(yīng)的文件緩沖區(qū),如果這是一個普通文件,那么未來就可以借助這個文件緩沖區(qū)將內(nèi)容刷新到磁盤中或者是把磁盤中的內(nèi)容讀取到內(nèi)存中,這些都是可以理解的,那么下一個要探討的問題是,如果此時還有一個進程b把這個文件打開了,那么操作系統(tǒng)是否還會做出同樣的內(nèi)容呢?會不會繼續(xù)加載這個文件對應(yīng)的文件緩沖區(qū),然后再創(chuàng)建等等一系列步驟呢?答案顯然是不會的,因為操作系統(tǒng)是一個非常講究效率的模塊,它不會做出任何違背效率的事,所以文件的內(nèi)容都存儲在內(nèi)存中,屬性也已經(jīng)加載好了,那么操作系統(tǒng)就不會重新加載了,所以兩個進程打開了同一個文件,文件對應(yīng)的緩沖區(qū),內(nèi)容和屬性這些內(nèi)容都是不用再重新加載的,但是還會有對應(yīng)的文件結(jié)構(gòu)體,用來描述這個文件進行讀寫到什么位置,這些還是會對應(yīng)的進行加載的
用下圖來對上述的這一系列原理做出一個解釋:
從這個圖也能看出來,其實命名管道和匿名管道的原理基本上是一樣的,沒什么區(qū)別,操作系統(tǒng)判別到底是不是一個文件的標(biāo)準(zhǔn)就是看路徑+文件名,有了文件名就有了inode,于是就有了這上述的一系列邏輯,就做到了,讓進程a和進程b都看到了同一份資源,進而借助緩沖區(qū)完成了進程之間的通信
代碼級命名管道
其實相比起匿名管道來說,命名管道反而更加簡單,所以有了前面進程池的代碼基礎(chǔ),實現(xiàn)也不算很難:
// comm.h
#pragma once#define FILENAME "fifo"
// client.cc
#include <iostream>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "comm.h"int main()
{int wfd = open(FILENAME, O_WRONLY);if (wfd < 0){std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;return 1;}std::cout << "open fifo success... write" << std::endl;std::string message;while (true){std::cout << "Please Enter# ";std::getline(std::cin, message);ssize_t s = write(wfd, message.c_str(), message.size());if (s < 0){std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;break;}}close(wfd);std::cout << "close fifo success..." << std::endl;return 0;
}
// server.cc
#include <iostream>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "comm.h"bool MakeFifo()
{int n = mkfifo(FILENAME, 0666);if (n < 0){std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;return false;}std::cout << "mkfifo success... read" << std::endl;return true;
}int main()
{
Start:int rfd = open(FILENAME, O_RDONLY);if (rfd < 0){std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;if (MakeFifo())goto Start;elsereturn 1;}std::cout << "open fifo success..." << std::endl;char buffer[1024];while (true){ssize_t s = read(rfd, buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = 0;std::cout << "Client say# " << buffer << std::endl;}else if (s == 0){std::cout << "client quit, server quit too!" << std::endl;break;}}close(rfd);std::cout << "close fifo success..." << std::endl;return 0;
}