Linux探秘坊-------13.進程間通信

1.進程間通信?的

在這里插入圖片描述

2.管道

在這里插入圖片描述
在這里插入圖片描述

2.1 匿名管道

-----通常用來實現 父子通信

創建子進程時,需要把父進程的進程內容全部拷貝一份,但文件管理是不需要拷貝的 但是我們把父進程的文件描述符表給拷貝下來了,文件描述符表里是一堆指針他們仍然指向父進程打開的那些文件

在這里插入圖片描述

  • 這也是為什么之前運行子進程會在同一個屏幕上打印內容,因為父子進程用的是同一個顯示器文件自然在同一個屏幕上打印咯,
  • 和c++中遇到的 淺拷貝 十分相似

在這里插入圖片描述

在這里插入圖片描述

2.2 原理

在這里插入圖片描述

2.2 管道樣例

#include <iostream>
#include <unistd.h>using namespace std;
int main()
{int fd[2]={0};//這里使用fd模擬文件描述符表,忽略了0,1,2即標準輸入stdin,標準輸出stdout,標準錯誤stderrint n=pipe(fd);//pipe函數需要頭文件unistd.hif(n<0)//運行失敗會是n<0{cout<<"error"<<endl;return 1;}cout<<"fd[0]:"<<fd[0]<<endl;cout<<"fd[1]:"<<fd[1]<<endl;return 0;
}

在這里插入圖片描述

最終結果是:

在這里插入圖片描述

  • 因為0,1,2即標準輸入,標準輸出,標準錯誤一直在被打開,所以只能分配3,4

完整父子進程管道代碼:

#include <iostream>      // 標準輸入輸出(cout, endl)
#include <unistd.h>      // 提供 pipe(), fork(), close(), read(), write(), sleep() 等系統調用
#include <cstdio>        // 提供 printf() 等 C 標準 I/O 函數
#include <cstring>       // 提供字符串處理函數(如 memset)
#include <sys/types.h>   // 提供 pid_t 等數據類型定義
#include <sys/wait.h>    // 提供 waitpid() 函數using namespace std;// 子進程向管道寫入數據的函數
void childwrite(int wfd) {char c = 0;          // 寫入的字符(這里固定為 0)int cnt = 0;         // 計數器,記錄寫入次數while (true) {write(wfd, &c, 1); // 向管道寫入 1 字節(實際寫入的是 '\0')printf("child: %d\n", cnt++); // 打印寫入次數}
}// 父進程從管道讀取數據的函數
void fatherread(int rfd) {char buffer[1024];    // 讀取緩沖區while (true) {sleep(100);       // 父進程休眠 100 秒(實際會被 read() 打斷)buffer[0] = 0;    // 清空緩沖區(可選)// 從管道讀取數據(最多讀 sizeof(buffer)-1 字節,預留 1 字節給 '\0')ssize_t n = read(rfd, buffer, sizeof(buffer)-1);if (n > 0) {      // 讀取成功buffer[n] = 0; // 手動添加字符串結束符 '\0'std::cout << "child say: " << buffer << std::endl; // 打印讀取的內容} else if (n == 0) { // 管道寫端關閉(子進程退出)std::cout << "n : " << n << std::endl;std::cout << "child 退出,我也退出";break;} else {          // 讀取錯誤break;}break;            // 測試時提前退出循環(實際應去掉)}
}int main() {// 1. 創建管道int fd[2] = {0};      // fd[0]:讀端,fd[1]:寫端int n = pipe(fd);     // 調用 pipe() 創建匿名管道if (n < 0) {          // 創建失敗cout << "error" << endl;return 1;}cout << "fd[0]:" << fd[0] << endl; // 打印讀端 fdcout << "fd[1]:" << fd[1] << endl; // 打印寫端 fd// 2. 創建子進程pid_t pid = fork();   // 調用 fork() 創建子進程if (pid == 0) {       // 子進程邏輯close(fd[0]);     // 關閉讀端(子進程只寫)childwrite(fd[1]); // 調用子進程寫入函數close(fd[1]);     // 關閉寫端(實際不會執行到這里)exit(0);          // 子進程退出}sleep(5);             // 父進程休眠 5 秒(等待子進程寫入數據)close(fd[1]);         // 關閉寫端(父進程只讀)fatherread(fd[0]);    // 調用父進程讀取函數close(fd[0]);         // 關閉讀端// 等待子進程退出int status = 0;int ret = waitpid(pid, &status, 0); // 阻塞等待子進程結束if (ret > 0) {        // 子進程已退出// 打印子進程退出狀態(高 8 位是退出碼,低 7 位是終止信號)printf("exit code: %d, exit signal: %d\n", (status>>8)&0xFF, status&0x7F);sleep(5);         // 父進程再休眠 5 秒(觀察用)}return 0;
}

2.3 五種特性

在這里插入圖片描述

2.4 四種通信情況

在這里插入圖片描述

blog.csdnimg.cn/direct/eeef895593df4fd08b31442035e93198.png)

3.進程池的模擬

在這里插入圖片描述

3.1 hpp文件的使用

#ifndef __PROCESS_POOL_HPP__  // 頭文件保護宏(雙下劃線風格)
#define __PROCESS_POOL_HPP__#include <iostream>  // 系統頭文件用尖括號<>// 函數聲明/定義
void test() {std::cout << "test" << std::endl;  // 直接使用std::前綴
}#endif  // __PROCESS_POOL_HPP__

-函數的聲明和定義可以放在一塊寫,注意頭兩行和末尾一行 是格式

3.2 進程池代碼實現

ProcessPool.hpp:

#ifndef __PROCESS_POOL_HPP__  // 頭文件保護宏,防止重復包含
#define __PROCESS_POOL_HPP__#include <iostream>      // 標準輸入輸出
#include <cstdlib>       // C標準庫(替代stdlib.h的C++版本)
#include <vector>        // 動態數組容器
#include <unistd.h>      // POSIX API(pipe/fork/close等)
#include <sys/wait.h>    // 進程等待相關函數
#include "Task.hpp"      // 自定義任務管理頭文件// Channel類:管理單個子進程的通信通道
class Channel
{
public:// 構造函數:初始化寫端fd和子進程IDChannel(int fd, pid_t id) : _wfd(fd), _subid(id){// 生成通道名稱(格式:channel-[fd]-[pid])_name = "channel-" + std::to_string(_wfd) + "-" + std::to_string(_subid);}// 析構函數(空實現,資源通過Close()顯式釋放)~Channel() {}// 向子進程發送任務碼void Send(int code){int n = write(_wfd, &code, sizeof(code));(void)n; // 顯式忽略返回值(避免編譯器警告)}// 關閉寫端文件描述符void Close(){close(_wfd); // 關閉管道寫端}// 等待子進程退出,回收子進程,避免僵尸進程出現void Wait(){pid_t rid = waitpid(_subid, nullptr, 0); // 阻塞等待(void)rid; // 顯式忽略返回值}// Getter方法int Fd() { return _wfd; }             // 獲取寫端fdpid_t SubId() { return _subid; }      // 獲取子進程PIDstd::string Name() { return _name; }  // 獲取通道名稱private:int _wfd;            // 管道寫端文件描述符pid_t _subid;        // 子進程PIDstd::string _name;   // 通道標識名稱
};// ChannelManager類:管理所有子進程通道
class ChannelManager
{
public:ChannelManager() : _next(0) {}  // 初始化輪詢索引// 添加新通道void Insert(int wfd, pid_t subid){_channels.emplace_back(wfd, subid); // 原地構造Channel對象,加入channel數組}// 輪詢選擇下一個通道(簡單負載均衡)Channel &Select(){auto &c = _channels[_next];_next = (_next + 1) % _channels.size(); // 環形選擇return c;}// 打印所有通道信息void PrintChannel(){for (auto &channel : _channels){std::cout << channel.Name() << std::endl;}}// 關閉所有子進程管道void StopSubProcess(){for (auto &channel : _channels){channel.Close();//關掉讀std::cout << "關閉: " << channel.Name() << std::endl;}}// 回收所有子進程void WaitSubProcess(){for (auto &channel : _channels){channel.Wait();std::cout << "回收: " << channel.Name() << std::endl;}}~ChannelManager() {}  // 析構函數(vector自動釋放)private:std::vector<Channel> _channels;  // 存儲所有Channel對象int _next;                       // 輪詢索引
};const int gdefaultnum = 5;  // 默認子進程數量// ProcessPool類:主進程池實現
class ProcessPool
{
public:// 構造函數:初始化進程數并注冊任務ProcessPool(int num) : _process_num(num){_tm.Register(PrintLog);    // 注冊日志任務_tm.Register(Download);    // 注冊下載任務_tm.Register(Upload);      // 注冊上傳任務}//把這三個函數指針全部加入函數指針數組中// 子進程工作循環void Work(int rfd){while (true){int code = 0;ssize_t n = read(rfd, &code, sizeof(code));//從rfd中讀任務嗎,和channel的send函數相對應,正常一次讀4字節if (n > 0)  // 成功讀取{if (n != sizeof(code)) continue;  // 數據不完整則繼續讀取std::cout << "子進程[" << getpid() << "]收到任務碼: " << code << std::endl;_tm.Execute(code);  // 執行對應任務,就是三個函數之一,上傳,下載。。。。}else if (n == 0)  // 管道關閉(父進程終止){std::cout << "子進程退出" << std::endl;break;}else  // 讀取錯誤{std::cerr << "讀取錯誤" << std::endl;break;}}}// 啟動進程池bool Start(){for (int i = 0; i < _process_num; i++){// 1. 創建管道int pipefd[2] = {0};if (pipe(pipefd) < 0) return false;  // 創建失敗// 2. 創建子進程pid_t subid = fork();if (subid < 0) return false;  // fork失敗if (subid == 0)  // 子進程分支{close(pipefd[1]);  // 關閉寫端Work(pipefd[0]);    // 進入工作循環close(pipefd[0]);exit(0);            // 正常退出}else  // 父進程分支{close(pipefd[0]);            // 關閉讀端_cm.Insert(pipefd[1], subid); // 記錄通道信息}}return true;}// 調試用:打印所有通道void Debug() { _cm.PrintChannel(); }// 運行任務(主進程調用)void Run(){int taskcode = _tm.Code();          // 1. 獲取任務碼auto &c = _cm.Select();             // 2. 選擇子進程std::cout << "選擇子進程: " << c.Name() << std::endl;c.Send(taskcode);                   // 3. 發送任務std::cout << "發送任務碼: " << taskcode << std::endl;}// 停止進程池void Stop(){_cm.StopSubProcess();  // 關閉所有管道_cm.WaitSubProcess();  // 回收所有子進程}~ProcessPool() {}  // 析構函數private:ChannelManager _cm;      // 通道管理器int _process_num;        // 子進程數量TaskManager _tm;         // 任務管理器
};#endif

Task.hpp:

// 防止頭文件被重復包含的編譯器指令(現代C++替代#ifndef的方式)
#pragma once// 標準輸入輸出庫(用于cout等)
#include <iostream>
// 動態數組容器(用于存儲任務函數指針)
#include <vector>
// 時間相關函數(用于隨機數種子初始化)
#include <ctime>// 定義函數指針類型:無參數、無返回值的函數,名字是task_t!!!!!
typedef void (*task_t)(); 調試用任務函數 
// 打印日志任務函數
void PrintLog()
{std::cout << "我是一個打印日志的任務" << std::endl;
}// 下載任務函數 
void Download()
{std::cout << "我是一個下載的任務" << std::endl;
}// 上傳任務函數
void Upload()
{std::cout << "我是一個上傳的任務" << std::endl;
}
//// 任務管理類
class TaskManager
{
public:// 構造函數:初始化隨機數種子TaskManager(){srand(time(nullptr)); // 用當前時間初始化隨機數生成器}// 注冊任務函數:將函數指針存入vectorvoid Register(task_t t){_tasks.push_back(t); // 添加到任務列表末尾}// 生成隨機任務碼:返回[0, 任務數量-1]的隨機數int Code(){return rand() % _tasks.size(); // 取模保證不越界}// 執行任務:根據code調用對應的函數void Execute(int code){// 檢查code是否合法(防御性編程)if(code >= 0 && code < _tasks.size()){_tasks[code](); // 通過函數指針調用任務,就是上面三個打印,上傳,下載函數}// 注意:未處理非法code的情況(可添加錯誤處理)}// 析構函數(當前為空實現)~TaskManager(){}private:std::vector<task_t> _tasks; // 存儲所有注冊的任務函數指針
};

makefile:

process_pool:Main.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -f process_pool

main.cc:

#include "ProcessPool.hpp"int main()
{// 創建進程池對象ProcessPool pp(gdefaultnum);// 啟動進程池pp.Start();//剛開始就是建立5個子進程和通道,但通道內沒有內容即任務碼,所以子進程的work會被卡住。// 自動派發任務int cnt = 10;while(cnt--){pp.Run();//往子進程里去發放任務碼,子進程開始work,也就是開始調用manager的Execute函數,就是在三個上傳,下載函數中隨機選一個來執行sleep(1);}// 回收,結束進程池pp.Stop();// 關閉所有管道-即回收父進程的wfd---使用close函數關掉所有channel中的wfd//回收所有子進程----調用waitpid函數return 0;
}

小問題:
在這里插入圖片描述

如果我每關一個wfd,回收一個子進程會怎樣? 會在第一個子進程回收時阻塞!!!-------------------------------------- read()沒有返回0

  • 第一次產生子進程,父進程文件描述符表分配3,4(三是讀,四是寫),子進程先是拷貝父類的內容,所以子進程也是(三是讀,四是寫),然后關閉不需要的fd,父進程關閉3,子進程關閉4
  • 第二次產生子進程,父進程分配文件描述符3,5(因為4已經在上次的過程中被占用三是讀,五是寫),同理,子進程也是(三是讀,五是寫),然后再次關閉不需要的fd,父進程關閉3,子進程關閉5要注意的是,第二次產生的子進程會繼承父進程的4,即二號子進程的4是指向第一個子進程管道的寫端的!!!!!!
  • 所以第一次,父進程的wfd被關閉后,寫端并沒有完全關閉(因為剩余的四個子進程都繼承了4號寫端計數器還有4,read函數就不會返回0,自然就沒辦法結束第一個子進程,自然就沒辦法使用wait函數回收,導致阻塞

在這里插入圖片描述

在這里插入圖片描述
在這里插入圖片描述

  • 解決辦法-----倒著關,因為只有最后一個子進程的寫端是由父進程一人持有的,父進程關了那就是真的關了,可以讓read直接返回0,完成回收----以此類推
  • 也可以在子進程創立時,遍歷channel數組,把里面的wfd都關了,說白了就是把繼承下來的寫端全關了,這樣所有的寫端都只由父進程持有

4.命名管道

在這里插入圖片描述
在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/913085.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/913085.shtml
英文地址,請注明出處:http://en.pswp.cn/news/913085.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

深入理解Vapnik-Chervonenkis(VC)維度:機器學習泛化能力的理論基礎

引言 通過本篇閱讀,從理論上去理解為什么: 要選擇復雜度低的模型 過擬合的時候,增加樣本量有用 以及如何根據樣本量選擇特征個數 PAC機器學習框架, VC 維是機器學習最重要的基礎理論之一 在機器學習領域&#xff0c;模型泛化能力是衡量算法性能的核心指標…

redis持久化-純緩存模式

redis持久化-純緩存模式 文檔 redis單機安裝redis常用的五種數據類型redis數據類型-位圖bitmapredis數據類型-基數統計HyperLogLogredis數據類型-地理空間GEOredis數據類型-流Streamredis數據類型-位域bitfieldredis持久化-RDBredis持久化-AOFredis持久化-RDBAOF混合模式 官…

HTML DOM 訪問

HTML DOM 訪問 引言 HTML DOM&#xff08;文檔對象模型&#xff09;是現代Web開發中不可或缺的一部分。它允許開發者通過JavaScript操作HTML文檔中的元素&#xff0c;從而實現豐富的交互效果。本文將詳細介紹HTML DOM的訪問方法&#xff0c;包括如何獲取元素、如何修改元素屬…

雙系統如何做接口認證-V1

現有A系統&#xff0c;B系統&#xff0c;A系統啟動的時候調用B系統的注冊接口API1&#xff08;把A系統配置信息注冊到B系統&#xff09;&#xff0c;A系統定時向B系統接口AP2發送心跳信息&#xff0c;B系統根據業務情況&#xff0c;調用A系統的業務接口AP3&#xff0c;請設計兩…

Wireshark TS | 詭異的光貓網絡問題

前言 來自于朋友分享的一個案例&#xff0c;最后定位的原因是光貓問題&#xff0c;而類似這類的設備所產生的網絡問題&#xff0c;也曾碰到過兩三次&#xff0c;但這一次的數據包現象挺特別&#xff0c;分析思路和過程也有所不同&#xff0c;故記錄分享一下。 問題背景 用戶所反…

mac mini m4安裝node.js@16以下版本方法

設備&#xff1a;mac mini m4 目的&#xff1a;使用nvm 安裝 node.js14.x 版本 結果&#xff1a;安裝不上 原因&#xff1a;Node.js 14 發布時&#xff0c;Apple Silicon&#xff08;M1/M2&#xff09;尚未普及&#xff0c;因此 沒有官方預編譯的 macOS ARM64 版本 處理方案&am…

系統安全設計方案,軟件系統安全設計方案

1.1 總體設計 1.1.1 設計原則 1.2 物理層安全 1.2.1 機房建設安全 1.2.2 電氣安全特性 1.2.3 設備安全 1.2.4 介質安全措施 1.3 網絡層安全 1.3.1 網絡結構安全 1.3.2 劃分子網絡 1.3.3 異常流量管理 1.3.4 網絡安全審計 1.3.5 網絡訪問控制 1.3.6 完整性檢查 1.…

Python入門Day3

Python的基礎數據類型 1.Python中提供了六種內置的數據類型&#xff0c;一般用于存儲數據&#xff1a; –數值Number –字符串String –列表List –元組Tuple –字典Dictionary –集合Set 2.Python中的數據類型可以做以下幾個分類&#xff1a; –有序&#xff1a;可以使用下標…

前端富文本添加錄音功能方案

為富文本編輯器添加錄音功能可以增強內容創作的多樣性。以下是幾種實現方案&#xff1a; 方案一&#xff1a;基于Web Audio API原生實現 實現步驟獲取用戶麥克風權限 navigator.mediaDevices.getUserMedia({ audio: true }).then(stream > { /* 處理音頻流 */ }).catch(err …

解鎖阿里云Hologres:開啟實時數據分析新時代

引言在當今這個數字化浪潮洶涌澎湃的大數據時代&#xff0c;數據就如同企業和組織的 “數字石油”&#xff0c;成為了最具價值的資產之一。隨著信息技術的飛速發展&#xff0c;各行業所產生和收集的數據量正以指數級的速度增長&#xff0c;從社交媒體上的用戶互動信息&#xff…

python學習打卡day59

DAY 59 經典時序預測模型3 知識點回顧&#xff1a; SARIMA模型的參數和用法&#xff1a;SARIMA(p, d, q)(P, D, Q)m模型結果的檢驗可視化&#xff08;昨天說的是摘要表怎么看&#xff0c;今天是對這個內容可視化&#xff09;多變量數據的理解&#xff1a;內生變量和外部變量多變…

java中agent的作用

一 java中agent1.1 agent-javaagent 是 Java 虛擬機 (JVM) 提供的一個啟動參數&#xff0c;用于在 Java 程序 main 方法執行之前&#xff0c;加載一個特殊的 Java 代理程序&#xff08;Java Agent&#xff09;。它的核心作用是對運行中的 Java 程序進行字節碼層面的動態修改、監…

[C/C++內存安全]_[中級]_[如何避免數組訪問越界]

場景 C/C的標準在C26以前還沒支持內存安全的訪問連續內存的類或特性。在開發分析內存數據或文件數據的程序時&#xff0c;經常需要把一段內存數據復制到另一個堆空間里。 這時目標內存空間由于起始地址的移動&#xff0c;剩余大小的計算錯誤&#xff0c;經常會導致訪問越界錯誤…

rabbitmq 與 Erlang 的版本對照表 win10 安裝方法

win10 64位系統 安裝的版本 otp_win64_27.3.3.exe rabbitmq-server-4.1.1.exe rabbitmq 與 Erlang 的版本對照表 Erlang Version Requirements This guide covers Erlang/OTP version requirements https://www.rabbitmq.com/docs/which-erlang Erlang 28 is not currently…

kali安裝教程

kali教程 我下載的是kali的集成環境&#xff0c;可以直接進行打開&#xff0c;無需進行安裝。 Get Kali | Kali Linux&#xff0c; 官網下載路徑 直接按enter鍵 安裝完成 生成一個小皮安裝鏈接 會給你生成一個外網和內網地址&#xff0c; 可以進行瀏覽 點擊我同意這個協議…

微信小程序入門實例_____快速搭建一個快遞查詢小程序?

&#x1f337;&#x1f337;之前幾篇博文我們一起開發了天氣查詢、單詞速記和待辦事項小程序&#xff0c;這次我們來對生活中常用的功能 —— 快遞查詢來探索相關的小程序。網購已經成為大家生活的一部分&#xff0c;有了自己的快遞查詢小程序&#xff0c;不用切換多個應用&…

【防火墻基礎之傳統墻到 UTM 到 NGFW 再到 AI 的變化】

防火墻技術演進與未來趨勢&#xff1a;從傳統防御到AI驅動的智能安全 防火墻技術歷經數十年發展&#xff0c;已從早期的簡單包過濾演進為融合AI的智能安全平臺。當前&#xff0c;傳統爬蟲防護技術如頻率限制和人機校驗已無法應對現代攻擊&#xff0c;而全面風控體系通過多維協同…

【仿muduo庫實現并發服務器】Poller模塊

仿muduo庫實現并發服務器 1.Poller模塊成員變量創建epoll模型對于一個描述符添加或修改事件監控對于一個描述符移除事件監控啟動epoll事件監控&#xff0c;獲取所有活躍連接 1.Poller模塊 Poller模塊主要是對任意的描述符進行IO事件監控。 它是對epoll的封裝&#xff0c;可以讓…

小程序學習筆記:使用 MobX 實現全局數據共享,實例創建、計算屬性與 Actions 方法

在小程序開發過程中&#xff0c;組件間的數據共享是一個常見且關鍵的問題。今天&#xff0c;我們就來深入探討一下如何在小程序中實現全局數據共享&#xff0c;借助 MobX 相關的包&#xff0c;讓數據管理變得更加高效便捷。 什么是全局數據共享 全局數據共享&#xff0c;也被…

觀測云 × AWS SSO:權限治理可觀測實踐

AWS IAM Identity Center 介紹 AWS IAM Identity Center&#xff08;原 AWS Single Sign-On&#xff09;是 AWS 提供的一項云原生身份與訪問管理&#xff08;IAM&#xff09;服務&#xff0c;旨在集中簡化多 AWS 賬戶、多業務應用的安全訪問控制。 觀測云 觀測云是一款專為 …