南京做網(wǎng)站seo免費(fèi)seo在線工具
🤖個(gè)人主頁:晚風(fēng)相伴-CSDN博客
💖如果覺得內(nèi)容對(duì)你有幫助的話,還請(qǐng)給博主一鍵三連(點(diǎn)贊💜、收藏🧡、關(guān)注💚)吧
🙏如果內(nèi)容有誤的話,還望指出,謝謝!!!
?下一篇文章:《進(jìn)程間通信之共享內(nèi)存》敬請(qǐng)期待💪
目錄
理解進(jìn)程間通信的本質(zhì)
管道
管道的分類
匿名管道?
匿名管道如何實(shí)現(xiàn)進(jìn)程間通信
?從文件描述符的角度理解匿名管道
用代碼實(shí)現(xiàn)匿名管道
匿名管道讀寫的4個(gè)特殊情況?
管道的特點(diǎn)
利用匿名管道設(shè)計(jì)一個(gè)進(jìn)程池
命名管道?
命名管道如何實(shí)現(xiàn)進(jìn)程間通信?
命名管道的創(chuàng)建
代碼實(shí)現(xiàn)命名管道?
理解進(jìn)程間通信的本質(zhì)
因?yàn)檫M(jìn)程具有獨(dú)立性,所以每個(gè)進(jìn)程都只知道自己,而不知道有另外的進(jìn)程存在,所以要實(shí)現(xiàn)不同進(jìn)程間的通信,就要讓不同的進(jìn)程都能看到同一塊資源,這塊資源不屬于任意一個(gè)進(jìn)程,而是強(qiáng)調(diào)共享,利用這塊資源就可以實(shí)現(xiàn)進(jìn)程間通信了。
總結(jié)一下要點(diǎn)?
- 進(jìn)程間通信的前提是要讓不同的進(jìn)程看到同一塊資源
- 這一塊資源不隸屬于任何一個(gè)進(jìn)程,而是被這些進(jìn)程所共享
管道
管道想必大家都不陌生吧,在Linux命令行中我們可以通過管道( | )將一個(gè)進(jìn)程輸出連接到另一個(gè)進(jìn)程的輸入,從而實(shí)現(xiàn)數(shù)據(jù)的傳輸、連接、過濾和處理等功能。例如
管道也好理解就比如家里面的水管,一端進(jìn)水,另一端出水。
管道的分類
- 匿名管道
- 命名管道?
🔥匿名管道?
匿名管道主要用于父子進(jìn)程之間的通信。?
創(chuàng)建匿名管道的接口
?
參數(shù):fildes是一個(gè)文件描述符數(shù)組,fildes[0]表示讀端,fildes[1]表示寫端
返回值:成功返回0,失敗則返回-1并設(shè)置對(duì)應(yīng)的錯(cuò)誤碼
🔥匿名管道如何實(shí)現(xiàn)進(jìn)程間通信
- 父進(jìn)程創(chuàng)建匿名管道,得到兩個(gè)文件描述符,一個(gè)文件描述符用來讀數(shù)據(jù),另一個(gè)文件描述符用來寫數(shù)據(jù)
- 調(diào)用fork創(chuàng)建子進(jìn)程,創(chuàng)建出來的子進(jìn)程會(huì)繼承父進(jìn)程創(chuàng)建管道時(shí)獲得的那兩個(gè)文件描述符
- 在父子進(jìn)程中關(guān)閉不需要的文件描述符,比如子進(jìn)程讀取數(shù)據(jù),父進(jìn)程寫入數(shù)據(jù),那就在子進(jìn)程中關(guān)閉寫入的文件描述符,父進(jìn)程中關(guān)閉讀取的文件描述符,之后就可以進(jìn)行相應(yīng)的通信操作了。
?🔥從文件描述符的角度理解匿名管道
用代碼實(shí)現(xiàn)匿名管道
#include <iostream>
#include <cstdio>
#include <cstring>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>using namespace std;int main()
{//1.創(chuàng)建管道int pipefd[2] = {0};//pipefd[0]:讀端,pipefd[1]:寫端int n = pipe(pipefd);assert(n != -1);//在Debug下才有用(void)n;//確保在release不報(bào)警告//條件編譯
#ifdef DEBUGcout << "pipefd[0]:" << pipefd[0] << endl;cout << "pipefd[1]:" << pipefd[1] << endl;
#endif//2.創(chuàng)建子進(jìn)程并構(gòu)建通信信道——父進(jìn)程寫入,子進(jìn)程讀取pid_t id = fork();assert(id != -1);if(id == 0){//子進(jìn)程close(pipefd[1]);//關(guān)閉子進(jìn)程不需要的fd//讀取數(shù)據(jù)char buffer[1024];while(true){//sleep(10);ssize_t s = read(pipefd[0], buffer, sizeof buffer - 1);if(s > 0){buffer[s] = 0;cout << "child get a massage [" << getpid() << "] father say:" << buffer << endl;}}close(pipefd[0]);exit(0);}//父進(jìn)程close(pipefd[0]);//關(guān)閉父進(jìn)程不需要的fd//發(fā)送數(shù)據(jù)string message = "我是父進(jìn)程,我正在給你發(fā)送數(shù)據(jù)";int count = 0;//統(tǒng)計(jì)發(fā)送次數(shù)char send_buffer[1024];while(true){//sleep(10);//將要發(fā)送的數(shù)據(jù)打入send_massage中snprintf(send_buffer, sizeof send_buffer, "%s[%d] : %d\n", message.c_str(), getpid(), count++);//寫入數(shù)據(jù)write(pipefd[1], send_buffer, strlen(send_buffer));sleep(1);}//等待子進(jìn)程退出pid_t ret = waitpid(id, nullptr, 0);assert(ret);(void)ret;close(pipefd[1]);return 0;
}
?運(yùn)行結(jié)果
🔥匿名管道讀寫的4個(gè)特殊情況?
情況一:寫端很快,讀端很慢,寫端將緩沖區(qū)寫滿后就不能再寫了,必須等讀端讀取后才能寫讓子進(jìn)程讀端sleep(10)秒鐘
情況二:寫端很慢,讀端很快,管道中沒有數(shù)據(jù)的時(shí)候,讀端必須等待寫端寫入數(shù)據(jù)之后才能讀
讓父進(jìn)程寫端sleep(10)秒鐘
情況三:寫端關(guān)閉,讀端讀到0時(shí)表示讀到了文件末尾
現(xiàn)象就是它會(huì)阻塞在那
情況四:讀端關(guān)閉,寫端繼續(xù)寫,當(dāng)將緩沖區(qū)寫滿后操作系統(tǒng)會(huì)終止寫進(jìn)程
🔥管道的特點(diǎn)
- 管道是用于具有親緣關(guān)系的進(jìn)程之間進(jìn)行進(jìn)程間通信(常用于父子進(jìn)程)
- 管道可以讓父子進(jìn)程之間進(jìn)行協(xié)同,并且提供了訪問控制(上面的4種情況就是訪問控制的體現(xiàn))
- 管道提供的是面向流式的通信服務(wù)(面向字節(jié)流)
- 管道的生命周期是隨進(jìn)程的
- 管道是單向通信的,也就是半雙工通信的一種特殊情況
完整代碼鏈接:匿名管道
利用匿名管道設(shè)計(jì)一個(gè)進(jìn)程池
原理:fork多個(gè)子進(jìn)程,然后用個(gè)隨機(jī)數(shù)式的負(fù)載均衡讓多個(gè)子進(jìn)程都有概率能被使用到
?
匿名管道版進(jìn)程池完整代碼鏈接:匿名管道版進(jìn)程池
🔥命名管道?
匿名管道的一個(gè)限制就是只能在具有親緣關(guān)系的進(jìn)程間通信,但是如果我們想在不相關(guān)的進(jìn)程之間實(shí)現(xiàn)通信,那么就需要用到命名管道。
🔥命名管道如何實(shí)現(xiàn)進(jìn)程間通信?
要實(shí)現(xiàn)進(jìn)程間通信本質(zhì)是要讓不同的進(jìn)程看到同一塊資源。命名管道其實(shí)是在磁盤上創(chuàng)建了一個(gè)管道文件,這個(gè)管道文件可以隨意被命名,并且有對(duì)應(yīng)的屬性但是沒有內(nèi)容,當(dāng)我們?cè)谝粋€(gè)進(jìn)程中打開這個(gè)管道文件并且將數(shù)據(jù)寫入這個(gè)文件中,另一個(gè)進(jìn)程也就可以打開這個(gè)管道文件并且從這個(gè)文件中讀取數(shù)據(jù),不同的進(jìn)程都能打開并使用這個(gè)管道文件,所以也就讓不同的進(jìn)程看到了同一塊資源。并且在這讀寫過程中管道文件中的數(shù)據(jù)不會(huì)加載到磁盤中,所以管道文件的大小始終為0保持不變。
命名管道的創(chuàng)建
在命令行上可以使用mkfifo命令來創(chuàng)建一個(gè)命名管道
也可以使用系統(tǒng)接口來創(chuàng)建命名管道
參數(shù):
- pathname:命名管道文件的路徑? ? ?
- mode:管道文件的權(quán)限
返回值:成功返回0,失敗則返回-1
代碼實(shí)現(xiàn)命名管道?
comm.hpp?
#ifndef _COMM_H_
#define _COMM_H_#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "log.hpp"using namespace std;#define MODE 0666 // 文件權(quán)限
#define SIZE 128string ipcPath = "./myfifo.ipc"; // 管道文件路徑#endif
server.cc
#include "comm.hpp"
#include <sys/wait.h>static void getMessage(int fd)
{// 進(jìn)行通信操作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){// 讀到文件結(jié)尾cerr << "[" << getpid() << "] "<< "read end of file, client quit, server quit too!" << endl;break;}else{// 讀取失敗perror("read");break;}}
}int main()
{// 創(chuàng)建管道文件if (mkfifo(ipcPath.c_str(), MODE) < 0){perror("mkfifo");exit(1);}logMessage(NORMAL, "創(chuàng)建管道文件成功, step 1"); // 打印日志// 打開管道文件int fd = open(ipcPath.c_str(), O_RDONLY);if (fd < 0){perror("open");exit(2);}logMessage(NORMAL, "打開管道文件成功, step 2");int nums = 5;for (int i = 0; i < nums; i++){pid_t id = fork();if (id == 0){// 子進(jìn)程搶占式讀取消息getMessage(fd);exit(1);}}// 等待子進(jìn)程退出for (int i = 0; i < nums; i++){waitpid(-1, nullptr, 0);}// 關(guān)閉文件close(fd);logMessage(NORMAL, "關(guān)閉管道文件成功, step 3");// 通信完成,將管道文件刪除unlink(ipcPath.c_str());logMessage(NORMAL, "刪除管道文件成功, step 4");return 0;
}
client.cc?
#include "comm.hpp"int main()
{//獲取管道文件int fd = open(ipcPath.c_str(), O_WRONLY);if(fd < 0){perror("open");exit(1);}//發(fā)送消息string buffer;while(true){cout << "Please Enter Message Line > ";getline(cin, buffer);write(fd, buffer.c_str(), buffer.size());}//關(guān)閉文件描述符close(fd);return 0;
}
完整代碼鏈接:命名管道