目錄
一.?進程間通信概述
二.?管道的概念?
三.?通過管道實現(xiàn)進程間通信
3.1?實現(xiàn)原理
3.2?匿名管道創(chuàng)建系統(tǒng)接口pipe
3.3?管道通信的模擬實現(xiàn)
3.4?管道通信的訪問控制規(guī)則
3.5?管道通信的特點
四.?通過匿名管道實現(xiàn)進程池
4.1?進程池的概念
4.2?進程池的模擬實現(xiàn)
五.?命名管道
5.1?命名管道的功能
5.2?命名管道的創(chuàng)建和使用
六.?總結(jié)
一.?進程間通信概述
進程間通信的目的:實現(xiàn)進程之間的數(shù)據(jù)傳輸、共享資源、事件通知、多進程協(xié)同等操作。‘
進程間通信的技術(shù)手段:進程間要實現(xiàn)通信,就必須要讓不同的進程看到同一塊資源(內(nèi)存空間),而由于進程之間具有獨立性,因此這塊資源不能隸屬于任何一個進程,應(yīng)當由操作系統(tǒng)內(nèi)核提供。
進程間通信的方法:管道、SystemV、POSIX
管道:匿名管道、命名管道。
System V:共享內(nèi)存、消息隊列、信號量 。--? ?用于本地計算機進行單機進程間通信
POSIX:消息隊列、共享內(nèi)存、信號量、互斥量、讀寫鎖、條件變量。 --? ?在網(wǎng)絡(luò)中,用于多機之間的進程間通信。

二.?管道的概念?
管道,是用于傳輸資源(數(shù)據(jù))的一種媒介,可以實現(xiàn)進程之間的單向通信(也只能單向通信)。
由于進程之間具有相互獨立性,因此,管道只能由操作系統(tǒng)內(nèi)核提供,不能源自任意進程,管道的本質(zhì)是一種內(nèi)存級文件,即:內(nèi)容不會被刷新到磁盤上的文件。

三.?通過管道實現(xiàn)進程間通信
3.1?實現(xiàn)原理
管道,尤其是匿名管道,一般用于具有親緣關(guān)系的進程之間的通信,其底層實現(xiàn)原理如下:
- 父進程以讀和寫的方式創(chuàng)建匿名管道,由于管道的本質(zhì)是文件,因此父進程會有兩個文件描述符fd分別指向管道(一個讀一個寫)。
- fork創(chuàng)建子進程,子進程的文件描述符及指向與父進程相同。
- 關(guān)閉不需要的文件描述符,一般父進程用于寫數(shù)據(jù),子進程用于讀數(shù)據(jù),因此父進程關(guān)閉用來讀的fd,子進程關(guān)閉用于寫的fd。
經(jīng)過上面的步驟,父進程的寫fd和子進程的讀fd就指向了相同的內(nèi)存級文件(管道),通過父進程向管道中寫的數(shù)據(jù),就能被子進程讀出來。

3.2?匿名管道創(chuàng)建系統(tǒng)接口pipe
原型:int pipe(int pipefd[2])
參數(shù):pipefd為輸出型參數(shù),pipefd[0]為讀端文件描述符,pipefd[1]為寫端文件描述符。
返回值:如果成功創(chuàng)建管道返回0,失敗返回-1并設(shè)置全局錯誤碼。
頭文件:#include <sys/fcntl.h>、#include <unistd.h>
一般pipe由父進程來調(diào)用,調(diào)用pipe后父進程要fork創(chuàng)建子進程,在父進程中一般要關(guān)閉pipefd[0],在子進程中一般要關(guān)閉pipefd[1]。

3.3?管道通信的模擬實現(xiàn)
代碼3.1通過管道,實現(xiàn)父進程向子進程發(fā)生消息,父進程每隔1s寫一次消息,子進程不間斷讀取并輸出消息,由于寫慢讀快,子進程需要阻塞等待父進程寫消息后才能讀。
代碼3.1:模擬實現(xiàn)管道通信
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/fcntl.h>
#define SIZE 1024
int main()
{
// 1. 父進程創(chuàng)建管道
int pipefd[2] = {0}; // 管道讀寫對應(yīng)的文件描述符
int n = pipe(pipefd); // 創(chuàng)建管道
if(n == -1) // 管道創(chuàng)建失敗
{
perror("pipe");
exit(1);
}
// 2. fork子進程
pid_t id = fork(); // 創(chuàng)建子進程
if(id < 0) //子進程創(chuàng)建失敗
{
perror("fork");
exit(2);
}
else if(id == 0) //子進程代碼
{
// 3. 子進程 -- 用于讀取管道中的數(shù)據(jù)
// 3.1 關(guān)閉不需要的文件描述符,子進程關(guān)寫pipefd[1]
close(pipefd[1]);
// 3.2 讀取數(shù)據(jù)并打印到標準輸出流
// 如果寫慢讀快,那么讀要等寫
// 如果寫快讀慢,那么等待管道被寫滿后,就不能再繼續(xù)寫
char read_buffer[1024] = {0}; // 讀數(shù)據(jù)文件緩沖區(qū)
while(true)
{
ssize_t sz = read(pipefd[0], read_buffer, SIZE - 1); //數(shù)據(jù)讀取
// 如果寫端退出,那么讀端read讀到0,讀端最終會退出
// 如果讀端退出,OS會強制終止寫端進程
if(sz > 0) // 確實讀到了數(shù)據(jù)
{
read_buffer[sz] = '\0';
std::cout << "Father# " << read_buffer << std::endl;
}
else if(sz == 0) // 寫端關(guān)閉
{
std::cout << "Father quit, write end, read end!" << std::endl;
break;
}
else // sz < 0
{
perror("read");
break;
}
}
close(pipefd[0]);
exit(0);
}
// 3. 父進程代碼 -- 用于寫數(shù)據(jù)
// 3.1 關(guān)閉不需要的文件描述符,父進程關(guān)讀pipdfd[0];
close(pipefd[0]);
// 3.2 向管道寫數(shù)據(jù)
const char* msg = "I am father process, I am sending message";
char send_buffer[SIZE] = {0}; // 寫數(shù)據(jù)緩沖區(qū)
int count = 0;
while(true)
{
snprintf(send_buffer, SIZE, "%s,%d", msg, ++count);
write(pipefd[1], send_buffer, strlen(send_buffer));
sleep(1);
}
close(pipefd[1]);
return 0;
}
3.4?管道通信的訪問控制規(guī)則
- 讀快寫慢:讀端需要阻塞等待寫端寫入數(shù)據(jù)后才能讀。
- 寫快讀慢:管道被寫滿后就不能繼續(xù)寫入,需要等待數(shù)據(jù)被讀出。
- 寫端關(guān)閉:讀端read讀到0,退出。
- 讀端關(guān)閉:OS強制終止寫端進程。
3.5?管道通信的特點
- 管道(匿名管道),常用于具有親緣關(guān)系的進程的進程間通信。
- 管道通信存在訪問控制。
- 管道通信是一種面向字節(jié)流式的通信。--?面向流式的通信:可以多次寫入的內(nèi)容一次讀取,也可以一次寫入的內(nèi)容分多次讀取。
- 管道的本質(zhì)是文件(內(nèi)存級文件),文件的生命周期隨進程的結(jié)束而結(jié)束,因此進程結(jié)束時,管道關(guān)閉。
- 管道通信為單向通信,是半雙工通信的一種特殊形式。
半雙工通信:通信雙方在某一時刻,只能單獨進行寫或讀。(并不是說某一端只能進行寫或讀,而是不能寫和讀同時進行)
全雙工通信:通信雙方可以寫和讀同時進行。
四.?通過匿名管道實現(xiàn)進程池
4.1?進程池的概念
父進程創(chuàng)建N個子進程,按照一定的規(guī)則向子進程派發(fā)任務(wù),父進程只負責向子進程派發(fā)任務(wù),具體的任務(wù)由子進程來完成。
如果父進程將均衡的向每個子進程派發(fā)任務(wù),這種算法稱為單機版負載均衡。

4.2?進程池的模擬實現(xiàn)
采用rand隨機生成來決定選用哪個子進程來執(zhí)行任務(wù)。
task.hpp文件:父進程派發(fā)的任務(wù)
#ifndef __TASK_DEFINE_
#define __TASK_DEFINE_
#include <iostream>
#include <vector>
#include <functional>
typedef std::function<void()> func; // 類型重定義
std::vector<func> trace_back; // 回調(diào)函數(shù)
std::vector<std::string> desc; // 任務(wù)編號及對應(yīng)說明
void execuleUrl()
{
std::cout << "execuleUrl" << std::endl;
}
void save()
{
std::cout << "save data" << std::endl;
}
void visitSQL()
{
std::cout << "visit SQL" << std::endl;
}
void online()
{
std::cout << "take online" << std::endl;
}
void load()
{
trace_back.emplace_back(execuleUrl);
desc.emplace_back("execuleUrl");
trace_back.emplace_back(save);
desc.emplace_back("save data");
trace_back.emplace_back(visitSQL);
desc.emplace_back("visit SQL");
trace_back.emplace_back(online);
desc.emplace_back("take online");
}
void show()
{
int count = 0;
for(const auto& msg : desc)
{
std::cout << count << ": " << msg << std::endl;
++count;
}
}
int handlerSize()
{
return trace_back.size();
}
#endif
PipePool.cc文件:進程池的實現(xiàn)源文件
#include <iostream>
#include <vector>
#include <ctime>
#include <cstdlib>
#include <cassert>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "task.hpp"
#define PROCESS_NUM 5
void distributeTask(int who, int fd, uint32_t command)
{
// 派發(fā)任務(wù)
ssize_t n = write(fd, &command, sizeof(uint32_t));
assert(n == sizeof(uint32_t));
}
uint32_t waitCommand(int fd, bool &quit)
{
uint32_t command = 0;
ssize_t n = read(fd, &command, sizeof(uint32_t));
if (n == 0)
{
quit = true;
return -1;
}
assert(n == sizeof(uint32_t));
return command;
}
int main()
{
load(); // 載入任務(wù)
// 創(chuàng)建子進程
int pipefd[2] = {0};
std::vector<std::pair<pid_t, int>> slot; // 記錄子進程id以及寫端文件描述符
for (int i = 0; i < PROCESS_NUM; ++i)
{
// 創(chuàng)建管道
int ret = pipe(pipefd);
if (ret == -1) // 管道創(chuàng)建失敗
{
perror("pipe");
exit(1);
}
// 創(chuàng)建子進程
pid_t id = fork();
if (id < 0) // 如果子進程創(chuàng)建失敗
{
perror("fork");
exit(2);
}
else if (id == 0) // 子進程代碼
{
// 子進程代碼
// 關(guān)閉不需要的文件描述符
close(pipefd[1]); // 子進程關(guān)寫
while (true)
{
// 阻塞等待指令
bool quit = false;
uint32_t command = waitCommand(pipefd[0], quit);
if (quit)
{
std::cout << "write close, read also close!" << std::endl;
break;
}
if (command >= 0 && command < handlerSize())
{
trace_back[command]();
}
else
{
std::cerr << "choice wrong" << std::endl;
}
}
close(pipefd[0]);
exit(0);
}
// 父進程代碼
close(pipefd[0]); // 關(guān)閉讀
slot.emplace_back(id, pipefd[1]); // 將子進程id和對應(yīng)寫端文件描述符插入順序表
}
srand((unsigned int)time(NULL));
// 父進程,開始派發(fā)任務(wù)
int select = 0;
while (true)
{
std::cout << "##############################" << std::endl;
std::cout << "## 1. show 2. choice ##" << std::endl;
std::cout << "##############################" << std::endl;
std::cout << "Please Select: > ";
std::cin >> select;
if (select == 1)
{
show();
}
else if (select == 2)
{
int choice = 0;
std::cout << "Please chose task: > ";
std::cin >> choice;
int proc = rand() % PROCESS_NUM; // 選擇子進程完成任務(wù)
distributeTask(slot[proc].first, slot[proc].second, choice); // 派發(fā)任務(wù)
usleep(100000);
}
else
{
std::cerr << "choice error" << std::endl;
break;
}
}
// 父進程關(guān)閉寫端,子進程退出
for (const auto &iter : slot)
{
close(iter.second);
}
// 父進程阻塞等待子進程退出
for (const auto &iter : slot)
{
waitpid(iter.first, NULL, 0);
}
return 0;
}
五.?命名管道
5.1?命名管道的功能
一般意義上的管道(匿名管道)只能實現(xiàn)父子進程之間的通信,而命名管道可以實現(xiàn)不相關(guān)進程之間的進程間通信。
命名管道是一種特殊類型的文件,具有以下特性:
- 可以被打開,但不會將內(nèi)存中的數(shù)據(jù)刷新到磁盤。
- 具有屬于自己的名稱。
- 在系統(tǒng)中有唯一的路徑。
兩個不相關(guān)的進程,可以通過訪問同一管道文件,來實現(xiàn)進程間通信。

5.2?命名管道的創(chuàng)建和使用
- 通過指令創(chuàng)建管道文件:mkfifo [文件名]? ?
- 刪除管道文件:unlink、rm均可

同樣,C語言庫函數(shù)中也有mkfifo,其功能也是創(chuàng)建管道文件,mkfifo庫函數(shù)的信息如下:
- 函數(shù)原型:int mkfifo(const char* pathname, mode_t mode);
- 函數(shù)參數(shù):pathname為創(chuàng)建的管道文件名和路徑,mode為起始權(quán)限。
- 返回值:創(chuàng)建成功返回0,失敗返回-1并設(shè)置全局錯誤碼。
也存在unlink庫函數(shù),用于刪除管道文件,原型為:int unlink(const char* pathname)
假設(shè)有兩個進程A和B,進程A以寫的方式打開管道文件,進程B以讀的方式打開管道文件,如果進程B先運行,需要等到進程A以寫的方式打開管道文件后,進程B才可以以讀打開的方式打開管道文件,否則,進程B要一直等待管道文件被以讀的方式打開。
代碼5.1通過命名管道,實現(xiàn)服務(wù)端進程(serve.exe)和客戶端進程(client.exe)之間的進程間通信,serve.cc創(chuàng)建管道文件,并以只讀方式打開管道文件,client.cc以只寫的方式打開管道文件,執(zhí)行代碼時先運行serve.exe,等待client.exe運行以只寫打開管道后,serve.exe才能執(zhí)行只讀打開管道文件的代碼,在client中輸入的信息,會顯示到serve中。文章來源:http://www.zghlxwxcb.cn/news/detail-650081.html
代碼5.1:命名管道實現(xiàn)不相關(guān)進程間的通信文章來源地址http://www.zghlxwxcb.cn/news/detail-650081.html
// log.hpp頭文件 -- 日志打印相關(guān)聲明和實現(xiàn)
// 日志操作
#include <iostream>
#include <string>
#include <ctime>
#ifndef __LOG_DEFINE_
#define __LOG_DEFINE_
#define DEBUG 0
#define NOTICE 1
#define WARNING 2
#define ERROR 3
std::string msg[] = {
"Debug",
"Notice",
"Waring",
"Error"
};
std::ostream& log(const std::string& message, int level)
{
std::cout << (unsigned int)time(NULL) << " | " << msg[level] << " | " << message << std::endl;
}
#endif
// common.hpp頭文件 -- 聲明宏,包含庫文件
#ifndef __COMMON_DEF_
#define __COMMON_DEF_
#include <iostream>
#include <cstdio>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include "log.hpp"
#define MODE 0666
#define SIZE 1024
#define PROCESS_NUM 3
std::string ipcPath = "fifo.ipc";
#endif
// serve.cc -- 服務(wù)端源文件代碼
#include "commom.hpp"
// 服務(wù)端函數(shù)
int main()
{
// 1. 以只寫的方式打開管道文件
int fd = open(ipcPath.c_str(), O_WRONLY);
if(fd < 0)
{
perror("client open");
exit(1);
}
// 2. 開始執(zhí)行進程間通信
std::string send_buffer; // 信息發(fā)送緩沖區(qū)
while(true)
{
std::cout << "請輸入要發(fā)送的信息: > ";
std::getline(std::cin, send_buffer); // 逐行讀取信息
write(fd, send_buffer.c_str(), send_buffer.size()); //寫數(shù)據(jù)
}
// 3. 關(guān)閉文件
close(fd);
return 0;
}
// client.cc -- 客戶端源文件代碼
#include "commom.hpp"
// 信息讀取函數(shù)
void GetMessage(int fd)
{
char read_buffer[SIZE] = {0}; //讀數(shù)據(jù)緩沖區(qū)
while(true)
{
ssize_t n = read(fd, read_buffer, SIZE - 1); //從管道文件讀數(shù)據(jù)
if(n > 0)
{
read_buffer[n] = '\0';
std::cout << "[ " << getpid() << " ] client say# " << read_buffer << std::endl;
}
else if(n == 0)
{
std::cout << "client quit, read end, serve quit too! " << std::endl;
break;
}
else // n < 0 -- 讀取出錯
{
perror("read");
break;
}
}
}
int main()
{
// 1. 創(chuàng)建命名管道文件
if(mkfifo(ipcPath.c_str(), MODE) < 0)
{
perror("mkfifo");
exit(1);
}
log("管道創(chuàng)建成功", DEBUG);
// 2. 服務(wù)端以只讀方式打開文件
int fd = open(ipcPath.c_str(), O_RDONLY);
if(fd < 0) //檢驗打開是否成功
{
perror("serve fopen");
exit(2);
}
log("文件打開成功", DEBUG);
// 3. 創(chuàng)建子進程,進行進程間通信(讀數(shù)據(jù))
for(int i = 0; i < PROCESS_NUM; ++i)
{
pid_t id = fork();
if(id == 0) //子進程代碼
{
GetMessage(fd); //信息讀取函數(shù)
exit(0);
}
}
// 4. 阻塞等待子進程退出
for(int i = 0; i < PROCESS_NUM; ++i)
{
waitpid(-1, NULL, 0);
}
log("子進程全部退出", NOTICE);
// 5. 關(guān)閉文件,刪除命名管道文件
close(fd);
unlink(ipcPath.c_str());
log("文件關(guān)閉成功", NOTICE);
// std::cout << "文件關(guān)閉成功" << std::endl;
return 0;
}

六.?總結(jié)
- 實現(xiàn)進程間通信的方式有三種,分別為管道、System V、POSIX,其中System V用于本地單機進程間通信,POSIX用于網(wǎng)絡(luò)進程間通信。
- 管道的本質(zhì)是內(nèi)存級文件,由OS內(nèi)核提供,管道一般用于具有親緣關(guān)系的進程間通信,管道通信為單向通信,是面向字節(jié)流式的通信,存在訪問控制,管道的生命周期隨進程的終止而終止。
- 通過命名管道,可以實現(xiàn)不相關(guān)進程間的通信。
到了這里,關(guān)于Linux系統(tǒng)編程:采用管道的方式實現(xiàn)進程間通信的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!