ERROR_BROKEN_PIPE 无法读取进程终止前产生的进程输出

Posted

技术标签:

【中文标题】ERROR_BROKEN_PIPE 无法读取进程终止前产生的进程输出【英文标题】:ERROR_BROKEN_PIPE cannot read process output produced before process termination 【发布时间】:2015-01-27 12:19:28 【问题描述】:

我在 Win32 中有一个简单的进程重定向例程。这里的问题是,如果我在从子进程 stdout 的读取之间放置一个睡眠,一旦进程在我睡眠时终止,我就会错过输出 ERROR_BROKEN_PIPE 的管道中的最后一个字节。似乎,一旦子进程终止,它的管道和关联的句柄就会关闭,并且任何未决的东西都会被丢弃。唯一的解决方案似乎是尽可能快地从管道中读取文件,但由于软件的设计,这对我来说不仅仅是一个问题。

int _tmain(int argc, _TCHAR* argv[])

    BOOL bSuccess;
    WCHAR szCmdLine[MAX_PATH];
    char chBuf[BUFSIZE];
    DWORD dwRead;
    HANDLE g_hChildStd_OUT_Rd2 = NULL;
    HANDLE g_hChildStd_OUT_Wr2 = NULL;
    SECURITY_ATTRIBUTES saAttr2; 
    STARTUPINFO si2;
    PROCESS_INFORMATION pi2;

    ZeroMemory( &si2, sizeof(si2) );
    si2.cb = sizeof(si2);
    ZeroMemory( &pi2, sizeof(pi2) );
    //create pipe
    saAttr2.nLength = sizeof(SECURITY_ATTRIBUTES); 
    saAttr2.bInheritHandle = TRUE; 
    saAttr2.lpSecurityDescriptor = NULL; 
    assert(CreatePipe(&g_hChildStd_OUT_Rd2, &g_hChildStd_OUT_Wr2, &saAttr2, 0));
    //create child process
    bSuccess = FALSE;
    memset(szCmdLine, 0, MAX_PATH);
    wsprintf(szCmdLine, L"c:\\myprocess.exe");
    ZeroMemory( &pi2, sizeof(PROCESS_INFORMATION) );
    ZeroMemory( &si2, sizeof(STARTUPINFO) );
    si2.cb = sizeof(STARTUPINFO); 
    si2.hStdOutput = g_hChildStd_OUT_Wr2;
    si2.hStdError = g_hChildStd_OUT_Wr2; // also add the pipe as stderr!
    si2.dwFlags |= STARTF_USESTDHANDLES;
    assert(CreateProcess(NULL, szCmdLine, NULL, NULL, TRUE, 0, NULL, NULL, &si2, &pi2));
    //read from pipe
    CloseHandle(g_hChildStd_OUT_Wr2);
    memset(chBuf, 0, BUFSIZE);
    for (;;) 
     
        DWORD dwRead = 0;
        DWORD bytes = 0;
        if (!PeekNamedPipe(g_hChildStd_OUT_Rd2,NULL,NULL,NULL,&bytes,NULL)) 
            //printf("Peek named pipe failed!");
            break;
        

        if (bytes != 0) 
            if (!ReadFile( g_hChildStd_OUT_Rd2, chBuf, BUFSIZE, &dwRead, NULL)) 
            
                printf("EOF!!\n");
                break;

            
            else 
                chBuf[dwRead] = 0;
                printf("%s", chBuf);
            

         else 
            Sleep(5000);
        

    
    while(1) 
    Sleep(1000);
    printf("Lopp!\n");
    
    return 0;

有什么提示吗?有没有办法让进程保持暂停,就像在 POSIX 中发生的那样,直到它的管道被读取?

谢谢!

【问题讨论】:

你必须明确地这样做,在你终止之前调用 FlushFileBuffers()。 @HansPassant:这似乎没有必要。即使让子进程自行终止而不是干净地退出,我也无法重现该问题。除非孩子实际上是在调用 DisconnectNamedPipe 或同样奇怪的东西,否则我无法想象它会做什么来导致这种情况。 @Leonardo:假设问题确实出在子进程并且无法解决,最好的解决方法可能是不关闭g_hChildStd_OUT_Wr2。问题是这意味着 ReadFile/PeekNamedPipe 不会告诉您子进程何时退出,因此您必须单独检查。这意味着使用低效的轮询(或复杂的异步 I/O),但如果您找不到其他解决方案,这可能是唯一的出路。 我正在考虑将读取操作移到另一个阻塞线程上,这样我可以尽可能快地读取...... 这仍然是一个竞争条件,因为线程可能无法及时安排。它可能在大多数情况下都有效,但并不可靠。 【参考方案1】:

我认为问题不存在。

您的代码确实存在一些问题。许多是未成年人:你初始化了两次 si2 和 pi2(无害),你有一个无限循环,所以你的 prog 永远不会结束(我假设你 Ctrl-C ...),你正在混合 _tmain 和 WCHAR (应该是 @ 987654322@ 和 WCHAR_tmainTCHAR) 但如果你进行 unicode 构建就可以了。

一个更严重:chBuf 的大小为BUFSIZE,您可以在其中读取BUFSIZE 字符(直到这里还好)。但是,如果您确实阅读了 BUFSIZE 字符,dwReadBUFSIZE 并且在下一行您有缓冲区溢出

chBuf[dwRead] = 0; // buffer overflow if BUFSIZE chars have been read !

我只用 :

解决了这个问题
if (!ReadFile( g_hChildStd_OUT_Rd2, chBuf, BUFSIZE - 1, &dwRead, NULL))

程序正常运行。

顺便说一句:系统知道子进程(即使已终止),直到它的所有句柄都已关闭...并且您在 pi2.hProcess 中有一个句柄。

当然我不能使用c:\\myprocess.exe 并使用cmd /c dir 进行测试。因此,如果上述修复还不够,请尝试使用与我相同的孩子,因为问题可能来自myprocess.exe,它以 NULL hStdInput 开头

【讨论】:

那是我用来复制和发布的测试代码,生产代码有些不同。这并没有改变这样一个事实,即睡眠而不是快速获取最后一个字节,不会捕获输出。我开始认为问题出在我无法控制的子进程上.. @LeonardoBernardini :Hans Passant 的评论可能比我写的更明确,但我同意你们两个:恕我直言,问题出在子进程中,因为我无法使用cmd /dir 重现。但是您也可以尝试创建第二个管道(用于hStdInput),以便您的子进程可以在标准输入上读取而不会出错。【参考方案2】:

仅供参考,我想分享我的经验以及我是如何解决问题的。 我产生的子进程可能有问题,但据我所知,如果我在非阻塞模式下调用 Read,所以只有在 PeekNamedPipe 之后,没有什么可以阻止进程被释放,即使我保留对管道的引用。 我已经解决了启动另一个在管道描述符上阻塞读取的线程,并且我不再丢失最后一个字节..

【讨论】:

以上是关于ERROR_BROKEN_PIPE 无法读取进程终止前产生的进程输出的主要内容,如果未能解决你的问题,请参考以下文章

linux c编程:进程环境

匿名管道的 ReadFile 函数

其中一个子进程无法从管道中读取

无法在继承进程中读取文件

父进程无法读取来自 C 中 4 个不同管道的所有消息

无法从 Java 进程(Runtime.getRuntime().exec() 或 ProcessBuilder)读取 InputStream