Windows C++中ReadFile复制空文件时阻塞不返回问题排查
解决PSCP复制空文件时ReadFile阻塞的问题
问题场景
调用PSCP将Linux服务器文件复制到Windows本地时,若待复制文件为0字节,ReadFile()会持续阻塞不返回;但文件包含数据时可正常运行。若在ReadFile()前调用CloseHandle(hWritePipe)能解决阻塞,但因需要先检查复制状态、处理错误或密码输入,后续还要执行WriteFile()和再次读取,因此不想提前关闭该句柄。
阻塞原因
复制空文件时,PSCP进程不会向stdout/stderr输出任何内容,其持有的管道写端不会主动关闭。而父进程仍保留hWritePipe句柄,导致管道所有写端未完全关闭,ReadFile()会一直等待数据输入,无法退出循环。
解决方案
方案1:异步读取+等待进程退出
通过异步I/O读取管道,同时监听子进程状态,当进程结束后立即终止读取,避免阻塞。
修改后的代码示例:
#include <windows.h> #include <iostream> #include <string> #include <vector> #define BUFSIZE 4096 int main() { LPCTSTR commandLine = TEXT("D:\\tools\\pscp.exe -scp -2 user@linuxserver:'/home/user/testzero.cmd' D:\\Tools\\ProcessExplorer\\testzero.cmd"); STARTUPINFO si; PROCESS_INFORMATION pi; SECURITY_ATTRIBUTES sa; HANDLE hReadPipe = NULL, hWritePipe = NULL; char buffer[BUFSIZE]; DWORD bytesRead; BOOL bSuccess; ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES)); sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.bInheritHandle = TRUE; sa.lpSecurityDescriptor = NULL; if (!CreatePipe(&hReadPipe, &hWritePipe, &sa, 0)) { std::cerr << "CreatePipe failed. Error code: " << GetLastError() << std::endl; return 1; } wchar_t* writableCommandLine = _wcsdup(commandLine); ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); si.hStdError = hWritePipe; si.hStdOutput = hWritePipe; si.dwFlags |= STARTF_USESTDHANDLES; if (!CreateProcess(NULL, writableCommandLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { std::cerr << "Failed to create process. Error code: " << GetLastError() << std::endl; CloseHandle(hReadPipe); CloseHandle(hWritePipe); free(writableCommandLine); return 1; } // 初始化异步读取事件 OVERLAPPED ov = {0}; ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (ov.hEvent == NULL) { std::cerr << "CreateEvent failed. Error code: " << GetLastError() << std::endl; CloseHandle(hReadPipe); CloseHandle(hWritePipe); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); free(writableCommandLine); return 1; } // 同时监听进程退出和管道读取事件 HANDLE waitHandles[] = {pi.hProcess, ov.hEvent}; BOOL isReading = TRUE; while (isReading) { // 发起异步读取 bSuccess = ReadFile(hReadPipe, buffer, BUFSIZE, &bytesRead, &ov); if (bSuccess && bytesRead > 0) { std::string output(buffer, bytesRead); std::cout << output; ResetEvent(ov.hEvent); } else if (GetLastError() == ERROR_IO_PENDING) { DWORD waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE); switch (waitResult) { case WAIT_OBJECT_0: // 进程已退出,终止读取 isReading = FALSE; break; case WAIT_OBJECT_0 + 1: // 读取完成,处理数据 if (GetOverlappedResult(hReadPipe, &ov, &bytesRead, FALSE) && bytesRead > 0) { std::string output(buffer, bytesRead); std::cout << output; } ResetEvent(ov.hEvent); break; default: // 等待出错,终止循环 std::cerr << "Wait failed. Error code: " << GetLastError() << std::endl; isReading = FALSE; break; } } else { // 读取出错或无数据,终止循环 break; } } // 清理资源 CloseHandle(ov.hEvent); CloseHandle(hWritePipe); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); CloseHandle(hReadPipe); free(writableCommandLine); return 0; }
方案2:复制管道写句柄,关闭父进程的hWritePipe
子进程已继承管道写句柄,父进程可关闭自身持有的hWritePipe,让管道写端仅由子进程持有。当子进程退出时,写端自动关闭,ReadFile()会返回0并结束循环。该方法不影响子进程输出,还可配合额外的stdin管道处理密码输入。
关键修改代码段:
// 创建管道后,复制写句柄供子进程使用 HANDLE hChildWritePipe; if (!DuplicateHandle(GetCurrentProcess(), hWritePipe, GetCurrentProcess(), &hChildWritePipe, 0, TRUE, DUPLICATE_SAME_ACCESS)) { std::cerr << "DuplicateHandle failed. Error code: " << GetLastError() << std::endl; return 1; } // 将子进程的stdout/stderr指向复制后的句柄 si.hStdError = hChildWritePipe; si.hStdOutput = hChildWritePipe; // 立即关闭父进程持有的原写句柄 CloseHandle(hWritePipe);
内容的提问来源于stack exchange,提问作者madoverdose
相关产品推荐
相关产品推荐

