專業(yè)的建網(wǎng)站的公司廣州seo網(wǎng)站推廣公司
如何創(chuàng)建命名管道
使用mkfifo函數(shù)就可以在程序里面創(chuàng)建管道文件,該函數(shù)的聲明如下:
該函數(shù)需要兩個(gè)參數(shù),第一個(gè)參數(shù)表示要在哪個(gè)路徑下創(chuàng)建管道文件并且這個(gè)路徑得待上管道文件的名字,因?yàn)槊總€(gè)文件都有對應(yīng)的權(quán)限,所以函數(shù)的第二個(gè)參數(shù)就表示管道文件的權(quán)限,如果管道文件創(chuàng)建成功了該函數(shù)就返回0,如果創(chuàng)建失敗該函數(shù)就直接返回對應(yīng)的錯(cuò)誤碼:
那么接下來我們就創(chuàng)建一個(gè)管道文件出來瞧瞧,當(dāng)前所在的路徑如下:
那么我們就可以在程序里面通過函數(shù)mkfifo來創(chuàng)建管道文件:
#include<iostream>
#include<cerrno>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
using namespace std;
int main()
{umask(0);int n=mkfifo("/home/xbb/folder13/name_pipe",0600);if (n == 0){cout<<"creat success"<<endl;}else{cout << "errno: " << errno << " err string: " << strerror(errno) << endl;}return 0;
}
那么運(yùn)行的結(jié)果就如下:
可以看到程序運(yùn)行成功之后多出來了一個(gè)名為name_pipe的文件,并且這個(gè)文件是以p為開頭,那么這個(gè)p就表示pipe的意思表示當(dāng)前文件是管道文件,并且使用指令ls -il
查看文件inode的時(shí)候也可以發(fā)現(xiàn)該文件有inode,那么這就說明管道文件是一個(gè)獨(dú)立的文件,并且當(dāng)前管道文件的大小為0表示當(dāng)前文件里面沒有任何的數(shù)據(jù),然后使用下面的指令我們可以往屏幕上面不停地輸出數(shù)據(jù):cnt=0; while :; do echo "hello world-> $cnt"; let cnt++; sleep 1;done
:
那么這里我們就可以使用重定向,將原本輸出到屏幕的數(shù)據(jù)輸出到管道文件里面:
運(yùn)行當(dāng)前的指令就會(huì)不停地往管道里面輸入數(shù)據(jù),但是我們再創(chuàng)建一個(gè)進(jìn)程不停地查看的管道文件的大小時(shí),便會(huì)發(fā)現(xiàn)管道文件的大小沒有發(fā)生任何變化:
可是雖然文件的大小沒有發(fā)生任何的變化,但是當(dāng)我們使用cat指令輸出文件的內(nèi)容時(shí)便又會(huì)發(fā)現(xiàn),屏幕中打印了我們輸入到文件里面的內(nèi)容:
那么這就是管道文件的特性,那么接下來我們來看看命名管道的原理。
命名管道的原理
當(dāng)我們在操作系統(tǒng)中打開一個(gè)文件,操作系統(tǒng)會(huì)創(chuàng)建一個(gè)struct file對象,該對象里面含有緩沖區(qū)和文件有廣的操作方法,然后進(jìn)程中的PCB中就會(huì)有一個(gè)指針指向一個(gè)名為file_struct的結(jié)構(gòu)體,結(jié)構(gòu)體中存在一個(gè)指針數(shù)組,數(shù)組的每個(gè)元素就指向不同文件的struct file對象:
但是這里存在一個(gè)問題,如果多個(gè)進(jìn)程打開同一個(gè)文件,那操作系統(tǒng)會(huì)為這個(gè)文件創(chuàng)建多個(gè)struct file對象嗎?答案是不會(huì)的,即使多個(gè)進(jìn)程都打開了同一個(gè)文件,操作系統(tǒng)也只會(huì)創(chuàng)建一個(gè)struct file對象,所以這就會(huì)導(dǎo)致多個(gè)進(jìn)程共用一個(gè)struct file,如果多個(gè)進(jìn)程共用一個(gè)struct file的話,那這不就是讓多個(gè)進(jìn)程看到同一份資源嘛,所以這個(gè)strcut file就相當(dāng)于是一個(gè)管道,只不過該管道文件的strcut file不會(huì)將內(nèi)部緩沖區(qū)的數(shù)據(jù)刷新到磁盤中的文件里面,struct file對象中的數(shù)據(jù)都是內(nèi)存級被寫入和讀取,那命名管道是如何做到讓不同的進(jìn)程看到同一份資源的呢?答案是讓不同的進(jìn)程打開指定名稱(路徑+文件名)的同一個(gè)文件,路徑+文件名=唯一確定的文件,之所以叫命名管道是因?yàn)樵摴艿朗峭ㄟ^文件名的方式來看到同一份資源,而匿名管道是父子進(jìn)程通過繼承的方式來確定唯一性,并不通過文件名,所以將其稱之為匿名管道。
命名管道的通信
函數(shù)mkfifo可以在程序里面創(chuàng)建命名管道,既然有創(chuàng)建那么同樣的道理就有對應(yīng)的函數(shù)來刪除管道文件,unlink函數(shù)就可以用來刪除創(chuàng)建的管道文件,該函數(shù)的聲明如下:
函數(shù)內(nèi)的參數(shù)表示要?jiǎng)h除的管道文件,如果刪除成功就返回0,刪除失敗就返回對應(yīng)的錯(cuò)誤碼:
那么這里我們就可以使用這兩個(gè)函數(shù)來實(shí)現(xiàn)進(jìn)程之間的通信,首先創(chuàng)建一個(gè)文件,這個(gè)文件里面就包含兩個(gè)函數(shù),一個(gè)函數(shù)用來創(chuàng)建管道文件,一個(gè)函數(shù)用來刪除管道文件:
#include<iostream>
#include<cerrno>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
using namespace std;
#define PIPE_PATH "/home/xbb/folder13/name_pipe"
bool createFifo(const string &path)
{}void removeFifo(string &path)
{}
根絕前面的經(jīng)驗(yàn)我們可以很容易得實(shí)現(xiàn)createFifo函數(shù),當(dāng)mkfifo函數(shù)返回的值等于0的話就返回true,函數(shù)的返回值為非0的話就返回false:
bool createFifo(const string &path)
{umask(0);int n=mkfifo(path.c_str(),0600);if(n==0){return true;}else{cout << "errno: " << errno << " err string: " << strerror(errno) << endl;return false;}
}
同樣的道理removeFifo函數(shù)里面就是調(diào)用unlink函數(shù)來進(jìn)行刪除,那么這里我們就用assert函數(shù)來進(jìn)行判斷文件是否刪除成功,那這里的代碼如下:
void removeFifo(const string &path)
{int n=unlink(path.c_str()); assert(n==0);(void)n;
}
那么這就是comm.hpp文件的內(nèi)容,接下來我們還要?jiǎng)?chuàng)建server.cc文件和client.cc文件,server.cc文件負(fù)責(zé)從管道里面讀取數(shù)據(jù),client.cc文件就負(fù)責(zé)從管道里面寫入數(shù)據(jù),因?yàn)閟erver.cc文件是讀端,所以我們就讓他來決定管道的創(chuàng)建和刪除,那么在這個(gè)server.cc文件里面首先使用comm.hpp中的creatFifo函數(shù)創(chuàng)建管道,然后使用assert判斷一下創(chuàng)建是否成功,創(chuàng)建成功之后就使用open函數(shù)以讀的方式打開該管道文件,然后就得到這個(gè)函數(shù)的讀端的下標(biāo)
#include"comm.hpp"
int main()
{int r = createFifo(PIPE_PATH);assert(r);(void)r;int rfd=open(PIPE_PATH, O_RDONLY);cout<<"開始讀取"<<endl;if(rfd<0){exit(1);}return 0;
}
走到這里我們的管道文件就在當(dāng)前進(jìn)程順利的創(chuàng)建并且打開了,那么這時(shí)我們就可以創(chuàng)建一個(gè)while循環(huán)不停地往管道里面讀取信息,因?yàn)樽x取的信息要放到一個(gè)地方進(jìn)行存儲(chǔ),所以在循環(huán)之前我們還得穿件一個(gè)數(shù)組用來充當(dāng)緩沖區(qū),那么在循環(huán)里面就可以使用read函數(shù)從下標(biāo)為rfd的文件里面讀取數(shù)據(jù),將讀取的數(shù)據(jù)放到緩沖區(qū)里面,因?yàn)樽x取數(shù)據(jù)的時(shí)候可能會(huì)出現(xiàn)結(jié)束或者出錯(cuò)的情況,所以這里還得創(chuàng)建一個(gè)變量用來記錄read函數(shù)的返回值,那么這里的代碼如下:
#include"comm.hpp"
int main()
{int r = createFifo(PIPE_PATH);assert(r);(void)r;int rfd=open(PIPE_PATH, O_RDONLY);cout<<"開始讀取"<<endl;if(rfd<0){exit(1);}char buffer[1024]={0};while(true){ssize_t s = read(rfd, buffer, sizeof(buffer)-1);}return 0;
}
然后就使用if else語句對size的值進(jìn)行判斷,如果size的值大于0就表示當(dāng)前的讀取是正確的,我們直接輸出buffer里面的值,如果size的值等于0就表示當(dāng)前的讀取結(jié)束了直接使用break結(jié)束while循環(huán),如果sizede值小于0就表示當(dāng)前的讀取出現(xiàn)了錯(cuò)誤,那么我們就打印一下錯(cuò)誤碼看看哪里出現(xiàn)了問題并使用break結(jié)束循環(huán),循環(huán)結(jié)束之后我們就關(guān)閉當(dāng)前打開的管道文件,并且使用removeFifo函數(shù)刪除管道文件,那么這里的代碼如下:
#include"comm.hpp"
int main()
{int r = createFifo(PATH);assert(r);(void)r;int rfd=open(PATH, O_RDONLY);cout<<"開始讀取"<<endl;if(rfd<0){exif(1);}char buffer[1024]={0};while(true){ssize_t s = read(rfd, buffer, sizeof(buffer)-1);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 << "err string: " << strerror(errno) << std::endl;break;}}close(rfd);// sleep(10);removeFifo(NAMED_PIPE);return 0;
}
那么這是server.cc文件的內(nèi)容,那么對于client.cc文件也是同樣的道理,首先以寫的方式打開管道文件然后進(jìn)行判斷,再創(chuàng)建一個(gè)緩沖區(qū)將想要寫入管道的信息先寫入到緩沖區(qū)里,再使用write函數(shù)將緩沖區(qū)的內(nèi)容放到寫入到管道里面,然后根據(jù)write函數(shù)的返回值來判斷當(dāng)前的寫入是否成功,因?yàn)橐啻螌懭胨赃@里也得添加while循環(huán)來進(jìn)行循環(huán)寫入:
#include"comm.hpp"
int main()
{int Wfd=open(PIPE_PATH, O_WRONLY);if(Wfd < 0) exit(1);char buffer[1024];cout<<"client say"<<endl;while(true){fgets(buffer, sizeof(buffer), stdin);if(strlen(buffer) > 0) {//將末尾的/n去掉buffer[strlen(buffer)-1] = 0;}ssize_t n = write(Wfd, buffer, strlen(buffer));assert(n == strlen(buffer));(void)n;}close(Wfd);return 0;
}
此文件寫完之后我們就可以來完成makefile文件,首先該功能的運(yùn)行需要讓兩個(gè)文件都生成可執(zhí)行程序,所以這里就將指令較為all,該指令需要client和server生成可執(zhí)行程序:
.PHONY:
all: client server
但是當(dāng)前路徑下并沒有可執(zhí)行程序,所以我們還得添加兩個(gè)可執(zhí)行程序?qū)?yīng)的實(shí)現(xiàn)方法和依賴文件:
.PHONY:
all: client serverclient:client.ccg++ -o $@ $^ -std=c++11 -g
server:server.ccg++ -o $@ $^ -std=c++11 -g
然后就是刪除指令,該指令將生成的兩個(gè)可執(zhí)行程序刪除就行,那么makefile的全部內(nèi)容如下:
.PHONY:
all: client serverclient:client.ccg++ -o $@ $^ -std=c++11 -g
server:server.ccg++ -o $@ $^ -std=c++11 -g.PHONY:
clean:rm -f client server
那么接下來我們就可以進(jìn)行測試,首先使用make指令生成兩個(gè)可執(zhí)行程序:
然后先打開server再打開client程序,然后就可以看到這樣的現(xiàn)象:
因?yàn)閷懚藳]有打開,所以server一直阻塞在open函數(shù)那里,當(dāng)我們運(yùn)行client程序之后就可以看到server進(jìn)程打印出來了內(nèi)容:
然后我們往client進(jìn)程里面輸入內(nèi)容,然后便可以看到,輸入導(dǎo)client里面的內(nèi)容輸出到server這里:
那么這就是命名管道的通信。