1. 程式人生 > >[C++] 匿名管道的理解與實現

[C++] 匿名管道的理解與實現

什麼是匿名管道?

匿名管道用於程序之間通訊,且僅限於本地父子程序之間通訊,結構簡單,類似於一根水管,一端進水另一端出水(單工)。相對於命名管道,其佔用小實現簡單,在特定情況下,比如實現兩圍棋引擎本地對戰可以使用匿名管道。

怎樣實現匿名管道雙向通訊?

由於匿名管道是單工的,所以為實現父子程序雙向通訊需要建立兩根管道,並由子程序繼承一根管道的讀控制代碼和另一根管道的寫控制代碼。

如何理解匿名管道的雙向通訊?

管道相當於一段記憶體,一個程序輸入,一個程序讀出。

在程序通訊時一般會產生程序同步問題(程序同步講解請見作業系統類書籍):父子程序各自均具有讀寫功能,在管道為空時,相應讀程序應該被阻塞起來,直到管道被寫入為止才被喚醒。

這種空管道不允許讀的特性應當加一個鎖,但匿名管道自帶了這種功能,所以不需要對讀寫進行限制,其能自動阻塞。

在VS2017下實現匿名管道

對幾個基本點進行介紹

#include <windows.h>

匿名管道需要包含此標頭檔案

首先我們需要了解一下最後程式實現中我想要的效果:父程序輸入任意長數字(當然侷限於匿名管道的最大大小4MB)通過匿名管道傳給子程序,由子程序對該字串(由於在管道中以字元流形式存在)的各位數進行加和,把這個加和的結果返回父程序。

在實際製作時,我將子程序這個計算函式做成動態連結庫的形式進行鏈入。所以在實際程式碼中將以一行程式碼的形式呈現:

int Bitadd(char *ary1, char *ary2, unsigned long len, int Lcount);

其中ary1為子程序接收到的字串、ary2為計算結果、len是接收到的字串長度、Lcount為計算結果長度。

建立管道

函式原型:

BOOL WINAPI CreatePipe( _Out_PHANDLE hReadPipe, _Out_PHANDLE hWritePipe, _In_opt_LPSECURITY_ATTRIBUTES lpPipeAttributes,_In_DWORD nSize);

實際呼叫形式:

CreatePipe(&read, &write, &sa, 0);

其中read是讀控制代碼,write是寫控制代碼,sa是管道安全屬性,0代表管道緩衝設定為系統預設值。

由上函式可知在建立管道之前,需要先設定管道安全屬性。

設定管道安全屬性

物件原型:

typedef struct _SECURITY_ATTRIBUTES {

DWORD nLength; //結構體的大小,可用SIZEOF取得

LPVOID lpSecurityDescriptor; //安全描述符

BOOL bInheritHandle ;//安全描述的物件能否被新建立的程序繼承

} SECURITY_ATTRIBUTES,* PSECURITY_ATTRIBUTES;

在程式中僅需如下設定即可:(ParentView為我建立的父程序管道類)

void ParentView::CreateATTRIBUTES()  // 設定管道安全屬性
{
sa.bInheritHandle = TRUE; // TRUE為管道可以被子程序所繼承  
sa.lpSecurityDescriptor = NULL; // 預設為NULL
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
}

各引數在原型中已有很好的註釋。

建立好管道後,可以考慮建立子程序,使其繼承父程序的管道控制代碼。

建立子程序

先貼程式碼:

TCHAR szCmdline[] = TEXT("../../child/Debug/child.exe"); // 設定子程序路徑
PROCESS_INFORMATION pi;  // 用來接收新程序的識別資訊
STARTUPINFO si;  // 用於決定新程序的主窗體如何顯示
BOOL bSuccess = FALSE;

// 設定PROCESS_INFORMATION
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));  // 用0填充記憶體區域
// 設定STARTUPINFO
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);  // 結構大小
  //*************** 控制代碼繼承設定******************
  // 建立了兩個管道
  // 管道1由父程序讀,子程序寫
  // 管道2由父程序寫,子程序讀
si.hStdError = write1;      // 錯誤輸出控制代碼(在寫控制代碼中寫回父程序)
si.hStdOutput = write1;     // 子程序繼承管道1寫控制代碼
si.hStdInput = read2;    // 子程序繼承管道2讀控制代碼
  //*************** 控制代碼繼承設定******************
si.dwFlags |= STARTF_USESTDHANDLES;  // 使用hStdInput 、hStdOutput 和hStdError 成員  
          // 建立子程序
 // 摘自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 
   // 以上兩個欄位都可以建立目標子程序
NULL,          // process security attributes 
NULL,          // primary thread security attributes 
TRUE,          // bInheritHandles:指示新程序是否從呼叫程序處繼承了控制代碼
0,    // creation flags:指定附加的、用來控制優先類和程序的建立的標誌。
   // 設定為 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 << "建立子程式失敗" << endl;
else
{
// 關閉一些子程序用的控制代碼
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(write1);
CloseHandle(read2);
}

首先設定子程序所在路徑,子程序為一個exe可執行程式。然後會用到兩個型別STARTUPINFO和PROCESS_INFORMATION,有興趣的朋友可自行百度,檢視兩種類中的引數。

這裡也不貼CreateProcess的函式原型了,程式碼塊中有較好的註釋。

其實對於管道建立和子程序建立都是一個模版框架。

讀寫函式請見github原始碼

實現雙向通訊

在父程序中建立兩個匿名管道。此時父程序共有六個控制代碼Read1,Write1,Read2,Write2,標準輸入輸出控制代碼。

                                  

由圖所示,標準輸入輸出控制代碼用於在Dos視窗的輸入和輸出。

然後我們需要讓建立的子程序繼承Write1控制代碼和Read2控制代碼。

                                        

子程序初始化控制代碼程式碼

read = GetStdHandle(STD_INPUT_HANDLE); // 繼承控制代碼
write = GetStdHandle(STD_OUTPUT_HANDLE);
if ((read == INVALID_HANDLE_VALUE) || (write == INVALID_HANDLE_VALUE))
cout << "繼承控制代碼無效" << endl;

可以看到,子程序的標準輸入輸出控制代碼已經被繼承的Write1控制代碼和Read2控制代碼所覆蓋。

因此無法實現在子程序的Dos視窗進行顯示,子程序視窗將是永遠黑窗,可以在父程式中註釋掉子程序所繼承的寫控制代碼進行對比,並將CreateProcess函式中的一個引數設定為顯示子程序視窗(註釋中有)。

需要注意的坑點:

  • si.dwFlags |= STARTF_USESTDHANDLES;

  • 若要實現雙向通訊,子程序Dos是黑窗。但是可以將子程序收到的結果寫到檔案。

原始碼地址:https://github.com/YuxiangTang/AnonymousPipe