2016-04-22 114 views
0

按照主題我試圖開發一個簡單的管道父/子程序。 該程序的主要目的是保持子進程的活動,並使用std::cinstd::cout在父進程/子進程之間進行通信。Windows:如何讓子進程讀取輸入匿名管道而不關閉它?

在Linux上,所有這些工作都很好。

在Windows上我一直在下面的例子中here,並有一個與Linux的一個奇特的差別:一個具有調用

CloseHandle(g_hChildStd_IN_Wr) 

要寫入孩子管沖洗掉。這有關閉管道的副作用,因此將我的連接終止到子進程。
我也試過使用FlushFileBuffers,但它不起作用。

任何想法我怎麼能刷新緩衝區而不必關閉匿名管

以下兩種來源過程。
如果過程的代碼基本上是一個在上面的例子:

// IN_Wr_ is initialized as below with bInheritHandle=TRUE 
::CreatePipe(&IN_Rd_, &IN_Wr_, &saAttr, 0); 
// and 
::SetHandleInformation(IN_Wr_, HANDLE_FLAG_INHERIT, 0) 
// When I spawn the child process I do 
STARTUPINFO   siStartInfo = {0}; 
siStartInfo.cb = sizeof(STARTUPINFO); 
siStartInfo.hStdError = INVALID_HANDLE_VALUE; 
siStartInfo.hStdOutput = OUT_Wr_; 
siStartInfo.hStdInput = IN_Rd_; 
siStartInfo.dwFlags |= STARTF_USESTDHANDLES; 
... 
// then in order to write to std::cin 
const DWORD reqSz = static_cast<DWORD>(std::strlen(request)); 
DWORD  written = 0; 
while(true) { 
    DWORD curWritten = 0; 
    if(!WriteFile(IN_Wr_, request + written, reqSz-written, &curWritten, NULL)) 
     throw std::runtime_error("Error on WriteFile"); 
    written += curWritten; 
    if(written == reqSz) { 
     // all written, done 
     break; 
    } 
} 
::FlushFileBuffers(IN_Wr_); 
// only when I do this CloseHandle then the child process 
// is able to read data 
::CloseHandle(IN_Wr_); 

孩子代碼是一個簡單回聲服務器,沿着線:

buif[2048+1]; 
while(std::cin) { 
    std::cin.read(buf, 2048); 
    const auto rb = std::cin.gcount(); 
    buf[rb] = '\0'; 
    std::cout << buf << std::endl; // this does flush 
} 
+0

好吧,首先,linux沒有那個MSDN函數:'CloseHandle()'。然後使用語法::'fflush(FILE * stream);' – user3629249

+0

可以刷新任何輸出流。無需任何操作*來刷新管道。該示例專門關閉句柄以通知孩子它已完成,而不是因爲孩子不能讀取數據。 (如果你使用運行時庫例程,顯然你需要刷新這些緩衝區,但是你不需要在操作系統級別做任何事情。) –

+0

@HarryJohnston沒有*關閉*管道,子進程*塊*在Windows上無限期地在該流('std :: cin')上進行測試(在Windows 7上測試)。嘗試發送〜10個字節。發生什麼事是那些保持緩衝,直到或緩衝區已滿或*刷新*。唉'FlushFileBuffers'不起作用,因此唯一的辦法就是'CloseHandle'。其中*強制*沖洗*,但也關閉了溝通渠道。 – Emanuele

回答

1

這是你的問題:

std::cin.read(buf, 2048); 

它在做什麼你問什麼它:等到已經閱讀2048個字符或到達文件末尾。您不會發送2048個字符,因此在服務器關閉管道之前什麼也沒有發生,管道作爲此上下文中的文件末尾。

相反,你應該使用類似getline(s, 2048, '\0')的東西,它將在看到空字符時停止閱讀。(當然,您需要修改發件人,以便在字符串末尾寫入空字符)。

或者,您可以使用本機API:ReadFile具有您似乎想要的語義。理想情況下,您將使用消息模式管道,該管道專爲此類使用而設計。

+0

在Java中有趣的是,類似的API 'BufferedReader.read'類似於低級別的API('read'和/或'ReadFile')。 – Emanuele

0

這篇文章可能有幫助:https://support.microsoft.com/en-us/kb/190351。當使用printf將數據發送到重定向管道時,它有一個關於刷新問題的部分,這似乎是在你的情況下完成的。建議的解決方案是使用fflush(NULL)來刷新C運行時IO緩衝區。

+0

不是一個不合理的建議,但我不認爲這是問題。如果是,那麼調用CloseHandle將無法工作,因爲這不會影響運行時庫的輸出緩衝區。 –

0

看起來問題是std::cin::read(甚至fread(..., ..., ..., stdin))的MSFT實現。 如果不是依靠:

// C++ API 
while(std::cin) { 
    std::cin.read(buf, 2048); 
    ... 
// or also "C" API 
int rb = 0; 
while(0 < (rb = fread(buf, 2048, 1, stdin))) { 
    ... 

我做

// Low level Win32 "C" API 
while(::ReadFile(hStdin, buf, 2048, &rb, 0)) { 
    ... 
// or also low level unix-like "C" API 
int rb = 0; 
while(0 < (rb = _read(0, buf, 2048))) { 
    ... 

上面的例子只是正常工作(有趣的是,在調用FlushFileBuffers甚至不需要)。

+1

這是因爲'istream :: read'會讀取指定數量的字符,除非您到達文件末尾,在管道上下文中意味着管道的另一端關閉。另一方面,'ReadFile'有一個特殊情況:在管道的另一端完成寫入操作時退出。 (如果在Linux上使用istream :: read,*表示*可能是實現錯誤,因爲C++標準似乎不允許讀取提前退出,除非文件結束。) –