太原網(wǎng)站推廣服務(wù)seo推廣seo技術(shù)培訓(xùn)
文章目錄
- 一、管道
- 1. 匿名管道
- 2. 命名管道
進(jìn)程具有獨(dú)立性,因此進(jìn)程間通信的前提是兩個(gè)進(jìn)程能看到同一份資源
一、管道
對(duì)于進(jìn)程打開的內(nèi)存文件,操作系統(tǒng)是以引用計(jì)數(shù)的方式創(chuàng)建的 file 結(jié)構(gòu)體,如果讓兩個(gè)進(jìn)程與同一個(gè) file 結(jié)構(gòu)體關(guān)聯(lián),便可以讓兩個(gè)進(jìn)程看到同一份資源
由于 file 結(jié)構(gòu)體的緩沖區(qū)只有一個(gè),因此只能讓一個(gè)進(jìn)程以寫的方式,另一個(gè)進(jìn)程以讀的方式打開同一個(gè)文件,這樣便實(shí)現(xiàn)了單向的進(jìn)程間通信
操作系統(tǒng)提供了僅在內(nèi)存中創(chuàng)建 file 結(jié)構(gòu)體(不在磁盤上創(chuàng)建對(duì)應(yīng)的文件),這種特殊的文件稱為管道文件,其中沒(méi)有名字的稱為 匿名管道,有名字的稱為 命名管道
1. 匿名管道
進(jìn)程創(chuàng)建匿名管道成功后,會(huì)以讀和寫兩種方式打開管道文件,因此匿名管道通常用于父子進(jìn)程的進(jìn)程間通信
系統(tǒng)調(diào)用 pipe,頭文件 unistd.h
- int pipe(int pipefd[2]),創(chuàng)建匿名管道
返回值:匿名管道創(chuàng)建成功返回 0,出錯(cuò)返回 -1,并且 errno 被設(shè)置為相應(yīng)的出錯(cuò)信息
參數(shù):pipefd 為輸出型參數(shù),pipefd[0] 存儲(chǔ)讀端文件描述符,pipefd[1] 存儲(chǔ)寫端文件描述符
父子進(jìn)程通信的步驟:
- 父進(jìn)程創(chuàng)建管道
父進(jìn)程以讀和寫兩種方式打開管道文件 - 創(chuàng)建子進(jìn)程
子進(jìn)程會(huì)拷貝父進(jìn)程的文件描述符表,因此子進(jìn)程也會(huì)以讀和寫兩種方式打開同一個(gè)管道文件 - 父進(jìn)程和子進(jìn)程分別關(guān)閉自己不需要的讀端或?qū)懚?/strong>
一個(gè)進(jìn)程向管道中寫入,另一個(gè)進(jìn)程從管道中讀取
子進(jìn)程向匿名管道寫入,父進(jìn)程從匿名管道讀取
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>using namespace std;int main()
{// 創(chuàng)建匿名管道// pipefd[0] 表示讀// pipefd[1] 表示寫int pipefd[2] = { 0 };if (pipe(pipefd) < 0){// 創(chuàng)建匿名管道失敗cout << "匿名管道創(chuàng)建失敗: " << errno << " " << strerror(errno) << endl;exit(1);}// 創(chuàng)建子進(jìn)程pid_t id = fork();assert(id != -1);if (id == 0){// 子進(jìn)程向匿名管道寫入,需要關(guān)閉讀close(pipefd[0]);// 開始通信int cnt = 0;char buffer[64];while (true){snprintf(buffer, sizeof(buffer), "我是子進(jìn)程,這是我給你發(fā)的第 %d 個(gè)信息", ++cnt);// 向匿名管道中寫入write(pipefd[1], buffer, strlen(buffer));cout << cnt << endl;// sleep(1); // 讓寫端慢一點(diǎn)// if (cnt == 3) break; // 模擬寫端關(guān)閉}close(pipefd[1]);exit(0);}// 父進(jìn)程從匿名管道讀取,需要關(guān)閉寫close(pipefd[1]);// 開始通信char buffer[64];while (true){// 從匿名管道中讀取// sleep(3); // 讓讀端慢一點(diǎn)// sleep(3); break; // 模擬讀端關(guān)閉int n = read(pipefd[0], buffer, sizeof(buffer) - 1);if (n > 0){ buffer[n] = '\0';cout << buffer << endl;}else if (n == 0){cout << "寫端已經(jīng)關(guān)閉,我讀到文件結(jié)尾了" << endl;break;}else{cout << "讀取錯(cuò)誤" << endl;break;}}close(pipefd[0]);// 讀端關(guān)閉,寫端會(huì)收到 13 號(hào)信號(hào) SIGPIPEint status;waitpid(id, &status, 0);cout << "子進(jìn)程退出信號(hào): " << (status & 0x7F) << endl;return 0;
}
- 放開子進(jìn)程代碼中的 sleep(1),讓寫端慢一點(diǎn),匿名管道中沒(méi)有數(shù)據(jù)時(shí),讀端會(huì)等待寫端寫入
- 放開父進(jìn)程中的 sleep(3),讓讀端慢一點(diǎn),匿名管道中寫滿數(shù)據(jù)時(shí),寫端會(huì)等待讀端讀取
- 放開子進(jìn)程中模擬寫端關(guān)閉的代碼,并且讓寫端慢一點(diǎn),寫端關(guān)閉后,讀端讀取完匿名管道中的數(shù)據(jù)后,讀端會(huì)讀取到文件結(jié)尾
- 放開子進(jìn)程中模擬讀端關(guān)閉的代碼,并且讓寫端慢一點(diǎn),讀端關(guān)閉時(shí),此時(shí)匿名管道無(wú)意義,操作系統(tǒng)會(huì)向?qū)懚税l(fā)送 SIGPIPE 13 號(hào)信號(hào)
命令行中的 | 即為匿名管道,| 會(huì)將前一個(gè)進(jìn)程的標(biāo)準(zhǔn)輸出重定向到匿名管道,后一個(gè)進(jìn)程的標(biāo)準(zhǔn)輸入重定向到匿名管道
通過(guò)匿名管道創(chuàng)建進(jìn)程池:
// processpool.hpp
#include <iostream>
#include <vector>
#include <functional>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>namespace starrycat
{class EndPoint{public:EndPoint(){}EndPoint(pid_t cid, int wfd) : _cid(cid), _wfd(wfd){}~EndPoint(){}public:pid_t _cid;int _wfd;};const int NUM = 5;class ProcessPool{using fun_t = std::function<void(int)>;public:ProcessPool(fun_t Command, int num = NUM){std::vector<int> tmpfd; // 保存子進(jìn)程不需要的父進(jìn)程的文件描述符for (int i = 0; i < num; ++i){// 創(chuàng)建管道int pipefd[2] = {0};if (pipe(pipefd) < 0){std::cout << "創(chuàng)建管道失敗: " << errno << " " << strerror(errno) << std::endl;exit(1);}// 創(chuàng)建子進(jìn)程int id = fork();assert(id != -1);if (id == 0){// 子進(jìn)程從管道中讀取,需要關(guān)閉寫close(pipefd[1]);for (auto e : tmpfd) close(e); // 關(guān)閉不需要的文件描述符// 開始通信while (true){// 讀取四字節(jié)整數(shù)的命令int cmd = 0;int n = read(pipefd[0], &cmd, sizeof(int));if (n == sizeof(int)){// 測(cè)試std::cout << getpid() << " ";// 執(zhí)行命令Command(cmd);}else if (n == 0){// 測(cè)試std::cout << getpid() << " 讀取到文件結(jié)尾了" << std::endl;break;}else{std::cout << "讀取異常" << std::endl;break;}}close(pipefd[0]);exit(0);}// 父進(jìn)程向管道中寫入,需要關(guān)閉讀close(pipefd[0]);_endPoints.push_back(EndPoint(id, pipefd[1]));tmpfd.push_back(pipefd[1]);}}ProcessPool(const ProcessPool &p) = delete;ProcessPool &operator=(const ProcessPool &p) = delete;~ProcessPool(){for (auto& e : _endPoints) {close(e._wfd);waitpid(e._cid, nullptr, 0);// 測(cè)試std::cout << "等待子進(jìn)程:" << e._cid << "成功" << std::endl;sleep(1);}}// 規(guī)定命令為四字節(jié)整數(shù)void push(int command){// 以輪訓(xùn)的方式調(diào)用子進(jìn)程static int index = 0;write(_endPoints[index]._wfd, &command, sizeof(int));index++;index %= _endPoints.size();}private:std::vector<EndPoint> _endPoints;};
}// myctrlprocess.cc
#include "processpool.hpp"
#include <iostream>
#include <string>using namespace std;// 子進(jìn)程個(gè)數(shù)
const int num = 5;void PrintLog()
{cout << "打印日志任務(wù),正在被執(zhí)行..." << endl;
}void InsertMySQL()
{cout << "執(zhí)行數(shù)據(jù)庫(kù)任務(wù),正在被執(zhí)行..." << endl;
}void NetQuest()
{cout << "執(zhí)行網(wǎng)絡(luò)請(qǐng)求任務(wù),正在被執(zhí)行..." << endl;
}void CommandError()
{cout << "任務(wù)不存在" << endl;
}void Command(int cmd)
{switch (cmd){case 0:PrintLog();break;case 1:InsertMySQL();break;case 2:NetQuest();break;default:CommandError();break;}
}int main()
{starrycat::ProcessPool ppool(Command);int command = 0;while (true){cout << "請(qǐng)輸入命令: ";cin >> command;// 測(cè)試if (command == -1) break;ppool.push(command);sleep(1);}return 0;
}
2. 命名管道
命名管道支持兩個(gè)毫不相關(guān)的進(jìn)程通信,其使用和文件一樣
系統(tǒng)調(diào)用 mkfifo,頭文件 sys/types.h、sys/stat.h
- int mkfifo(const char *pathname, mode_t mode),創(chuàng)建命名管道
返回值:命名管道創(chuàng)建成功返回 0,出錯(cuò)返回 -1,并且 errno 被設(shè)置為相應(yīng)的出錯(cuò)信息
參數(shù):
- pathname 表示創(chuàng)建命名管道的路徑名(如果只有文件名,則表示在進(jìn)程所在的路徑下創(chuàng)建)
- mode 表示創(chuàng)建命名管道的文件權(quán)限,受 umask 影響
客戶端向命名管道寫入,服務(wù)端從命名管道讀取
// namepipe.h
#pragma once// 命名管道文件名
const char* const fifoname = "fifo";// client.cc
#include "namepipe.h"
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cerrno>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>using namespace std;int main()
{// 以寫方式打開命名管道int wfd = open(fifoname, O_WRONLY);if (wfd < 0){cout << "打開文件失敗" << errno << " " << strerror(errno) << endl;exit(1);}// 開始通信char buffer[1024];while (true){cout << "請(qǐng)輸入: ";char* str = fgets(buffer, sizeof(buffer), stdin);if (str == NULL){cout << "客戶端退出" << endl;break;}buffer[strlen(buffer) - 1] = '\0'; // 去掉輸入的回車符// 輸入 quit 表示客戶端退出if(strcmp(buffer, "quit") == 0) {cout << "客戶端退出" << endl;break;}// 向命名管道寫入write(wfd, buffer, strlen(buffer));}close(wfd);return 0;
}// server.cc
#include "namepipe.h"
#include <iostream>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>using namespace std;int main()
{// 創(chuàng)建命名管道umask(0); // 設(shè)置進(jìn)程的權(quán)限掩碼if (mkfifo(fifoname, 0666) < 0){cout << "創(chuàng)建管道失敗: " << errno << " " << strerror(errno) << endl;exit(1);}// 以讀方式打開命名管道int rfd = open(fifoname, O_RDONLY);if (rfd < 0){cout << "打開文件失敗" << errno << " " << strerror(errno) << endl;}// 開始通信char buffer[1024];while (true){// 從命名管道讀取int n = read(rfd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = '\0';cout << "客戶端: " << buffer << endl;}else if (n == 0){cout << "客戶端退出了,服務(wù)端也退出" << endl;break;}else{cout << "讀取異常" << endl;break;}}close(rfd); unlink(fifoname); // 刪除命名管道return 0;
}
先啟動(dòng) myserver,在啟動(dòng) myclient
命名管道和匿名管道的通信特性是一樣的
mkfifo 文件名
功能:創(chuàng)建命名管道