?
? 本派你文章主要是對(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)程通信的方式
二、匿名管道
2、1 什么是管道
2、2 匿名管道通信
2、3 管道通信 demo代碼
2、3、1 pipe 創(chuàng)建管道
2、3、2 demo 代碼
2、4 匿名管道特點(diǎn)
2、5 進(jìn)程池
2、5、1 Tasks.hpp
2、5、2?ProcessPool.cpp
2、5、3 demo 代碼解釋
三、命名管道
3、1 什么是命名管道
3、2 命名管道通信
3、3 命名管道 demo 代碼
3、3、1 mkfifo
3、3、2 demo 代碼
四、總結(jié)
???♂??作者:@Ggggggtm????♂?
???專(zhuān)欄:Linux從入門(mén)到精通? ??
???標(biāo)題:管道通信??
????寄語(yǔ):與其忙著訴苦,不如低頭趕路,奮路前行,終將遇到一番好風(fēng)景????
一、進(jìn)程通信簡(jiǎn)單介紹
1、1 什么是進(jìn)程通信
? 進(jìn)程通信是指不同進(jìn)程之間進(jìn)行數(shù)據(jù)交換、消息傳遞和協(xié)作的過(guò)程。在操作系統(tǒng)中,每個(gè)進(jìn)程都是獨(dú)立運(yùn)行的單位,它們擁有各自的內(nèi)存空間和執(zhí)行環(huán)境。為了實(shí)現(xiàn)進(jìn)程之間的互動(dòng)和合作,需要通過(guò)進(jìn)程通信來(lái)進(jìn)行數(shù)據(jù)共享、狀態(tài)同步、任務(wù)協(xié)作等操作。
? 我們知道進(jìn)程都是獨(dú)立的,各自有各自的地址空間。而進(jìn)程通信的本質(zhì)是讓不同的進(jìn)程能夠看到同一塊“內(nèi)存”。而這塊內(nèi)存并不屬于任何一個(gè)進(jìn)程,是所有進(jìn)程共享的。
1、2?為什么要進(jìn)行通信
? 我們之前學(xué)習(xí)的都是單進(jìn)程。單進(jìn)程不能使用并發(fā)能力,更無(wú)法實(shí)現(xiàn)多進(jìn)程協(xié)同。下面給出需要進(jìn)程通信的原因:
數(shù)據(jù)共享和傳遞:不同的進(jìn)程可能需要共享數(shù)據(jù),例如一個(gè)進(jìn)程產(chǎn)生的結(jié)果可能需要被其他進(jìn)程使用。通過(guò)進(jìn)程間通信,可以實(shí)現(xiàn)數(shù)據(jù)的傳遞和共享,讓不同的進(jìn)程能夠獲取彼此的數(shù)據(jù),并保持?jǐn)?shù)據(jù)的一致性。
任務(wù)協(xié)作和協(xié)調(diào):在復(fù)雜的應(yīng)用程序中,多個(gè)進(jìn)程往往需要協(xié)同工作完成某個(gè)任務(wù)。通過(guò)進(jìn)程間通信,進(jìn)程可以互相發(fā)送消息和指令,協(xié)調(diào)彼此的行動(dòng),實(shí)現(xiàn)任務(wù)的劃分、分工和協(xié)作。
資源共享和管理:在計(jì)算機(jī)系統(tǒng)中,各個(gè)進(jìn)程需要共享有限的資源,如內(nèi)存、文件、設(shè)備等。進(jìn)程間通信能夠確保多個(gè)進(jìn)程正確地訪(fǎng)問(wèn)和管理共享資源,避免沖突和資源浪費(fèi)。
進(jìn)程控制和同步:進(jìn)程間通信提供了一種機(jī)制,使得進(jìn)程能夠進(jìn)行進(jìn)程間的控制和同步操作。例如,一個(gè)進(jìn)程可能需要等待另一個(gè)進(jìn)程完成某個(gè)任務(wù)后才能繼續(xù)執(zhí)行,通過(guò)進(jìn)程間通信,可以實(shí)現(xiàn)進(jìn)程的阻塞和喚醒,實(shí)現(xiàn)進(jìn)程的協(xié)調(diào)。
?1、3 進(jìn)程通信的方式
常見(jiàn)的進(jìn)程通信方法包括以下幾種:
管道(Pipe):管道提供了一種半雙工的、單向的通信機(jī)制,通常用于具有父子關(guān)系的進(jìn)程之間進(jìn)行通信。
消息隊(duì)列(Message Queue):消息隊(duì)列是一種使用消息緩沖區(qū)進(jìn)行通信的形式,進(jìn)程可以把消息發(fā)送到隊(duì)列中,然后其他進(jìn)程從隊(duì)列中讀取消息。
共享內(nèi)存(Shared Memory):共享內(nèi)存是一種將一塊內(nèi)存區(qū)域映射到多個(gè)進(jìn)程的機(jī)制,多個(gè)進(jìn)程可以直接訪(fǎng)問(wèn)這塊共享內(nèi)存,實(shí)現(xiàn)高效的數(shù)據(jù)共享。
信號(hào)量(Semaphore):信號(hào)量是一種用于進(jìn)程之間同步和互斥的機(jī)制,可以通過(guò)提供一個(gè)計(jì)數(shù)器,控制多個(gè)進(jìn)程對(duì)共享資源的訪(fǎng)問(wèn)。
? 管道通信又分為匿名管道和命名管道通信。本篇文章講解的重點(diǎn)就是管道通信。
二、匿名管道
2、1 什么是管道
??在Linux中,管道是一種用于進(jìn)程間通信的特殊文件。它可以連接一個(gè)進(jìn)程的輸出到另一個(gè)進(jìn)程的輸入,實(shí)現(xiàn)數(shù)據(jù)的傳輸和共享。管道通信是一種基于管道的進(jìn)程間通信方式。
? 舉一個(gè)具體的例子來(lái)解釋一下如何使用管道和進(jìn)行管道通信:假設(shè)有兩個(gè)命令,command1和command2,我們希望將command1的輸出傳遞給command2進(jìn)行處理。首先,我們可以使用管道符號(hào)
|
將這兩個(gè)命令連接起來(lái)。具體如下:command1 | command2
2、2 匿名管道通信
? 匿名管道顧名思義:沒(méi)有名字的管道。匿名管道只能在具有親緣關(guān)系的進(jìn)程間使用,通常用于父進(jìn)程和子進(jìn)程之間進(jìn)行通信。我們上面了解了管道是一個(gè)文件,文件不都是有名字的嗎?在磁盤(pán)上的文件都是有名字的。但是匿名管道并不是在磁盤(pán)上,而是在內(nèi)存中存儲(chǔ)。需要注意的是,匿名管道的數(shù)據(jù)是臨時(shí)存儲(chǔ)在內(nèi)存中的,而不是永久保存在磁盤(pán)上。當(dāng)相關(guān)的進(jìn)程結(jié)束時(shí),管道和其中的數(shù)據(jù)也會(huì)被釋放,不會(huì)留下任何痕跡。那我們接下來(lái)看看是怎么進(jìn)行通信的。
? 管道通信本質(zhì)上就是進(jìn)程通信。也可以理解為進(jìn)程通信的手段是利用了管道。我們創(chuàng)建一個(gè)進(jìn)程,以讀寫(xiě)的形式打開(kāi)一個(gè)文件。具體如下圖:
? 然后我們?cè)賱?chuàng)建一個(gè)子進(jìn)程。我們知道子進(jìn)程會(huì)繼承父進(jìn)程的相關(guān)代碼個(gè)數(shù)據(jù)結(jié)構(gòu)的。創(chuàng)建完子進(jìn)程后,具體如下圖:
?
? 我們知道相關(guān)的數(shù)據(jù)結(jié)構(gòu)會(huì)繼承,但是文件也會(huì)被繼承嗎(就是所指向的文件也會(huì)被拷貝一份嗎)?并不會(huì)的。這時(shí)候不就是讓不同的進(jìn)程看到了同一塊內(nèi)存資源嗎?。?!管道是單向通信的機(jī)制。然后我們?cè)訇P(guān)閉我們不需要的文件描述符(fd),不就是我們所說(shuō)的管道嗎?。?!
? 從上述過(guò)程我們發(fā)現(xiàn)了, 創(chuàng)建匿名管道分為以下三個(gè)步驟:
- 分別以讀寫(xiě)的方式打開(kāi)同一個(gè)文件;
- fork()創(chuàng)建子進(jìn)程;
- 父子進(jìn)程各自關(guān)閉自己不需要的文件描述符(fd)。
? 下面我們不妨模擬一下管道通信,是我們的理解更加深刻。
2、3 管道通信 demo代碼
2、3、1 pipe 創(chuàng)建管道
? 我們?cè)趺赐瑫r(shí)以讀寫(xiě)的方式打開(kāi)一個(gè)文件呢?可以利用一個(gè)系統(tǒng)調(diào)用函數(shù):pipe。
? pipe函數(shù)是一種創(chuàng)建管道的系統(tǒng)調(diào)用。它被用于在進(jìn)程間進(jìn)行通信,使得一個(gè)進(jìn)程的輸出能夠直接成為另一個(gè)進(jìn)程的輸入。我們來(lái)看一下pipe函數(shù)的使用。
? 參數(shù) pipefd[2] 是輸出型參數(shù)。調(diào)用成功后,會(huì)將所打開(kāi)文件的讀端、寫(xiě)端的文件描述符寫(xiě)入該數(shù)組。pipefd[0]用于讀取管道數(shù)據(jù),pipefd[1]用于寫(xiě)入管道數(shù)據(jù)。我們不妨來(lái)測(cè)試一下pipefd 數(shù)組中是否獲得了所打開(kāi)文件的文件描述符。代碼如下:
#include<iostream> #include<unistd.h> #include<assert.h> using namespace std; int main() { int pipefd[2]; int n=pipe(pipefd); assert(n != -1); cout<< "pipefd[0] : "<<pipefd[0]<<endl; cout<< "pipefd[1] : "<<pipefd[1]<<endl; return 0; }
? 我們來(lái)看一下輸出結(jié)果:
? 根據(jù)我們之前學(xué)的文件描述符章節(jié),確實(shí)是將新打開(kāi)的文件描述符寫(xiě)入了數(shù)組。加入我們是想要父進(jìn)程寫(xiě),子進(jìn)程來(lái)讀。那我們就可以讓父子進(jìn)程各自進(jìn)行關(guān)閉對(duì)應(yīng)的不需要的文件描述符了。具體結(jié)合下圖理解:
2、3、2 demo 代碼
? 對(duì)上述的了解后,我們大概知道了匿名管道通信的過(guò)程。那么下面我們看一下demo代碼:
#include<iostream> #include<unistd.h> #include<sys/types.h> #include<cstring> #include<string> #include<sys/wait.h> #include<assert.h> using namespace std; int main() { int pipefd[2]; //默認(rèn)情況 pipefd[0]:讀端口、pipefd[1]:寫(xiě)端口 int n=pipe(pipefd); assert(n!=-1); (void)n; pid_t id=fork(); assert(id!=-1); // 父寫(xiě) 子讀 if(id==0) { //子進(jìn)程 //構(gòu)建單向通信管道,關(guān)閉不需要的端口 close(pipefd[1]); char buffer[1024]; while(true) { ssize_t s = read(pipefd[0],buffer,sizeof(buffer)-1); if(s > 0) { buffer[s]=0; cout << "child read successfully:[" << getpid() << "]<< Fatehr#" << buffer<<endl; } } close(pipefd[0]); exit(0); } //父進(jìn)程 close(pipefd[0]); string message="我是父進(jìn)程,我正在發(fā)消息"; int count=0; char send_buffer[1024]; while(true) { snprintf(send_buffer,sizeof(send_buffer),"%s[%d]:%d", message.c_str(),getpid(),count++); write(pipefd[1],send_buffer,strlen(send_buffer)); sleep(1); } pid_t ret=waitpid(id,nullptr,0); assert(ret>0); (void)ret; close(pipefd[1]); return 0; }
? 我們對(duì)上述demo代碼的思路進(jìn)行解釋?zhuān)?/p>
首先包含了一些必要的頭文件,包括輸入輸出流、進(jìn)程管理和管道相關(guān)的系統(tǒng)調(diào)用等。然后定義了一個(gè)整型數(shù)組
pipefd
,用于存儲(chǔ)管道的讀寫(xiě)端口。pipe(pipefd)
創(chuàng)建了一個(gè)匿名管道,并將讀端口和寫(xiě)端口的文件描述符存儲(chǔ)在pipefd
數(shù)組中。接下來(lái),通過(guò)
fork()
函數(shù)創(chuàng)建了一個(gè)子進(jìn)程。子進(jìn)程執(zhí)行的代碼是在if(id==0)
條件中(子進(jìn)程),首先關(guān)閉了寫(xiě)端口pipefd[1]
,然后進(jìn)入一個(gè)無(wú)限循環(huán)。在循環(huán)中,子進(jìn)程通過(guò)read()
函數(shù)從管道的讀端口pipefd[0]
讀取數(shù)據(jù)。如果讀取成功,就將讀取到的數(shù)據(jù)存儲(chǔ)在buffer
中,并在控制臺(tái)上打印輸出。在父進(jìn)程中,首先關(guān)閉了讀端口
pipefd[0]
(因?yàn)楦高M(jìn)程不需要從管道中讀取數(shù)據(jù)),然后進(jìn)入一個(gè)無(wú)限循環(huán)。在循環(huán)中,父進(jìn)程將包含有特定格式信息的字符串存儲(chǔ)在send_buffer
中,然后通過(guò)write()
函數(shù)將send_buffer
中的數(shù)據(jù)寫(xiě)入管道的寫(xiě)端口pipefd[1]
。發(fā)送完畢后,父進(jìn)程通過(guò)sleep(1)
函數(shù)暫停1秒。最后,父進(jìn)程通過(guò)
waitpid()
等待子進(jìn)程結(jié)束,并關(guān)閉寫(xiě)端口pipefd[1]
。整個(gè)程序執(zhí)行完成后,返回0。總結(jié):該程序利用了管道(pipe)實(shí)現(xiàn)了父子進(jìn)程之間的單向通信。父進(jìn)程向管道寫(xiě)入消息,子進(jìn)程從管道讀取消息并輸出。? 上述不過(guò)成就模擬出了父子進(jìn)程通過(guò)匿名管道進(jìn)行了數(shù)據(jù)交互,我們也稱(chēng)之為進(jìn)程間通信。
2、4 匿名管道特點(diǎn)
? 我們接下來(lái)再總結(jié)一下管道的特點(diǎn):
- 管道是用來(lái)進(jìn)行具有血緣關(guān)系的進(jìn)程進(jìn)性進(jìn)程間通信——常用于父子通信;
- 管道具有通過(guò)讓進(jìn)程間協(xié)同,提供了訪(fǎng)問(wèn)控制!
- 管道提供的是面向流式的通信服務(wù)——面向字節(jié)流——協(xié)議;
- 管道是基于文件的,文件的生命周期是隨進(jìn)程的,管道的生命周期是隨進(jìn)程的!
- 管道是單向通信的,就是半雙工通信的一種特殊情況;
? ?上述特點(diǎn)中說(shuō)到管道提供了訪(fǎng)問(wèn)控制,什么是訪(fǎng)問(wèn)控制呢?就上述我們的管導(dǎo)通信 demo代碼來(lái)說(shuō),父進(jìn)程是每隔一秒向管到文件中寫(xiě)入一條數(shù)據(jù)。而子進(jìn)程并沒(méi)有進(jìn)行休眠,是一直處于讀的狀態(tài)。那運(yùn)行結(jié)果是什么情況呢?如下圖:
? 通過(guò)觀(guān)察上述情況,我們也不難發(fā)現(xiàn)子進(jìn)程也是沒(méi)休眠一秒進(jìn)程再讀取數(shù)據(jù),跟父進(jìn)程的寫(xiě)數(shù)據(jù)是同步的。這是為什么呢?原因就是管道提供了訪(fǎng)問(wèn)控制。當(dāng)管道沒(méi)有數(shù)據(jù)時(shí),讀端就要進(jìn)行等待,等待寫(xiě)端寫(xiě)入數(shù)據(jù)后再進(jìn)行讀?。。?!我們接下來(lái)總結(jié)一下管道訪(fǎng)問(wèn)控制的特點(diǎn):
- 寫(xiě)快,讀慢,寫(xiě)滿(mǎn)不能在寫(xiě)了;
- 寫(xiě)慢,讀快,管道沒(méi)有數(shù)據(jù)的時(shí)候,讀必須等待;
- 寫(xiě)關(guān),讀0,標(biāo)識(shí)讀到了文件結(jié)尾;
- 讀關(guān),寫(xiě)繼續(xù)寫(xiě),OS終止寫(xiě)進(jìn)程。
2、5 進(jìn)程池
? 此小節(jié)是一個(gè)擴(kuò)展,可以加強(qiáng)理解學(xué)習(xí)。我們知道一個(gè)進(jìn)程可能會(huì)處理很多任務(wù)。但是一個(gè)父進(jìn)程只能建立與其對(duì)應(yīng)的唯一一個(gè)管道嗎?其實(shí)并不是的。一個(gè)父進(jìn)程可以建立多個(gè)子進(jìn)程,那么對(duì)應(yīng)的是不是就可以創(chuàng)建多個(gè)管道了!具體如下圖:
? 那這樣的話(huà),我們父進(jìn)程是不是就可以將任務(wù)派發(fā)個(gè)子進(jìn)程來(lái)處理,這樣是不是就可以提高了效率!我們接下來(lái)看一下我們上述所講的 demo 代碼。
2、5、1 Tasks.hpp
#pragma once #include <iostream> #include <string> #include <vector> #include <unordered_map> #include <functional> #include <cstdlib> #include <ctime> #include <cassert> #include <unistd.h> #include <sys/wait.h> #include <sys/types.h> typedef std::function<void()> func; std::vector<func> callbacks; std::unordered_map<int, std::string> desc; void readMySQL() { std::cout << "sub process[" << getpid() << " ] 執(zhí)行訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)的任務(wù)\n" << std::endl; } void execuleUrl() { std::cout << "sub process[" << getpid() << " ] 執(zhí)行url解析\n" << std::endl; } void cal() { std::cout << "sub process[" << getpid() << " ] 執(zhí)行加密任務(wù)\n" << std::endl; } void save() { std::cout << "sub process[" << getpid() << " ] 執(zhí)行數(shù)據(jù)持久化任務(wù)\n" << std::endl; } void load() { desc.insert({callbacks.size(), "readMySQL: 讀取數(shù)據(jù)庫(kù)"}); callbacks.push_back(readMySQL); desc.insert({callbacks.size(), "execuleUrl: 進(jìn)行url解析"}); callbacks.push_back(execuleUrl); desc.insert({callbacks.size(), "cal: 進(jìn)行加密計(jì)算"}); callbacks.push_back(cal); desc.insert({callbacks.size(), "save: 進(jìn)行數(shù)據(jù)的文件保存"}); callbacks.push_back(save); } void showHandler() { for(const auto &iter : desc ) { std::cout << iter.first << "\t" << iter.second << std::endl; } } int handlerSize() { return callbacks.size(); }
2、5、2?ProcessPool.cpp
#include "Task.hpp" #define PROCESS_NUM 5 using namespace std; int waitCommand(int waitFd, bool &quit) //如果對(duì)方不發(fā),我們就阻塞 { uint32_t command = 0; ssize_t s = read(waitFd, &command, sizeof(command)); if (s == 0) { quit = true; return -1; } assert(s == sizeof(uint32_t)); return command; } void sendAndWakeup(pid_t who, int fd, uint32_t command) { write(fd, &command, sizeof(command)); cout << "main process: call process " << who << " execute " << desc[command] << " through " << fd << endl; } int main() { // 代碼中關(guān)于fd的處理,有一個(gè)小問(wèn)題,不影響我們使用,但是你能找到嗎?? load(); // pid: pipefd vector<pair<pid_t, int>> slots; // 先創(chuàng)建多個(gè)進(jìn)程 for (int i = 0; i < PROCESS_NUM; i++) { // 創(chuàng)建管道 int pipefd[2] = {0}; int n = pipe(pipefd); assert(n == 0); (void)n; pid_t id = fork(); assert(id != -1); // 子進(jìn)程我們讓他進(jìn)行讀取 if (id == 0) { // 關(guān)閉寫(xiě)端 close(pipefd[1]); // child while (true) { // pipefd[0] // 等命令 bool quit = false; // 等待任務(wù)派發(fā) int command = waitCommand(pipefd[0], quit); //如果對(duì)方不發(fā),我們就阻塞 if (quit) break; // 執(zhí)行對(duì)應(yīng)的命令 if (command >= 0 && command < handlerSize()) { callbacks[command](); } else { cout << "非法command: " << command << endl; } } exit(1); } // father,進(jìn)行寫(xiě)入,關(guān)閉讀端 close(pipefd[0]); // pipefd[1] slots.push_back(pair<pid_t, int>(id, pipefd[1])); } // 父進(jìn)程派發(fā)任務(wù) srand((unsigned long)time(nullptr) ^ getpid() ^ 23323123123L); // 讓數(shù)據(jù)源更隨機(jī) while (true) { // 自動(dòng)派發(fā)任務(wù) // 選擇一個(gè)任務(wù) int command = rand() % handlerSize(); // 選擇一個(gè)進(jìn)程 ,采用隨機(jī)數(shù)的方式,選擇進(jìn)程來(lái)完成任務(wù),隨機(jī)數(shù)方式的負(fù)載均衡 int choice = rand() % slots.size(); // 把任務(wù)給指定的進(jìn)程 sendAndWakeup(slots[choice].first, slots[choice].second, command); sleep(1); // 手動(dòng)選擇派發(fā)任務(wù) // int select; // int command; // cout << "############################################" << endl; // cout << "# 1. show funcitons 2.send command #" << endl; // cout << "############################################" << endl; // cout << "Please Select> "; // cin >> select; // if (select == 1) // showHandler(); // else if (select == 2) // { // cout << "Enter Your Command> "; // // 選擇任務(wù) // cin >> command; // // 選擇進(jìn)程 // int choice = rand() % slots.size(); // // 把任務(wù)給指定的進(jìn)程 // sendAndWakeup(slots[choice].first, slots[choice].second, command); // } // else // { // } } // 關(guān)閉fd, 所有的子進(jìn)程都會(huì)退出 for (const auto &slot : slots) { close(slot.second); } // 回收所有的子進(jìn)程信息 for (const auto &slot : slots) { waitpid(slot.first, nullptr, 0); } }
2、5、3 demo 代碼解釋
? ? 給定的代碼是一個(gè)C++程序,它創(chuàng)建多個(gè)子進(jìn)程,并使用管道進(jìn)行進(jìn)程間通信來(lái)分配任務(wù)。讓我們逐步了解代碼:
- `waitCommand` 函數(shù)用于等待通過(guò)文件描述符(`waitFd`)發(fā)送的命令。它從文件描述符中讀取一個(gè)無(wú)符號(hào)32位整數(shù),并返回命令值。如果讀操作返回0,意味著另一端已關(guān)閉連接,將設(shè)置 `quit` 標(biāo)志為 true,退出程序。
- `sendAndWakeup` 函數(shù)負(fù)責(zé)通過(guò)文件描述符(`fd`)向由進(jìn)程ID(`who`)標(biāo)識(shí)的進(jìn)程發(fā)送命令。它將命令值寫(xiě)入文件描述符,并顯示指示正在執(zhí)行的進(jìn)程和命令的消息。
- 在 `main` 函數(shù)中,調(diào)用了 `load` 函數(shù),該函數(shù)加載程序所需的一些必要數(shù)據(jù)(上述代碼就是加在加載我們所需要的任務(wù))。
- 創(chuàng)建了一個(gè)名為 `slots` 的pair vector,用于存儲(chǔ)子進(jìn)程的進(jìn)程ID和文件描述符。
- 程序進(jìn)入循環(huán)以創(chuàng)建多個(gè)子進(jìn)程。在每次迭代中,使用 `pipe` 函數(shù)創(chuàng)建一個(gè)管道,提供一個(gè)數(shù)組 `pipefd` 以保存讀端和寫(xiě)端的文件描述符。然后調(diào)用 `fork` 函數(shù)來(lái)創(chuàng)建一個(gè)子進(jìn)程,并通過(guò)檢查返回的ID是否為0來(lái)區(qū)分子進(jìn)程。在子進(jìn)程中,關(guān)閉管道的寫(xiě)端,并進(jìn)入一個(gè)無(wú)限循環(huán)以等待命令并執(zhí)行它們。如果收到無(wú)效命令,顯示一條消息。最后,子進(jìn)程退出。
- 在父進(jìn)程中,關(guān)閉管道的讀端,并將進(jìn)程ID和寫(xiě)端文件描述符添加到 `slots` 向量中。
- 父進(jìn)程進(jìn)入另一個(gè)循環(huán)來(lái)分配任務(wù)給子進(jìn)程。在每次迭代中,使用 `rand` 函數(shù)生成從0到 `handlerSize()` 的隨機(jī)命令索引。然后,從 `slots` 向量中選擇一個(gè)隨機(jī)子進(jìn)程,使用 `sendAndWakeup` 函數(shù)將選擇的命令發(fā)送給該進(jìn)程。發(fā)送命令后,程序暫停1秒鐘,然后進(jìn)行下一次迭代。
- 上述循環(huán)不斷地將任務(wù)分配給子進(jìn)程,確保它們執(zhí)行各自的任務(wù)。還有被注釋掉的代碼,可以手動(dòng)選擇要發(fā)送給子進(jìn)程的命令,但目前未激活。
? 總之,此代碼使用管道進(jìn)行進(jìn)程間通信創(chuàng)建多個(gè)子進(jìn)程,并隨機(jī)將任務(wù)分配給這些子進(jìn)程。子進(jìn)程不斷等待命令并執(zhí)行它們,而父進(jìn)程則選擇隨機(jī)命令并將其發(fā)送給一個(gè)子進(jìn)程。
三、命名管道
3、1 什么是命名管道
??命名管道(Named Pipe),也被稱(chēng)為FIFO(First In, First Out)管道,是一種特殊類(lèi)型的文件,用于實(shí)現(xiàn)不同進(jìn)程之間的通信。它允許兩個(gè)或多個(gè)進(jìn)程在系統(tǒng)中通過(guò)一個(gè)公共的命名管道進(jìn)行雙向通信。
??命名管道有一個(gè)在文件系統(tǒng)中可見(jiàn)的唯一名稱(chēng),而匿名管道沒(méi)有。在創(chuàng)建命名管道時(shí),需要指定一個(gè)路徑和名稱(chēng);而創(chuàng)建匿名管道時(shí),不需要提供名稱(chēng)。
? 我們?cè)贚inux下進(jìn)行測(cè)試,使用命令進(jìn)行創(chuàng)建一個(gè)命名管道。具體如下:
- 首先,我們需要使用系統(tǒng)調(diào)用函數(shù)mkfifo來(lái)創(chuàng)建一個(gè)命名管道。通過(guò)指定一個(gè)路徑和一個(gè)唯一的名稱(chēng),可以創(chuàng)建一個(gè)新的命名管道文件。指令如下:
//mkfifo 路徑+文件名稱(chēng) mkfifo ./mypipe
? 創(chuàng)建的結(jié)果如下圖所示:
接下來(lái)我們?cè)俅蜷_(kāi)命名管道。當(dāng)然我們可以使用代碼進(jìn)行打開(kāi)管道(下文會(huì)對(duì)此講解),也可以直接向管道輸入內(nèi)容。具體如下圖:
? 通過(guò)上述情況,我們發(fā)現(xiàn)當(dāng)向管道輸入內(nèi)容后,相當(dāng)于就是打開(kāi)了管道的寫(xiě)端。這是我們必須打開(kāi)管道的讀端進(jìn)行讀取。否則寫(xiě)端就會(huì)認(rèn)為該操作(進(jìn)程)一直在運(yùn)行,并未結(jié)束。
3、2 命名管道通信
? 命名管道的通信與匿名管道的通信基本相同。都需要看到同一塊資源。但是匿名管道并不需要在磁盤(pán)上建立文件。命名管道是有一個(gè)唯一的路徑和名稱(chēng)的。那進(jìn)行通信的時(shí)候,會(huì)不會(huì)要進(jìn)行磁盤(pán)I/O呢?命名管道通信會(huì)不會(huì)效率很慢呢?我們接著往下看。
? 我們之前學(xué)習(xí)中,C語(yǔ)言模擬實(shí)現(xiàn)進(jìn)度條小程序中提到了輸出緩沖區(qū)。 同時(shí)在上述的匿名管道的demo代碼中,也定義了我們自己的緩沖區(qū)(buffer[ ])。當(dāng)然,因?yàn)檫@時(shí)的磁盤(pán)文件時(shí)用來(lái)通信的。是一個(gè)進(jìn)程的數(shù)據(jù)讓另一個(gè)進(jìn)程看到,并不需要把數(shù)據(jù)刷新到磁盤(pán)。數(shù)據(jù)傳輸是通過(guò)內(nèi)存緩沖區(qū)完成的,進(jìn)程之間直接交換數(shù)據(jù),不需要寫(xiě)入硬盤(pán)。命名管道的數(shù)據(jù)傳輸緩沖區(qū)位于內(nèi)存中。當(dāng)進(jìn)程向命名管道寫(xiě)入數(shù)據(jù)時(shí),數(shù)據(jù)會(huì)被暫存在內(nèi)存緩沖區(qū)中。接收進(jìn)程從該緩沖區(qū)中讀取數(shù)據(jù)。因此實(shí)際上并不會(huì)效率很低。
3、3 命名管道 demo 代碼
3、3、1 mkfifo
? 我們了解了利用指令創(chuàng)建命名管道。那么我們想通過(guò)接口來(lái)創(chuàng)建命名管道就可以使用mkfifo函數(shù)。具體使用如下圖:
? 我們看一下mkfifo的具體使用方法。函數(shù)原型:
int mkfifo(const char *pathname, mode_t mode);
? 這個(gè)函數(shù)接受兩個(gè)參數(shù),
pathname
表示要?jiǎng)?chuàng)建的管道的路徑名,mode
表示設(shè)置文件權(quán)限的模式。下面我們看一個(gè)舉例的例子:#include <sys/types.h> #include <sys/stat.h> #include <stdio.h> int main() { umask(0); // 設(shè)置文件權(quán)限掩碼為0,確保創(chuàng)建的管道擁有指定的權(quán)限 int result = mkfifo("/tmp/myfifo", 0666); if (result == 0) { printf("命名管道創(chuàng)建成功\n"); } else { perror("命名管道創(chuàng)建失敗"); } return 0; }
? 具體運(yùn)行結(jié)果如下:
? ?然后,另一個(gè)進(jìn)程就可以打開(kāi)此文件進(jìn)行通信了!命名管道應(yīng)用的一個(gè)限制就是只能在具有共同祖先(具有親緣關(guān)系)的進(jìn)程間通信。下面我們來(lái)模擬一下命名管道通信的方式。
3、3、2 demo 代碼
?
// comm.hpp #pragam once #include <iostream> #include <string> #include <cstdio> #include <cstring> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/wait.h> #include <fcntl.h> #include <ctime> #include "Log.hpp" using namespace std; #define MODE 0666 #define SIZE 128 string ipcPath = "./fifo.ipc"; // Log.hpp #define Debug 0 #define Notice 1 #define Warning 2 #define Error 3 const std::string msg[] = { "Debug", "Notice", "Warning", "Error" }; std::ostream &Log(std::string message, int level) { std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message; return std::cout; } // client.cpp #include "comm.hpp" int main() { // 1. 獲取管道文件 int fd = open(ipcPath.c_str(), O_WRONLY); if(fd < 0) { perror("open"); exit(1); } // 2. ipc過(guò)程 string buffer; while(true) { cout << "Please Enter Message Line :> "; std::getline(std::cin, buffer); write(fd, buffer.c_str(), buffer.size()); } // 3. 關(guān)閉 close(fd); return 0; } //server.cpp #include "comm.hpp" static void getMessage(int fd) { char buffer[SIZE]; while (true) { memset(buffer, '\0', sizeof(buffer)); ssize_t s = read(fd, buffer, sizeof(buffer) - 1); if (s > 0) { cout <<"[" << getpid() << "] "<< "client say> " << buffer << endl; } else if (s == 0) { // end of file cerr <<"[" << getpid() << "] " << "read end of file, clien quit, server quit too!" << endl; break; } else { // read error perror("read"); break; } } } int main() { // 1. 創(chuàng)建管道文件 if (mkfifo(ipcPath.c_str(), MODE) < 0) { perror("mkfifo"); exit(1); } Log("創(chuàng)建管道文件成功", Debug) << " step 1" << endl; // 2. 正常的文件操作 int fd = open(ipcPath.c_str(), O_RDONLY); if (fd < 0) { perror("open"); exit(2); } Log("打開(kāi)管道文件成功", Debug) << " step 2" << endl; int nums = 3; for (int i = 0; i < nums; i++) { pid_t id = fork(); if (id == 0) { // 3. 編寫(xiě)正常的通信代碼了 getMessage(fd); exit(1); } } for(int i = 0; i < nums; i++) { waitpid(-1, nullptr, 0); } // 4. 關(guān)閉文件 close(fd); Log("關(guān)閉管道文件成功", Debug) << " step 3" << endl; unlink(ipcPath.c_str()); // 通信完畢,就刪除文件 Log("刪除管道文件成功", Debug) << " step 4" << endl; return 0; }
? 這段代碼是一個(gè)簡(jiǎn)單的進(jìn)程間通信(IPC)示例,使用命名管道(FIFO)實(shí)現(xiàn)。下面對(duì)代碼進(jìn)行詳細(xì)解釋?zhuān)?/p>
comm.hpp:
- 定義了ipcPath字符串變量,表示管道文件的路徑。
- 定義了MODE和SIZE常量,分別表示管道文件的權(quán)限和緩沖區(qū)的大小。
Log.hpp:
- 定義了Debug、Notice、Warning和Error四個(gè)級(jí)別的日志常量。
- 定義了msg數(shù)組,存儲(chǔ)了每個(gè)級(jí)別的日志名稱(chēng)。
- 實(shí)現(xiàn)了Log函數(shù),用于打印日志信息。
client.cpp:
- 主函數(shù)中首先打開(kāi)管道文件(以只寫(xiě)方式)。
- 然后通過(guò)循環(huán)從標(biāo)準(zhǔn)輸入讀取用戶(hù)輸入的消息,并將消息寫(xiě)入管道中。
server.cpp:
- 首先通過(guò)mkfifo函數(shù)創(chuàng)建一個(gè)管道文件。
- 然后打開(kāi)管道文件(以只讀方式)。
- 創(chuàng)建了3個(gè)子進(jìn)程,每個(gè)子進(jìn)程通過(guò)getMessage函數(shù)讀取管道文件中的消息,并打印到標(biāo)準(zhǔn)輸出。
- 父進(jìn)程通過(guò)waitpid函數(shù)等待所有子進(jìn)程退出。
- 最后關(guān)閉管道文件,刪除管道文件。
? 該代碼演示了父進(jìn)程和多個(gè)子進(jìn)程之間通過(guò)命名管道進(jìn)行通信的過(guò)程。父進(jìn)程創(chuàng)建了一個(gè)管道文件,并打開(kāi)該文件以便讀取子進(jìn)程寫(xiě)入的消息,同時(shí)創(chuàng)建多個(gè)子進(jìn)程來(lái)同時(shí)處理不同的客戶(hù)請(qǐng)求。每個(gè)子進(jìn)程通過(guò)讀取管道文件中的消息來(lái)獲取客戶(hù)端發(fā)送的數(shù)據(jù),并打印到標(biāo)準(zhǔn)輸出。父進(jìn)程等待所有子進(jìn)程退出后,關(guān)閉管道文件并刪除管道文件。這樣就完成了進(jìn)程間的通信。
四、總結(jié)
??命名管道(named pipe)和匿名管道(anonymous pipe)是用于進(jìn)程間通信的機(jī)制,它們?cè)谀承┓矫嫦嗤灿幸恍﹨^(qū)別。
相同點(diǎn):
- 用途:兩種管道都可以用于進(jìn)程間的通信。進(jìn)程可以通過(guò)管道在同一臺(tái)計(jì)算機(jī)上進(jìn)行數(shù)據(jù)交換。
- 工作原理:管道都提供了一個(gè)單向的、先進(jìn)先出(FIFO)的數(shù)據(jù)流。進(jìn)程通過(guò)寫(xiě)入端將數(shù)據(jù)寫(xiě)入管道,在讀取端從管道中讀取數(shù)據(jù)。
- 實(shí)現(xiàn)方式:管道通常由操作系統(tǒng)提供支持,通過(guò)在內(nèi)核中創(chuàng)建一個(gè)緩沖區(qū)來(lái)實(shí)現(xiàn)。
區(qū)別:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-697178.html
- 命名管道有一個(gè)在文件系統(tǒng)中可見(jiàn)的唯一名稱(chēng),而匿名管道沒(méi)有。在創(chuàng)建命名管道時(shí),需要指定一個(gè)路徑和名稱(chēng);而創(chuàng)建匿名管道時(shí),不需要提供名稱(chēng)。
- 命名管道可以允許無(wú)關(guān)的進(jìn)程之間進(jìn)行通信,而匿名管道通常只用于相關(guān)的父子進(jìn)程間通信。
- 命名管道使用文件系統(tǒng)的權(quán)限和屬性,可以像普通文件一樣進(jìn)行操作(如權(quán)限控制、查看文件大小等),而匿名管道沒(méi)有關(guān)聯(lián)的文件系統(tǒng)屬性。
- 命名管道可以由多個(gè)進(jìn)程同時(shí)讀取或?qū)懭?,而匿名管道只能由在?chuàng)建時(shí)有親屬關(guān)系的進(jìn)程之間進(jìn)行通信。
? 本篇文章的內(nèi)容就將接到這里,下篇內(nèi)容會(huì)講解到共享內(nèi)存(System V)和信號(hào)量。感謝閱讀。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-697178.html
到了這里,關(guān)于【Linux從入門(mén)到精通】通信 | 管道通信(匿名管道 & 命名管道)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!