[C++] 匿名管道的理解与实现
什么是匿名管道?
匿名管道用于進程之間通信,且僅限于本地父子進程之間通信,結構簡單,類似于一根水管,一端進水另一端出水(單工)。相對于命名管道,其占用小實現(xiàn)簡單,在特定情況下,比如實現(xiàn)兩圍棋引擎本地對戰(zhàn)可以使用匿名管道。
怎樣實現(xiàn)匿名管道雙向通信?
由于匿名管道是單工的,所以為實現(xiàn)父子進程雙向通信需要創(chuàng)建兩根管道,并由子進程繼承一根管道的讀句柄和另一根管道的寫句柄。
如何理解匿名管道的雙向通信?
管道相當于一段內(nèi)存,一個進程輸入,一個進程讀出。
在進程通信時一般會產(chǎn)生進程同步問題(進程同步講解請見操作系統(tǒng)類書籍):父子進程各自均具有讀寫功能,在管道為空時,相應讀進程應該被阻塞起來,直到管道被寫入為止才被喚醒。
這種空管道不允許讀的特性應當加一個鎖,但匿名管道自帶了這種功能,所以不需要對讀寫進行限制,其能自動阻塞。
在VS2017下實現(xiàn)匿名管道
對幾個基本點進行介紹
#include <windows.h>
匿名管道需要包含此頭文件
首先我們需要了解一下最后程序?qū)崿F(xiàn)中我想要的效果:父進程輸入任意長數(shù)字(當然局限于匿名管道的最大大小4MB)通過匿名管道傳給子進程,由子進程對該字符串(由于在管道中以字符流形式存在)的各位數(shù)進行加和,把這個加和的結果返回父進程。
在實際制作時,我將子進程這個計算函數(shù)做成動態(tài)鏈接庫的形式進行鏈入。所以在實際代碼中將以一行代碼的形式呈現(xiàn):
int Bitadd(char *ary1, char *ary2, unsigned long len, int Lcount);
其中ary1為子進程接收到的字符串、ary2為計算結果、len是接收到的字符串長度、Lcount為計算結果長度。
創(chuàng)建管道
函數(shù)原型:
BOOL WINAPI CreatePipe(?
_Out_PHANDLE hReadPipe,?
_Out_PHANDLE hWritePipe,?
_In_opt_LPSECURITY_ATTRIBUTES lpPipeAttributes,
_In_DWORD nSize);
實際調(diào)用形式:
CreatePipe(&read, &write, &sa, 0);
其中read是讀句柄,write是寫句柄,sa是管道安全屬性,0代表管道緩沖設置為系統(tǒng)默認值。
由上函數(shù)可知在創(chuàng)建管道之前,需要先設置管道安全屬性。
設置管道安全屬性
對象原型:
typedef struct _SECURITY_ATTRIBUTES {
?
DWORD nLength; //結構體的大小,可用SIZEOF取得
?
LPVOID lpSecurityDescriptor; //安全描述符
?
BOOL bInheritHandle ;//安全描述的對象能否被新創(chuàng)建的進程繼承
?
} SECURITY_ATTRIBUTES,* PSECURITY_ATTRIBUTES;
?
在程序中僅需如下設置即可:(ParentView為我創(chuàng)建的父進程管道類)
void ParentView::CreateATTRIBUTES() ?// 設置管道安全屬性
{
sa.bInheritHandle = TRUE; // TRUE為管道可以被子進程所繼承 ?
sa.lpSecurityDescriptor = NULL; // 默認為NULL
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
}
各參數(shù)在原型中已有很好的注釋。
創(chuàng)建好管道后,可以考慮創(chuàng)建子進程,使其繼承父進程的管道句柄。
創(chuàng)建子進程
先貼代碼:
? ??
TCHAR szCmdline[] = TEXT("../../child/Debug/child.exe"); // 設置子進程路徑
PROCESS_INFORMATION pi; ?// 用來接收新進程的識別信息
STARTUPINFO si; ?// 用于決定新進程的主窗體如何顯示
BOOL bSuccess = FALSE;
?
? ? // 設置PROCESS_INFORMATION
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); ?// 用0填充內(nèi)存區(qū)域
? ? // 設置STARTUPINFO
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO); ?// 結構大小
? ? ? ? ? ? ? ? ? ? ? ? ??//*************** 句柄繼承設置******************
? ? ? ? ? ? ? ? ? ? ? ? ??// 創(chuàng)建了兩個管道
? ? ? ? ? ? ? ? ? ? ? ? ??// 管道1由父進程讀,子進程寫
? ? ? ? ? ? ? ? ? ? ? ? ??// 管道2由父進程寫,子進程讀
si.hStdError = write1; ?????// 錯誤輸出句柄(在寫句柄中寫回父進程)
si.hStdOutput = write1; ????// 子進程繼承管道1寫句柄
si.hStdInput = read2; ? ????// 子進程繼承管道2讀句柄
? ? ? ? ? ? ? ? ? ? ? ? ??//*************** 句柄繼承設置******************
si.dwFlags |= STARTF_USESTDHANDLES; ?// 使用hStdInput 、hStdOutput 和hStdError 成員 ?
??????????// 創(chuàng)建子進程
? ? ? ? ?// 摘自msdn:
?// If lpApplicationName is NULL,?
?// the first white space–delimited token of the command line specifies the module name.?
bSuccess = CreateProcess(
? ? ? ? NULL, ?????????// lpApplicationName
? ? ? ? szCmdline, ????// command line?
? ? ? ? ? ? ? ? ? ? ???// 以上兩個字段都可以創(chuàng)建目標子進程
? ? ? ? NULL, ?????????// process security attributes?
? ? ? ? NULL, ?????????// primary thread security attributes?
? ? ? ? TRUE, ?????????// bInheritHandles:指示新進程是否從調(diào)用進程處繼承了句柄
? ? ? ? 0, ?? ? ? ? ?// creation flags:指定附加的、用來控制優(yōu)先類和進程的創(chuàng)建的標志。
? ? ? ? ? ? ? ? ? ? ???// 設置為 CREATE_NEW_CONSOLE 可顯示子窗口
? ? ? ? NULL, ?????????// use parent's environment?
? ? ? ? NULL, ?????????// use parent's current directory?
? ? ? ? &si, ? ? ? ? ??// STARTUPINFO :指向一個用于決定新進程的主窗體如何顯示的STARTUPINFO結構體
? ? ? ? &pi ? ? ? ? ???// PROCESS_INFORMATION :指向一個用來接收新進程的識別信息的PROCESS_INFORMATION結構體
);
?
// If an error occurs, exit the application.?
if (!bSuccess)
? ? cout << "創(chuàng)建子程序失敗" << endl;
else
{
? ? // 關閉一些子進程用的句柄
? ? CloseHandle(pi.hProcess);
? ? CloseHandle(pi.hThread);
? ? CloseHandle(write1);
? ? CloseHandle(read2);
}
首先設置子進程所在路徑,子進程為一個exe可執(zhí)行程序。然后會用到兩個類型STARTUPINFO和PROCESS_INFORMATION,有興趣的朋友可自行百度,查看兩種類中的參數(shù)。
這里也不貼CreateProcess的函數(shù)原型了,代碼塊中有較好的注釋。
其實對于管道創(chuàng)建和子進程創(chuàng)建都是一個模版框架。
讀寫函數(shù)請見github源代碼
實現(xiàn)雙向通信
在父進程中創(chuàng)建兩個匿名管道。此時父進程共有六個句柄Read1,Write1,Read2,Write2,標準輸入輸出句柄。
??????????????????????????????????
由圖所示,標準輸入輸出句柄用于在Dos窗口的輸入和輸出。
然后我們需要讓創(chuàng)建的子進程繼承Write1句柄和Read2句柄。
????????????????????????????????????????
子進程初始化句柄代碼
read = GetStdHandle(STD_INPUT_HANDLE); // 繼承句柄
write = GetStdHandle(STD_OUTPUT_HANDLE);
if ((read == INVALID_HANDLE_VALUE) || (write == INVALID_HANDLE_VALUE))
? ? cout << "繼承句柄無效" << endl;
可以看到,子進程的標準輸入輸出句柄已經(jīng)被繼承的Write1句柄和Read2句柄所覆蓋。
因此無法實現(xiàn)在子進程的Dos窗口進行顯示,子進程窗口將是永遠黑窗,可以在父程序中注釋掉子進程所繼承的寫句柄進行對比,并將CreateProcess函數(shù)中的一個參數(shù)設置為顯示子進程窗口(注釋中有)。
需要注意的坑點:
si.dwFlags |= STARTF_USESTDHANDLES;
若要實現(xiàn)雙向通信,子進程Dos是黑窗。但是可以將子進程收到的結果寫到文件。
源代碼地址:https://github.com/YuxiangTang/AnonymousPipe
原文:https://blog.csdn.net/it2153534/article/details/79064643?
?
總結
以上是生活随笔為你收集整理的[C++] 匿名管道的理解与实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MFC匿名管道原理详解、函数总结、调用实
- 下一篇: 中国智慧VS西方智慧-看中国IT风云与I