路橋網(wǎng)站制作制作網(wǎng)頁教程
文章目錄
- 命名管道
- 指令級(jí)命名管道
- 代碼級(jí)命名管道
本篇要引入的內(nèi)容是命名管道
命名管道
前面的總結(jié)中已經(jīng)搞定了匿名管道,但是匿名管道有一個(gè)很嚴(yán)重的問題,它只允許具有血緣關(guān)系的進(jìn)程進(jìn)行通信,那如果是兩個(gè)不相關(guān)的進(jìn)程進(jìn)行通信,此時(shí)應(yīng)該如何處理?此時(shí)就可以采用的是命名管道
從名字上能看出來,它既然叫命名管道,就說明它是有名字的
指令級(jí)命名管道
系統(tǒng)默認(rèn)是支持指令級(jí)別的命名管道的,例如在bash中的豎劃線實(shí)際上就是指令級(jí)別的匿名管道,而命名管道當(dāng)然也是有指令級(jí)別的
fifo指令
從指令的角度來講,使用fifo命令就可以創(chuàng)建出一條命名管道,例如使用mkfifo filename,就可以在當(dāng)前目錄下創(chuàng)建出一個(gè)命名管道:
從上述的指令中可以看到,確確實(shí)實(shí)一個(gè)命名管道被創(chuàng)建出來了,并且在權(quán)限前面的p也證明這是一個(gè)管道文件
為什么說它是一個(gè)命名管道?第一個(gè)是說它有文件名,其實(shí)還有一層原因是因?yàn)樗嬖诼窂?#xff0c;只要有路徑和文件名,未來就可以通過路徑和文件名找到這個(gè)文件,既然可以找到文件,就可以借助這個(gè)文件進(jìn)行進(jìn)程間的通信,那么下面來看如何進(jìn)行進(jìn)程間的通信
上述是最簡(jiǎn)單的管道通信示意圖,基本原理就是一個(gè)進(jìn)程把內(nèi)容放入到管道中,另外一個(gè)進(jìn)程從管道中讀取信息,這就是最基本的原理
這個(gè)管道和匿名管道的一個(gè)區(qū)別是,匿名管道是內(nèi)存級(jí)的文件,簡(jiǎn)單來說就是不會(huì)在磁盤上創(chuàng)建信息,操作系統(tǒng)關(guān)閉這個(gè)文件也就隨之消失了,而命名管道是一個(gè)磁盤級(jí)的文件,不會(huì)隨著操作系統(tǒng)的關(guān)閉而消失,所以可以把這個(gè)文件當(dāng)做是一個(gè)正常的文件來看待
那么和正常文件的其中一個(gè)區(qū)別是,向管道中輸入文件后會(huì)發(fā)現(xiàn),此時(shí)文件的大小依舊是0:
出現(xiàn)這樣的原因是,雖然它是一個(gè)磁盤級(jí)的文件,但是在實(shí)際的數(shù)據(jù)通信的過程,這個(gè)文件必然是要讀端和寫端分別對(duì)兩個(gè)程序進(jìn)行開放,那么既然這個(gè)文件已經(jīng)被打開了,那么它的數(shù)據(jù)就沒有必要向磁盤中刷新,所以磁盤中這個(gè)文件此時(shí)還是沒有數(shù)據(jù)的,而對(duì)于其他的文件,都會(huì)及時(shí)的向磁盤中做刷新,以保存到磁盤中
管道的原理
下面討論的內(nèi)容是關(guān)于管道文件的原理,現(xiàn)在有兩個(gè)進(jìn)程a和進(jìn)程b,通信的本質(zhì)是讓兩個(gè)進(jìn)程看到同一份資源,這樣就可以借助這個(gè)同一份資源實(shí)現(xiàn)進(jìn)程之間的通信了,這是在最初就創(chuàng)建出的觀點(diǎn),那么對(duì)于命名管道來說,如何能讓兩個(gè)資源都看到我呢?怎么能保證呢?其實(shí)借助的就是路徑名和文件名的唯一性,這樣從宏觀上來講就能保證看到的是同一份資源,換個(gè)角度,從微觀上講,看到的真的是同一份資源嗎?答案也是肯定的,下面給出具體的解釋
首先,文件是存在于磁盤中的,現(xiàn)在a進(jìn)程有它對(duì)應(yīng)的PCB,有自己的文件描述符表,b進(jìn)程也有自己的PCB和文件描述符表,而現(xiàn)在如果a進(jìn)程打開了這個(gè)路徑中名字為某個(gè)名字的命名管道文件,操作系統(tǒng)為了方便管理信息,就要為這個(gè)文件創(chuàng)建一個(gè)文件結(jié)構(gòu)體來管理這個(gè)文件對(duì)象,然后再將文件描述符分配給a進(jìn)程的文件描述符表當(dāng)中,同時(shí)也有文件對(duì)應(yīng)的文件緩沖區(qū),如果這是一個(gè)普通文件,那么未來就可以借助這個(gè)文件緩沖區(qū)將內(nèi)容刷新到磁盤中或者是把磁盤中的內(nèi)容讀取到內(nèi)存中,這些都是可以理解的,那么下一個(gè)要探討的問題是,如果此時(shí)還有一個(gè)進(jìn)程b把這個(gè)文件打開了,那么操作系統(tǒng)是否還會(huì)做出同樣的內(nèi)容呢?會(huì)不會(huì)繼續(xù)加載這個(gè)文件對(duì)應(yīng)的文件緩沖區(qū),然后再創(chuàng)建等等一系列步驟呢?答案顯然是不會(huì)的,因?yàn)椴僮飨到y(tǒng)是一個(gè)非常講究效率的模塊,它不會(huì)做出任何違背效率的事,所以文件的內(nèi)容都存儲(chǔ)在內(nèi)存中,屬性也已經(jīng)加載好了,那么操作系統(tǒng)就不會(huì)重新加載了,所以兩個(gè)進(jìn)程打開了同一個(gè)文件,文件對(duì)應(yīng)的緩沖區(qū),內(nèi)容和屬性這些內(nèi)容都是不用再重新加載的,但是還會(huì)有對(duì)應(yīng)的文件結(jié)構(gòu)體,用來描述這個(gè)文件進(jìn)行讀寫到什么位置,這些還是會(huì)對(duì)應(yīng)的進(jìn)行加載的
用下圖來對(duì)上述的這一系列原理做出一個(gè)解釋:
從這個(gè)圖也能看出來,其實(shí)命名管道和匿名管道的原理基本上是一樣的,沒什么區(qū)別,操作系統(tǒng)判別到底是不是一個(gè)文件的標(biāo)準(zhǔn)就是看路徑+文件名,有了文件名就有了inode,于是就有了這上述的一系列邏輯,就做到了,讓進(jìn)程a和進(jìn)程b都看到了同一份資源,進(jìn)而借助緩沖區(qū)完成了進(jìn)程之間的通信
代碼級(jí)命名管道
其實(shí)相比起匿名管道來說,命名管道反而更加簡(jiǎn)單,所以有了前面進(jìn)程池的代碼基礎(chǔ),實(shí)現(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;
}