You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 04:53:12