柚子快報邀請碼778899分享:算法 匿名管道 Linux
柚子快報邀請碼778899分享:算法 匿名管道 Linux
管道
首先自己要用用戶層緩沖區(qū),還得把用戶層緩沖區(qū)拷貝到管道里,(從鍵盤里輸入數(shù)據(jù)到用戶層緩沖區(qū)里面),然后用戶層緩沖區(qū)通過系統(tǒng)調(diào)用(write)寫到管道里,然后再通過read系統(tǒng)調(diào)用,被對方(讀端)讀取,就要從管道拷貝到讀端,然后再顯示到顯示器上。
pipe創(chuàng)建一個管道
pipe的介紹
1完成這件事:
看圖分析
運行結(jié)果
#include
#include
using namespace std;
int main()
{
//創(chuàng)建管道
//先創(chuàng)建一個pipefd數(shù)組
int pipefd[2];
//用n接受一下,判斷是否成功
int n = pipe(pipefd);
if(n<0) return 1;//創(chuàng)建失敗了
//創(chuàng)建成功
//測試一下文件描述符是3和4
cout<<"pipefd[0]:"< return 0; } 2完成這件事: 創(chuàng)建一個子進程 pid_t id = fork(); if(id < 0)return 2;//創(chuàng)建失敗 if(id == 0)//創(chuàng)建成功 { //子進程 } //父進程 讓子進程寫入,父進程讀取 要想讓子進程進程寫,就需要在進程中關(guān)閉讀端 if(id == 0)//創(chuàng)建成功 { //子進程 close(pipefd[0]); } 同理 //父進程 close(pipefd[1]); 都用完結(jié)束后,可以都關(guān)掉 if(id == 0)//創(chuàng)建成功 { //子進程 close(pipefd[0]); //..... close(pipefd[1]); } //父進程 close(pipefd[1]); //..... close(pipefd[0]); IPC code,寫通信代碼 3這件事也完成了: 結(jié)構(gòu)就有了 然后在pipefd[1]這個管道里寫,定義一個Writer函數(shù) if(id == 0)//創(chuàng)建成功 { //子進程 close(pipefd[0]); //.....IPC code,寫通信代碼 //在pipefd[1]這個管道里寫 Writer(pipefd[1]); close(pipefd[1]); exit(0);//正常退出 } 同理父進程的???????? //父進程 close(pipefd[1]); //.....IPC code,寫通信代碼 //在pipefd[0]這個管道里寫 Reader(pipefd[0]); close(pipefd[0]); //子進程 void Writer(int wfd) { } //父進程 void Reader(int rfd) { } Writer //子進程 void Writer(int wfd) { string s = "hello,I am child"; pid_t self = getpid(); int number = 0; char buffer[10]; while(true) { buffer[0] = 0;//字符串清空,只是為了提醒閱讀代碼的人,我把這個數(shù)組當字符串了 } } 用到snprintf 介紹 將s和self和number放進buffer char buffer[100]; while(true) { buffer[0] = 0;//字符串清空,只是為了提醒閱讀代碼的人,我把這個數(shù)組當字符串了 snprintf(buffer,sizeof(buffer),"%s pid:%d\n",s.c_str(),self); cout<< buffer < sleep(1); }; 用cout打印測試一下,打印成功說明寫入buffer成功了 等待進程少不了,子進程exit后需要回收 //父進程 close(pipefd[1]); //.....IPC code,寫通信代碼 //在pipefd[0]這個管道里寫 Reader(pipefd[0]); //等待進程缺少不了 pid_t rid = waitpid(id,nullptr,0); if(rid < 0) return 3;//等待失敗了 close(pipefd[0]); 如何把消息發(fā)送/寫入給父進程 用到了write 用write寫入管道(管道也是文件),用strlen,不用+1,不用管\0,因為C語言規(guī)定\0結(jié)尾,和文件沒有關(guān)系,wfd寫入管道 //子進程 void Writer(int wfd) { string s = "hello,I am child"; pid_t self = getpid(); int number = 0; char buffer[100]; while(true) { buffer[0] = 0;//字符串清空,只是為了提醒閱讀代碼的人,我把這個數(shù)組當字符串了 snprintf(buffer,sizeof(buffer),"%s pid:%d %d\n",s.c_str(),self,number++); //用write寫入管道(管道也是文件),用strlen,不用+1,不用管\0,因為C語言規(guī)定\0結(jié)尾,和文件沒有關(guān)系,wfd寫入管道 write(wfd,buffer,strlen(buffer)); //cout<< buffer < sleep(1); }; } 父進程該怎么讀取呢 用到了read,fd是文件描述符,從特定的文件描述符里讀取,放在這個buf里,buf的長度是count 這里就需要考慮到\0,因為buffer中需要\0 //父進程 void Reader(int rfd) { char buffer[100]; while(true) { buffer[0] = 0; //用sizeof是為了留個空間放\0 ssize_t n = read(rfd, buffer, sizeof(buffer));//sizeof!=strlen if(n > 0) { //添加\0,因為要放在buffer數(shù)組中讀取 buffer[n]=0; cout << "father get a message[" << getpid() <<"]"<< buffer < } } } 運行結(jié)果 也會發(fā)現(xiàn):為什么子進程sleep,父進程不sleep,父進程還是會跟著子進程sleep,因為父子進程是要協(xié)同的 管道本質(zhì) 通信是為了更好的發(fā)送變化的數(shù)據(jù),管道本質(zhì)上是文件 所以必須要用到系統(tǒng)調(diào)用接口來訪問管道,其是由系統(tǒng)管理,read和write ,操作系統(tǒng)相當于中介? 結(jié)論:管道的特征: 1:具有血緣關(guān)系的進程進行進程間通信 2:管道只能單向通信 3:父子進程是會進程協(xié)同的,同步與互斥的--保護管道文件的數(shù)據(jù)安全 4:管道是面向字節(jié)流的 5:管道是基于文件的,而文件的生命周期是隨進程的 再測試,把子進程sleep去掉,就是讓子進程寫快一點,父進程sleep幾秒,就是讓父進程讀慢一點,看有什么現(xiàn)象 ?管道的四種情況 測試管道大小 把c一直往管道里寫,把父進程中休眠50秒 結(jié)果差不多64kb 寫端退了,測試結(jié)果 結(jié)果是: 讀端正常讀,寫端關(guān)閉,讀端就會讀到0,表明讀到了文件(pipe)結(jié)尾,不會被阻塞 read讀取成功會返回讀到的字符個數(shù),讀到結(jié)尾返回0 讀到結(jié)尾父進程也就可以停止讀取了,break后去把僵尸的子進程回收 break到這里 最后子進程會被waitpid回收 測試子進程一直寫,父進程讀一會就退出 定義一個cnt控制退出的時間 這里也要修改一下,加個sleep(5),觀察,close提前關(guān)閉 結(jié)果:通過13號信號殺死? 管道到的應(yīng)用場景 都會變成一個進程 寫一個進程池(pipe_use) 首先創(chuàng)建好文件 創(chuàng)建5個進程 channel通道的意思 cmdfd文件描述符 slaverid代表哪個子進程 把它放進vector容器里 思路步驟 管道創(chuàng)建 void(n),假裝使用一下,要不然編譯不過 創(chuàng)建父子進程 父進程寫,子進程讀 子進程要讀取,就要關(guān)閉自己的寫端,父進程同理 子進程中的任務(wù) 子進程pid有了管道也有了,就差在父進程添加字段了 先更改一下,在class里構(gòu)造一下 添加字段 測試一下:結(jié)果:文件描述符0,1,2是默認打開,3是從管道里讀,4是寫入管道 把初始化改造成函數(shù) debug測試函數(shù),純輸入函數(shù) 第二步開始控制進程了(想讓子進程做什么) 這里打印的rfd都是3,正常嗎,文件描述符是可以被子進程繼承的 父進程對應(yīng)的寫端拿到的是4-8,子進程拿到的讀端fd是3 改變一下,直接從鍵盤(0號描述符)里讀,不從管道(3)里讀了,就沒有管道的概念了,slaver就不用傳參了,父進程通過管道寫,子進程通過標準輸入讀 用到了dup2,將從pipefd[0]中讀變成從0開始讀 想讓父進程固定的向管道里寫入指定大小字節(jié)的內(nèi)容,必須讀取四個字節(jié),四個字節(jié)四個字節(jié)的寫和讀,這里的管道64kb 必須讀取四個字節(jié) 如果父進程不給子進程發(fā)送數(shù)據(jù)呢?阻塞等待! 開始控制子進程 生成一個隨機數(shù)種子 可以隨機選擇任務(wù)和選擇進程 cmd是任務(wù)碼,測試一下,父進程控制子進程,父進程發(fā)送給子進程(通過cmdcode連續(xù)) 在Task.hpp里 要用到函數(shù)指針 main中的任務(wù)了就屬于 再把任務(wù)裝載進來 輸出型參數(shù)用* 現(xiàn)在開始選擇任務(wù)和進程 再把main中的任務(wù)弄成全局的 進行判斷一下 測試 ,comcode和任創(chuàng)建的任務(wù)一致 這里的write是父進程進行寫入,向子進程發(fā)送,子進程不得閑,先寫到管道里,等得閑了再讀 也可以輪詢選擇,定義一個計數(shù)器,++弄,再%等 整理一下控制代碼,這里是輸入型參數(shù),只需要讀 這樣就可以輪詢方式選擇進程了,不用隨機了 結(jié)果 清理收尾 思路:把所有文件的描述符都關(guān)掉 等待方式設(shè)置為0? read返回0,就是失敗了,然后slaver就會調(diào)完 結(jié)束完就會exit直接退出 打印下更好顯示 關(guān)閉文件描述符后sleep(10)秒, 然后這10個子進程一瞬間都應(yīng)該break,然后最后到exit直接就退了,10秒結(jié)束后,父進程再回收他??????? 測試時不弄死循環(huán),用cnt,5秒后自動結(jié)束控制,正常退出流程 測試結(jié)果 手動控制一下 定義一個select,輸入0就是退出了,判斷完后,就走到了選擇任務(wù) 然后直接把cmdcode改為選擇的select,-1是因為是從下標0開始的,輸入1就是0下標的 測試 bug的地方: 這樣會有一些bug(一個子進程不是只有一個寫端(每一次子進程的創(chuàng)建都是有繼承)) ?這樣會有一些bug(一個子進程不是只有一個寫端(每一次子進程的創(chuàng)建都是有繼承)) 按理說這樣是對的,可是這樣就錯了 因為下面兩個紅線還沒有關(guān)掉,它們進程了最開始的w 這樣倒著回收是可以的 正確改法 修改一下 最后一個push_back的就都是父進程的寫入fd, 然后加一句這個紅線的,每創(chuàng)建子進程后都先把上一次父進程的讀端fd關(guān)掉就可以了,這里很妙,因為vector一開始是空的 方便看 這里這樣就可以了???????? 管道已經(jīng)完成 以上是匿名管道? 總文件總代碼 makefile中代碼 ProcessPool:ProcessPool.cc g++ -o $@ $^ -std=c++11 .PHNOY:clean clean: rm -f ProcessPool Task.hpp中代碼 #pragma once #include #include using namespace std; typedef void (*task_t)(); void task1() { cout<< "lol 刷新日志" < } void task2() { cout<< "lol 更新野區(qū)" < } void task3() { cout<< "lol 檢測軟件更新" < } void task4() { cout<< "lol 釋放技能" < } ProcessPool.cc中代碼 注意: 這里為啥是取地址一個comcode,不是一個0嗎,要發(fā)送的數(shù)據(jù)就是這個,所以要把地址傳給函數(shù),可以是個數(shù)組 換成數(shù)組的話,read這也接收數(shù)據(jù)的時候,就得用數(shù)組去接受,要是寫入超過int大小的話,就可能會出錯,這個就是通信的雙方要遵守的約定,這個判斷一下,就是派發(fā)的這個任務(wù)是不是合法的,假設(shè)你的tasks任務(wù)中,只有4個任務(wù),所以任務(wù)編號就是0 ~ 3,如果你接受到的任務(wù)編號是10或者-20,那么這些就是非法的,你執(zhí)行的話,程序就會崩潰,所以要做一個簡單的判斷。 write以后,cmdcode的值也會跟著傳到read對吧,write就是為了把cmdcode的值傳遞給給另外一個進程,以前見到的都是用的char buffer[];,這樣&cmdcode能更方便的傳值過去是不,看要傳的是什么數(shù)據(jù),只是傳遞一個int數(shù)據(jù)的話,就這樣傳遞,如果是文本數(shù)據(jù),或者是其他的話,可能就需要數(shù)組了,具體問題,具體討論 #include "Task.hpp" #include #include #include #include #include #include #include using namespace std; //打算創(chuàng)建5個進程 const int processnum = 5; //全局任務(wù) vector //先描述 class channel//管道 { public: channel(int cmdfd,pid_t slaverid,string& processname) :_cmdfd(cmdfd) ,_slaverid(slaverid) ,_processname(processname) {} public: int _cmdfd;//文件描述符 pid_t _slaverid;//代表哪個子進程 string _processname;//子進程的名字,方便打印日志 }; //子進程中讀的任務(wù) // void slaver(int rfd) // { // while(true) // { // cout<< getpid() <<" - "<< "read fd is->"< // sleep(1000); // } // } //改變一下從fd為0的地方開始讀 void slaver() { //read(0); while(true) { int cmdcode = 0; int n = read(0, &cmdcode, sizeof(int)); if(n == sizeof(int)) { //執(zhí)行cmdcode對應(yīng)的任務(wù)列表 cout<< "slaver say@ get a command:" << getpid() << ":cmdcode:" << cmdcode < //判斷一下并執(zhí)行 if(cmdcode >= 0 && cmdcode < tasks.size()) tasks[cmdcode](); } if(n == 0) break; } } //初始化 void Init(vector { for(int i =0;i < processnum;i++) { int pipefd[2]; int n = pipe(pipefd);//創(chuàng)建管道 //返回值小于0就創(chuàng)建失敗了 assert(!n); (void)n; pid_t id = fork(); if(id == 0) { //子進程:讀 close(pipefd[1]); //改變一下從fd為0的地方讀 dup2(pipefd[0],0); close(pipefd[0]); //任務(wù) slaver(); cout<< "process: " << getpid() << "quit" < //slaver(pipefd[0]); exit(0); } //父進程:寫 close(pipefd[0]); //channel添加字段 string name = "processs-" + to_string(i); //插入的是自定義類型,要構(gòu)造一下,第一個傳的是文件描述符,要寫入的fd channels.push_back(channel(pipefd[1], id, name)); } } //測試函數(shù),純輸入函數(shù) //輸入:const & //輸出:* //輸入輸出:& void debug(const vector { for(auto&e : channels) { cout<< e._cmdfd <<" "< } } void Loadtask(vector { tasks->push_back(task1); tasks->push_back(task2); tasks->push_back(task3); tasks->push_back(task4); } void memu() { cout<< "########################" < cout<< "1:lol 刷新日志 2:lol 更新野區(qū)" < cout<< "1:lol 檢測軟件更新 4:lol 釋放技能" < cout<< " 0:退出 " < cout<< "########################" < } //2:開始控制子進程 void ctrlSlaver(vector { int which = 0; int cnt = 5; while(true) { int select = 0; memu(); cout<< "Please Enter@:"; cin>> select; if(select == 0) break; //1:選擇任務(wù) //int cmdcode = rand()%tasks.size(); int cmdcode = select - 1; //2:隨機選擇進程 //int processpos = rand()%channels.size(); //2:輪詢選擇進程 cout<< "father say:"<< "cmdcode:" << cmdcode << " already sendto " < < //3:發(fā)送任務(wù) write(channels[which]._cmdfd, &cmdcode, sizeof(cmdcode)); which++; which%=channels.size();//保證不大于其長度 cnt--; if(cnt == 0) break; sleep(1); } } void QuitProcess(const vector { for(const auto& e : channels) close(e._cmdfd); sleep(10); for(const auto& e : channels) waitpid(e._slaverid, nullptr, 0);//進程的pid=_slaverid,關(guān)上了以后記得回收 } int main() { Loadtask(&tasks); //srand(time(nullptr)^getpid()^1023);//種一個隨機數(shù)種子 //在組織 vector //1:初始化 Init(channels); debug(channels); //2:開始控制子進程 ctrlSlaver(channels); //3:清理收尾 QuitProcess(channels); return 0; } 柚子快報邀請碼778899分享:算法 匿名管道 Linux 文章來源
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。