【问题标题】:WinSock Non-Blocking I/OWinSock 非阻塞 I/O
【发布时间】:2020-05-24 18:30:13
【问题描述】:

我有一个 WSASocket,当连接调用 CreateProcess 和服务器 cmd.exe 时,我想在进程 hStdInput 和套接字句柄之间实现一个管道来解析通过套接字发送的命令,一切似乎都运行顺利,除了因为当我运行像“ping 127.0.0.1”这样的命令并且必须等待输出时,在我通过套接字发送更多数据之前什么都没有显示,看来我的 ReadFile 调用正在阻止 hStdOut 处理程序发送任何内容。有没有什么办法解决这一问题?请不要被我的代码冒犯我正在编写这个项目作为学习练习,任何帮助将不胜感激。

int syncShell(SOCKET *ConnectSocket) {
    int iResult = 0;
    printf("Spawning process\n");
    char Process[] = "C:\\Windows\\System32\\cmd.exe";
    STARTUPINFO sinfo;
    PROCESS_INFORMATION pinfo;
    memset(&sinfo, 0, sizeof(sinfo));
    sinfo.cb = sizeof(sinfo);
    sinfo.dwFlags = (STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW);

    // create pipe for external commands
    SECURITY_ATTRIBUTES  saAttr;
    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
    saAttr.bInheritHandle = TRUE; 
    saAttr.lpSecurityDescriptor = NULL;
    HANDLE hReadPipe = NULL, hWritePipe = NULL;
    iResult = CreatePipe(&hReadPipe, &hWritePipe, &saAttr, DEFAULT_BUFLEN);
    if (iResult == 0) {
        printf("Pipe Error");
    }

    sinfo.hStdOutput = sinfo.hStdError = (HANDLE) *ConnectSocket;
    sinfo.hStdInput = hReadPipe;

    if (!CreateProcessA(NULL, Process, NULL, NULL, TRUE, 0, NULL, NULL, &sinfo, &pinfo)) {
        printf("CreateProcess failed (%d).\n", GetLastError());
    }

    // implement pipe logic
    char buf[DEFAULT_BUFLEN];
    DWORD len = 0;
    WSABUF DataBuf;

    while (1) {
        // causing the block?
        iResult = ReadFile((HANDLE) *ConnectSocket, buf, DEFAULT_BUFLEN, &len, NULL);
        if (iResult == 0) {
            printf("File Error or non-blocking");
        }
        else {
            printf("%d: %.*s\n", len, len, buf);
            WriteFile(hWritePipe, buf, len, NULL, NULL);
        }
        Sleep(1000);
    }

    WaitForSingleObject(pinfo.hProcess, INFINITE); // waits till proc finishes
    CloseHandle(pinfo.hProcess);
    CloseHandle(pinfo.hThread);
    printf("Process exited\n");

    return 0;
}

【问题讨论】:

  • 如果您只是在两次读取之间睡觉,那么使用非阻塞 I/O 完全没有意义。你最好屏蔽掉。
  • 我把它作为占位符放在那里,即使它不存在,但它仍然会阻止 hStdOut。
  • 难以理解。为什么要将进程自己的输出写回它也是一个持续的谜。
  • 感谢您的关注 207,在原始帖子中我提到我将通过管道解析命令。为简单起见,我忽略了这一点,这可能会让一些人感到困惑......
  • 让除了你之外的所有人都感到困惑。

标签: c++ c sockets winapi pipe


【解决方案1】:

首先,根据[ReadFile]文档:

对于异步读取操作,hFile 可以是任何句柄 由CreateFile 函数以FILE_FLAG_OVERLAPPED 标志打开, 或socketaccept函数返回的套接字句柄。

socket 默认使用WSA_FLAG_OVERLAPPED 创建套接字句柄。 如果您传递重叠句柄并将ReadFile 的最后一个参数设置为NULL,您将得到错误代码87(ERROR_INVALID_PARAMETER)。 使用重叠的示例:

OVERLAPPED oRead = { 0 };
oRead.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);  
iResult = ReadFile((HANDLE)ConnectSocket, buf, DEFAULT_BUFLEN, &len, &oRead);
if (!iResult && GetLastError() == ERROR_IO_PENDING)
{
    WaitForSingleObject(oRead.hEvent, INFINITE);
}
buf[oRead.InternalHigh] = 0;  //set string terminator for printf
printf("%s\n", buf);
WriteFile(hWritePipe1, buf, oRead.InternalHigh, NULL, NULL);

而且最好直接使用recv()

iResult = recv(ConnectSocket, buf, DEFAULT_BUFLEN, 0);
buf[iResult] = 0;  //set string terminator for printf
printf("%s\n", buf);
WriteFile(hWritePipe1, buf, iResult, NULL, NULL);

此外,重叠的套接字可用于将 IO 重定向到子进程 您可以创建 2 个管道来与子进程通信:

iResult = CreatePipe(&hReadPipe1, &hWritePipe1, &saAttr, DEFAULT_BUFLEN);
if (iResult == 0) {
    printf("Pipe Error");
}
iResult = CreatePipe(&hReadPipe2, &hWritePipe2, &saAttr, DEFAULT_BUFLEN);
if (iResult == 0) {
    printf("Pipe Error");
} 

从子进程(cmd.exe)读取,并发送给客户端。

或者,

只需使用WSASocket 而不是socket,并且不要指定WSA_FLAG_OVERLAPPED。(推荐)

【讨论】:

  • 我意识到我的子进程无法处理比字符串大的缓冲区,这就是为什么它没有给我输出,我仍然给你检查,因为你的回答真的很丰富谢谢!